Skip to content

Commit

Permalink
JSEntryTrampoline: check for stack space before pushing arguments
Browse files Browse the repository at this point in the history
Optimistically pushing a lot of arguments can run into the stack limit of the process, at least on operating systems where this limit is close to the limit that V8 sets for itself.

BUG=chromium:469768
LOG=y

Review URL: https://codereview.chromium.org/1056913003

Cr-Commit-Position: refs/heads/master@{#27614}
  • Loading branch information
jakobkummerow authored and Commit bot committed Apr 7, 2015
1 parent 13d6de4 commit 146598f
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 123 deletions.
68 changes: 43 additions & 25 deletions src/arm/builtins-arm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,39 @@ void Builtins::Generate_JSConstructStubForDerived(MacroAssembler* masm) {
}


enum IsTagged { kArgcIsSmiTagged, kArgcIsUntaggedInt };


// Clobbers r2; preserves all other registers.
static void Generate_CheckStackOverflow(MacroAssembler* masm,
const int calleeOffset, Register argc,
IsTagged argc_is_tagged) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
Label okay;
__ LoadRoot(r2, Heap::kRealStackLimitRootIndex);
// Make r2 the space we have left. The stack might already be overflowed
// here which will cause r2 to become negative.
__ sub(r2, sp, r2);
// Check if the arguments will overflow the stack.
if (argc_is_tagged == kArgcIsSmiTagged) {
__ cmp(r2, Operand::PointerOffsetFromSmiKey(argc));
} else {
DCHECK(argc_is_tagged == kArgcIsUntaggedInt);
__ cmp(r2, Operand(argc, LSL, kPointerSizeLog2));
}
__ b(gt, &okay); // Signed comparison.

// Out of stack space.
__ ldr(r1, MemOperand(fp, calleeOffset));
__ Push(r1, argc);
__ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION);

__ bind(&okay);
}


static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
bool is_construct) {
// Called from Generate_JS_Entry
Expand Down Expand Up @@ -857,6 +890,14 @@ static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
__ push(r1);
__ push(r2);

// Check if we have enough stack space to push all arguments.
// The function is the first thing that was pushed above after entering
// the internal frame.
const int kFunctionOffset =
InternalFrameConstants::kCodeOffset - kPointerSize;
// Clobbers r2.
Generate_CheckStackOverflow(masm, kFunctionOffset, r3, kArgcIsUntaggedInt);

// Copy arguments to the stack in a loop.
// r1: function
// r3: argc
Expand Down Expand Up @@ -1336,29 +1377,6 @@ void Builtins::Generate_FunctionCall(MacroAssembler* masm) {
}


static void Generate_CheckStackOverflow(MacroAssembler* masm,
const int calleeOffset) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
Label okay;
__ LoadRoot(r2, Heap::kRealStackLimitRootIndex);
// Make r2 the space we have left. The stack might already be overflowed
// here which will cause r2 to become negative.
__ sub(r2, sp, r2);
// Check if the arguments will overflow the stack.
__ cmp(r2, Operand::PointerOffsetFromSmiKey(r0));
__ b(gt, &okay); // Signed comparison.

// Out of stack space.
__ ldr(r1, MemOperand(fp, calleeOffset));
__ Push(r1, r0);
__ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION);

__ bind(&okay);
}


static void Generate_PushAppliedArguments(MacroAssembler* masm,
const int argumentsOffset,
const int indexOffset,
Expand Down Expand Up @@ -1416,7 +1434,7 @@ static void Generate_ApplyHelper(MacroAssembler* masm, bool targetIsArgument) {
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION);
}

Generate_CheckStackOverflow(masm, kFunctionOffset);
Generate_CheckStackOverflow(masm, kFunctionOffset, r0, kArgcIsSmiTagged);

// Push current limit and index.
const int kIndexOffset =
Expand Down Expand Up @@ -1544,7 +1562,7 @@ static void Generate_ConstructHelper(MacroAssembler* masm) {
__ push(r0);
__ InvokeBuiltin(Builtins::REFLECT_CONSTRUCT_PREPARE, CALL_FUNCTION);

Generate_CheckStackOverflow(masm, kFunctionOffset);
Generate_CheckStackOverflow(masm, kFunctionOffset, r0, kArgcIsSmiTagged);

// Push current limit and index.
const int kIndexOffset =
Expand Down
84 changes: 51 additions & 33 deletions src/arm64/builtins-arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,46 @@ void Builtins::Generate_JSConstructStubForDerived(MacroAssembler* masm) {
}


enum IsTagged { kArgcIsSmiTagged, kArgcIsUntaggedInt };


// Clobbers x10, x15; preserves all other registers.
static void Generate_CheckStackOverflow(MacroAssembler* masm,
const int calleeOffset, Register argc,
IsTagged argc_is_tagged) {
Register function = x15;

// Check the stack for overflow.
// We are not trying to catch interruptions (e.g. debug break and
// preemption) here, so the "real stack limit" is checked.
Label enough_stack_space;
__ LoadRoot(x10, Heap::kRealStackLimitRootIndex);
__ Ldr(function, MemOperand(fp, calleeOffset));
// Make x10 the space we have left. The stack might already be overflowed
// here which will cause x10 to become negative.
// TODO(jbramley): Check that the stack usage here is safe.
__ Sub(x10, jssp, x10);
// Check if the arguments will overflow the stack.
if (argc_is_tagged == kArgcIsSmiTagged) {
__ Cmp(x10, Operand::UntagSmiAndScale(argc, kPointerSizeLog2));
} else {
DCHECK(argc_is_tagged == kArgcIsUntaggedInt);
__ Cmp(x10, Operand(argc, LSL, kPointerSizeLog2));
}
__ B(gt, &enough_stack_space);
// There is not enough stack space, so use a builtin to throw an appropriate
// error.
__ Push(function, argc);
__ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION);
// We should never return from the APPLY_OVERFLOW builtin.
if (__ emit_debug_code()) {
__ Unreachable();
}

__ Bind(&enough_stack_space);
}


// Input:
// x0: code entry.
// x1: function.
Expand Down Expand Up @@ -832,6 +872,15 @@ static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
// Push the function and the receiver onto the stack.
__ Push(function, receiver);

// Check if we have enough stack space to push all arguments.
// The function is the first thing that was pushed above after entering
// the internal frame.
const int kFunctionOffset =
InternalFrameConstants::kCodeOffset - kPointerSize;
// Expects argument count in eax. Clobbers ecx, edx, edi.
Generate_CheckStackOverflow(masm, kFunctionOffset, argc,
kArgcIsUntaggedInt);

// Copy arguments to the stack in a loop, in reverse order.
// x3: argc.
// x4: argv.
Expand Down Expand Up @@ -1324,37 +1373,6 @@ void Builtins::Generate_FunctionCall(MacroAssembler* masm) {
}


static void Generate_CheckStackOverflow(MacroAssembler* masm,
const int calleeOffset) {
Register argc = x0;
Register function = x15;

// Check the stack for overflow.
// We are not trying to catch interruptions (e.g. debug break and
// preemption) here, so the "real stack limit" is checked.
Label enough_stack_space;
__ LoadRoot(x10, Heap::kRealStackLimitRootIndex);
__ Ldr(function, MemOperand(fp, calleeOffset));
// Make x10 the space we have left. The stack might already be overflowed
// here which will cause x10 to become negative.
// TODO(jbramley): Check that the stack usage here is safe.
__ Sub(x10, jssp, x10);
// Check if the arguments will overflow the stack.
__ Cmp(x10, Operand::UntagSmiAndScale(argc, kPointerSizeLog2));
__ B(gt, &enough_stack_space);
// There is not enough stack space, so use a builtin to throw an appropriate
// error.
__ Push(function, argc);
__ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION);
// We should never return from the APPLY_OVERFLOW builtin.
if (__ emit_debug_code()) {
__ Unreachable();
}

__ Bind(&enough_stack_space);
}


static void Generate_PushAppliedArguments(MacroAssembler* masm,
const int argumentsOffset,
const int indexOffset,
Expand Down Expand Up @@ -1422,7 +1440,7 @@ static void Generate_ApplyHelper(MacroAssembler* masm, bool targetIsArgument) {
}
Register argc = x0;

Generate_CheckStackOverflow(masm, kFunctionOffset);
Generate_CheckStackOverflow(masm, kFunctionOffset, argc, kArgcIsSmiTagged);

// Push current limit and index.
__ Mov(x1, 0); // Initial index.
Expand Down Expand Up @@ -1549,7 +1567,7 @@ static void Generate_ConstructHelper(MacroAssembler* masm) {
__ InvokeBuiltin(Builtins::REFLECT_CONSTRUCT_PREPARE, CALL_FUNCTION);
Register argc = x0;

Generate_CheckStackOverflow(masm, kFunctionOffset);
Generate_CheckStackOverflow(masm, kFunctionOffset, argc, kArgcIsSmiTagged);

// Push current limit and index, constructor & newTarget
__ Mov(x1, 0); // Initial index.
Expand Down
82 changes: 48 additions & 34 deletions src/ia32/builtins-ia32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,44 @@ void Builtins::Generate_JSConstructStubForDerived(MacroAssembler* masm) {
}


enum IsTagged { kEaxIsSmiTagged, kEaxIsUntaggedInt };


// Clobbers ecx, edx, edi; preserves all other registers.
static void Generate_CheckStackOverflow(MacroAssembler* masm,
const int calleeOffset,
IsTagged eax_is_tagged) {
// eax : the number of items to be pushed to the stack
//
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
Label okay;
ExternalReference real_stack_limit =
ExternalReference::address_of_real_stack_limit(masm->isolate());
__ mov(edi, Operand::StaticVariable(real_stack_limit));
// Make ecx the space we have left. The stack might already be overflowed
// here which will cause ecx to become negative.
__ mov(ecx, esp);
__ sub(ecx, edi);
// Make edx the space we need for the array when it is unrolled onto the
// stack.
__ mov(edx, eax);
int smi_tag = eax_is_tagged == kEaxIsSmiTagged ? kSmiTagSize : 0;
__ shl(edx, kPointerSizeLog2 - smi_tag);
// Check if the arguments will overflow the stack.
__ cmp(ecx, edx);
__ j(greater, &okay); // Signed comparison.

// Out of stack space.
__ push(Operand(ebp, calleeOffset)); // push this
__ push(eax);
__ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION);

__ bind(&okay);
}


static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
bool is_construct) {
ProfileEntryHookStub::MaybeCallEntryHook(masm);
Expand All @@ -599,6 +637,14 @@ static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
__ mov(eax, Operand(ebx, EntryFrameConstants::kArgcOffset));
__ mov(ebx, Operand(ebx, EntryFrameConstants::kArgvOffset));

// Check if we have enough stack space to push all arguments.
// The function is the first thing that was pushed above after entering
// the internal frame.
const int kFunctionOffset =
InternalFrameConstants::kCodeOffset - kPointerSize;
// Expects argument count in eax. Clobbers ecx, edx, edi.
Generate_CheckStackOverflow(masm, kFunctionOffset, kEaxIsUntaggedInt);

// Copy arguments to the stack in a loop.
Label loop, entry;
__ Move(ecx, Immediate(0));
Expand Down Expand Up @@ -990,38 +1036,6 @@ void Builtins::Generate_FunctionCall(MacroAssembler* masm) {
}


static void Generate_CheckStackOverflow(MacroAssembler* masm,
const int calleeOffset) {
// eax : the number of items to be pushed to the stack
//
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
Label okay;
ExternalReference real_stack_limit =
ExternalReference::address_of_real_stack_limit(masm->isolate());
__ mov(edi, Operand::StaticVariable(real_stack_limit));
// Make ecx the space we have left. The stack might already be overflowed
// here which will cause ecx to become negative.
__ mov(ecx, esp);
__ sub(ecx, edi);
// Make edx the space we need for the array when it is unrolled onto the
// stack.
__ mov(edx, eax);
__ shl(edx, kPointerSizeLog2 - kSmiTagSize);
// Check if the arguments will overflow the stack.
__ cmp(ecx, edx);
__ j(greater, &okay); // Signed comparison.

// Out of stack space.
__ push(Operand(ebp, calleeOffset)); // push this
__ push(eax);
__ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION);

__ bind(&okay);
}


static void Generate_PushAppliedArguments(MacroAssembler* masm,
const int argumentsOffset,
const int indexOffset,
Expand Down Expand Up @@ -1099,7 +1113,7 @@ static void Generate_ApplyHelper(MacroAssembler* masm, bool targetIsArgument) {
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION);
}

Generate_CheckStackOverflow(masm, kFunctionOffset);
Generate_CheckStackOverflow(masm, kFunctionOffset, kEaxIsSmiTagged);

// Push current index and limit.
const int kLimitOffset =
Expand Down Expand Up @@ -1229,7 +1243,7 @@ static void Generate_ConstructHelper(MacroAssembler* masm) {
__ push(Operand(ebp, kNewTargetOffset));
__ InvokeBuiltin(Builtins::REFLECT_CONSTRUCT_PREPARE, CALL_FUNCTION);

Generate_CheckStackOverflow(masm, kFunctionOffset);
Generate_CheckStackOverflow(masm, kFunctionOffset, kEaxIsSmiTagged);

// Push current index and limit.
const int kLimitOffset =
Expand Down
Loading

0 comments on commit 146598f

Please sign in to comment.