Commit 1382879f authored by mstarzinger's avatar mstarzinger Committed by Commit bot

[turbofan] Implement throwing exceptions into TurboFan code.

This extends the stack unwinding logic to respect optimized frames
and perform a lookup in the handler table to find handlers. It also
contains fixes to the API call stubs to allow a stack walk while
promoting scheduled exceptions.

R=jarin@chromium.org
TEST=cctest/test-run-jsexceptions

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

Cr-Commit-Position: refs/heads/master@{#27016}
parent 62d860f1
......@@ -4813,7 +4813,6 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
}
Label promote_scheduled_exception;
Label exception_handled;
Label delete_allocated_handles;
Label leave_exit_frame;
Label return_value_loaded;
......@@ -4835,15 +4834,8 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ cmp(r5, ip);
__ b(ne, &delete_allocated_handles);
// Check if the function scheduled an exception.
// Leave the API exit frame.
__ bind(&leave_exit_frame);
__ LoadRoot(r4, Heap::kTheHoleValueRootIndex);
__ mov(ip, Operand(ExternalReference::scheduled_exception_address(isolate)));
__ ldr(r5, MemOperand(ip));
__ cmp(r4, r5);
__ b(ne, &promote_scheduled_exception);
__ bind(&exception_handled);
bool restore_context = context_restore_operand != NULL;
if (restore_context) {
__ ldr(cp, *context_restore_operand);
......@@ -4855,15 +4847,19 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ mov(r4, Operand(stack_space));
}
__ LeaveExitFrame(false, r4, !restore_context, stack_space_operand != NULL);
// Check if the function scheduled an exception.
__ LoadRoot(r4, Heap::kTheHoleValueRootIndex);
__ mov(ip, Operand(ExternalReference::scheduled_exception_address(isolate)));
__ ldr(r5, MemOperand(ip));
__ cmp(r4, r5);
__ b(ne, &promote_scheduled_exception);
__ mov(pc, lr);
// Re-throw by promoting a scheduled exception.
__ bind(&promote_scheduled_exception);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ CallExternalReference(
ExternalReference(Runtime::kPromoteScheduledException, isolate), 0);
}
__ jmp(&exception_handled);
__ TailCallRuntime(Runtime::kPromoteScheduledException, 0, 1);
// HandleScope limit has changed. Delete allocated extensions.
__ bind(&delete_allocated_handles);
......
......@@ -5266,7 +5266,6 @@ static void CallApiFunctionAndReturn(
}
Label promote_scheduled_exception;
Label exception_handled;
Label delete_allocated_handles;
Label leave_exit_frame;
Label return_value_loaded;
......@@ -5288,6 +5287,7 @@ static void CallApiFunctionAndReturn(
__ Cmp(limit_reg, x1);
__ B(ne, &delete_allocated_handles);
// Leave the API exit frame.
__ Bind(&leave_exit_frame);
// Restore callee-saved registers.
__ Peek(x19, (spill_offset + 0) * kXRegSize);
......@@ -5295,13 +5295,6 @@ static void CallApiFunctionAndReturn(
__ Peek(x21, (spill_offset + 2) * kXRegSize);
__ Peek(x22, (spill_offset + 3) * kXRegSize);
// Check if the function scheduled an exception.
__ Mov(x5, ExternalReference::scheduled_exception_address(isolate));
__ Ldr(x5, MemOperand(x5));
__ JumpIfNotRoot(x5, Heap::kTheHoleValueRootIndex,
&promote_scheduled_exception);
__ Bind(&exception_handled);
bool restore_context = context_restore_operand != NULL;
if (restore_context) {
__ Ldr(cp, *context_restore_operand);
......@@ -5312,6 +5305,13 @@ static void CallApiFunctionAndReturn(
}
__ LeaveExitFrame(false, x1, !restore_context);
// Check if the function scheduled an exception.
__ Mov(x5, ExternalReference::scheduled_exception_address(isolate));
__ Ldr(x5, MemOperand(x5));
__ JumpIfNotRoot(x5, Heap::kTheHoleValueRootIndex,
&promote_scheduled_exception);
if (stack_space_operand != NULL) {
__ Drop(x2, 1);
} else {
......@@ -5319,13 +5319,9 @@ static void CallApiFunctionAndReturn(
}
__ Ret();
// Re-throw by promoting a scheduled exception.
__ Bind(&promote_scheduled_exception);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ CallExternalReference(
ExternalReference(Runtime::kPromoteScheduledException, isolate), 0);
}
__ B(&exception_handled);
__ TailCallRuntime(Runtime::kPromoteScheduledException, 0, 1);
// HandleScope limit has changed. Delete allocated extensions.
__ Bind(&delete_allocated_handles);
......
......@@ -1870,16 +1870,8 @@ void AstGraphBuilder::VisitYield(Yield* expr) {
void AstGraphBuilder::VisitThrow(Throw* expr) {
VisitForValue(expr->exception());
Node* exception = environment()->Pop();
if (FLAG_turbo_exceptions) {
execution_control()->ThrowValue(exception);
ast_context()->ProduceValue(exception);
} else {
// TODO(mstarzinger): Temporary workaround for bailout-id for debugger.
const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1);
Node* value = NewNode(op, exception);
PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine());
Node* value = BuildThrowError(exception, expr->id());
ast_context()->ProduceValue(value);
}
}
......@@ -2859,9 +2851,16 @@ Node* AstGraphBuilder::BuildSetHomeObject(Node* value, Node* home_object,
}
Node* AstGraphBuilder::BuildThrowError(Node* exception, BailoutId bailout_id) {
const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1);
Node* call = NewNode(op, exception);
PrepareFrameState(call, bailout_id);
return call;
}
Node* AstGraphBuilder::BuildThrowReferenceError(Variable* variable,
BailoutId bailout_id) {
// TODO(mstarzinger): Should be unified with the VisitThrow implementation.
Node* variable_name = jsgraph()->Constant(variable->name());
const Operator* op =
javascript()->CallRuntime(Runtime::kThrowReferenceError, 1);
......@@ -2872,7 +2871,6 @@ Node* AstGraphBuilder::BuildThrowReferenceError(Variable* variable,
Node* AstGraphBuilder::BuildThrowConstAssignError(BailoutId bailout_id) {
// TODO(mstarzinger): Should be unified with the VisitThrow implementation.
const Operator* op =
javascript()->CallRuntime(Runtime::kThrowConstAssignError, 0);
Node* call = NewNode(op);
......
......@@ -240,6 +240,7 @@ class AstGraphBuilder : public AstVisitor {
Node* BuildSetHomeObject(Node* value, Node* home_object, Expression* expr);
// Builders for error reporting at runtime.
Node* BuildThrowError(Node* exception, BailoutId bailout_id);
Node* BuildThrowReferenceError(Variable* var, BailoutId bailout_id);
Node* BuildThrowConstAssignError(BailoutId bailout_id);
......
......@@ -278,6 +278,9 @@ class StackFrame BASE_EMBEDDED {
// Checks if this frame includes any stack handlers.
bool HasHandler() const;
// Get the top handler from the current stack iterator.
inline StackHandler* top_handler() const;
// Get the type of this frame.
virtual Type type() const = 0;
......@@ -311,7 +314,6 @@ class StackFrame BASE_EMBEDDED {
// Resolves pc_address through the resolution address function if one is set.
static inline Address* ResolveReturnAddressLocation(Address* pc_address);
// Printing support.
enum PrintMode { OVERVIEW, DETAILS };
virtual void Print(StringStream* accumulator,
......@@ -332,9 +334,6 @@ class StackFrame BASE_EMBEDDED {
PrintMode mode,
int index);
// Get the top handler from the current stack iterator.
inline StackHandler* top_handler() const;
// Compute the stack frame type for the given state.
static Type ComputeType(const StackFrameIteratorBase* iterator, State* state);
......
......@@ -4893,7 +4893,6 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ mov(eax, return_value_operand);
Label promote_scheduled_exception;
Label exception_handled;
Label delete_allocated_handles;
Label leave_exit_frame;
......@@ -4905,7 +4904,17 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ Assert(above_equal, kInvalidHandleScopeLevel);
__ cmp(edi, Operand::StaticVariable(limit_address));
__ j(not_equal, &delete_allocated_handles);
// Leave the API exit frame.
__ bind(&leave_exit_frame);
bool restore_context = context_restore_operand != NULL;
if (restore_context) {
__ mov(esi, *context_restore_operand);
}
if (stack_space_operand != nullptr) {
__ mov(ebx, *stack_space_operand);
}
__ LeaveApiExitFrame(!restore_context);
// Check if the function scheduled an exception.
ExternalReference scheduled_exception_address =
......@@ -4913,7 +4922,6 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ cmp(Operand::StaticVariable(scheduled_exception_address),
Immediate(isolate->factory()->the_hole_value()));
__ j(not_equal, &promote_scheduled_exception);
__ bind(&exception_handled);
#if DEBUG
// Check if the function returned a valid JavaScript value.
......@@ -4950,14 +4958,6 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ bind(&ok);
#endif
bool restore_context = context_restore_operand != NULL;
if (restore_context) {
__ mov(esi, *context_restore_operand);
}
if (stack_space_operand != nullptr) {
__ mov(ebx, *stack_space_operand);
}
__ LeaveApiExitFrame(!restore_context);
if (stack_space_operand != nullptr) {
DCHECK_EQ(0, stack_space);
__ pop(ecx);
......@@ -4967,12 +4967,9 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ ret(stack_space * kPointerSize);
}
// Re-throw by promoting a scheduled exception.
__ bind(&promote_scheduled_exception);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kPromoteScheduledException, 0);
}
__ jmp(&exception_handled);
__ TailCallRuntime(Runtime::kPromoteScheduledException, 0, 1);
// HandleScope limit has changed. Delete allocated extensions.
ExternalReference delete_extensions =
......
......@@ -1031,29 +1031,94 @@ Object* Isolate::ReThrow(Object* exception) {
}
// TODO(turbofan): Make sure table is sorted and use binary search.
static int LookupInHandlerTable(Code* code, int pc_offset) {
FixedArray* handler_table = code->handler_table();
for (int i = 0; i < handler_table->length(); i += 2) {
int return_offset = Smi::cast(handler_table->get(i))->value();
int handler_offset = Smi::cast(handler_table->get(i + 1))->value();
if (pc_offset == return_offset) return handler_offset;
}
return -1;
}
Object* Isolate::FindHandler() {
Object* exception = pending_exception();
// Determine target stack handler. Special handling of termination exceptions
// which are uncatchable by JavaScript code, we unwind the handlers until the
// top ENTRY handler is found.
StackHandler* handler =
StackHandler::FromAddress(Isolate::handler(thread_local_top()));
if (!is_catchable_by_javascript(exception)) {
while (!handler->is_js_entry()) handler = handler->next();
Code* code = nullptr;
Context* context = nullptr;
intptr_t offset = 0;
Address handler_sp = nullptr;
Address handler_fp = nullptr;
// Special handling of termination exceptions, uncatchable by JavaScript code,
// we unwind the handlers until the top ENTRY handler is found.
bool catchable_by_js = is_catchable_by_javascript(exception);
// Compute handler and stack unwinding information by performing a full walk
// over the stack and dispatching according to the frame type.
for (StackFrameIterator iter(this); !iter.done(); iter.Advance()) {
StackFrame* frame = iter.frame();
// For JSEntryStub frames we always have a handler.
if (frame->is_entry() || frame->is_entry_construct()) {
StackHandler* handler = frame->top_handler();
DCHECK_EQ(StackHandler::JS_ENTRY, handler->kind());
DCHECK_EQ(0, handler->index());
// Restore the next handler.
thread_local_top()->handler_ = handler->next()->address();
// Gather information from the handler.
code = handler->code();
handler_sp = handler->address() + StackHandlerConstants::kSize;
offset = Smi::cast(code->handler_table()->get(0))->value();
break;
}
// For JavaScript frames which have a handler, we use the handler.
if (frame->is_java_script() && catchable_by_js && frame->HasHandler()) {
StackHandler* handler = frame->top_handler();
DCHECK_NE(StackHandler::JS_ENTRY, handler->kind());
// Restore the next handler.
thread_local_top()->handler_ = handler->next()->address();
// Compute handler and stack unwinding information.
// TODO(mstarzinger): Extend this to perform actual stack-walk and take into
// account that TurboFan code can contain handlers as well.
Code* code = handler->code();
Context* context = handler->is_js_entry() ? nullptr : handler->context();
int offset = Smi::cast(code->handler_table()->get(handler->index()))->value();
Address handler_sp = handler->address() + StackHandlerConstants::kSize;
Address handler_fp = handler->frame_pointer();
// Gather information from the handler.
code = handler->code();
context = handler->context();
offset = Smi::cast(code->handler_table()->get(handler->index()))->value();
handler_sp = handler->address() + StackHandlerConstants::kSize;
handler_fp = handler->frame_pointer();
break;
}
// For optimized frames we perform a lookup in the handler table.
if (frame->is_optimized() && catchable_by_js) {
Code* frame_code = frame->LookupCode();
DCHECK(frame_code->is_optimized_code());
int pc_offset = static_cast<int>(frame->pc() - frame_code->entry());
int handler_offset = LookupInHandlerTable(frame_code, pc_offset);
if (handler_offset < 0) continue;
// Compute the stack pointer from the frame pointer. This ensures that
// argument slots on the stack are dropped as returning would.
Address return_sp = frame->fp() -
StandardFrameConstants::kFixedFrameSizeFromFp -
frame_code->stack_slots() * kPointerSize;
// Gather information from the frame.
code = frame_code;
offset = handler_offset;
handler_sp = return_sp;
handler_fp = frame->fp();
break;
}
}
// Handler must exist.
CHECK(code != nullptr);
// Store information to be consumed by the CEntryStub.
thread_local_top()->pending_handler_context_ = context;
......
......@@ -4782,7 +4782,6 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
Operand* context_restore_operand) {
Label prologue;
Label promote_scheduled_exception;
Label exception_handled;
Label delete_allocated_handles;
Label leave_exit_frame;
Label write_back;
......@@ -4859,13 +4858,22 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ movp(Operand(base_reg, kNextOffset), prev_next_address_reg);
__ cmpp(prev_limit_reg, Operand(base_reg, kLimitOffset));
__ j(not_equal, &delete_allocated_handles);
// Leave the API exit frame.
__ bind(&leave_exit_frame);
bool restore_context = context_restore_operand != NULL;
if (restore_context) {
__ movp(rsi, *context_restore_operand);
}
if (stack_space_operand != nullptr) {
__ movp(rbx, *stack_space_operand);
}
__ LeaveApiExitFrame(!restore_context);
// Check if the function scheduled an exception.
__ Move(rsi, scheduled_exception_address);
__ Cmp(Operand(rsi, 0), factory->the_hole_value());
__ Move(rdi, scheduled_exception_address);
__ Cmp(Operand(rdi, 0), factory->the_hole_value());
__ j(not_equal, &promote_scheduled_exception);
__ bind(&exception_handled);
#if DEBUG
// Check if the function returned a valid JavaScript value.
......@@ -4902,14 +4910,6 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ bind(&ok);
#endif
bool restore_context = context_restore_operand != NULL;
if (restore_context) {
__ movp(rsi, *context_restore_operand);
}
if (stack_space_operand != nullptr) {
__ movp(rbx, *stack_space_operand);
}
__ LeaveApiExitFrame(!restore_context);
if (stack_space_operand != nullptr) {
DCHECK_EQ(stack_space, 0);
__ PopReturnAddressTo(rcx);
......@@ -4919,12 +4919,9 @@ static void CallApiFunctionAndReturn(MacroAssembler* masm,
__ ret(stack_space * kPointerSize);
}
// Re-throw by promoting a scheduled exception.
__ bind(&promote_scheduled_exception);
{
FrameScope frame(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kPromoteScheduledException, 0);
}
__ jmp(&exception_handled);
__ TailCallRuntime(Runtime::kPromoteScheduledException, 0, 1);
// HandleScope limit has changed. Delete allocated extensions.
__ bind(&delete_allocated_handles);
......
......@@ -19,6 +19,7 @@ TEST(Throw) {
TEST(ThrowSourcePosition) {
i::FLAG_turbo_exceptions = true;
static const char* src =
"(function(a, b) { \n"
" if (a == 1) throw 1; \n"
......@@ -134,9 +135,7 @@ TEST(CatchCall) {
FunctionTester T(src);
CompileRun("function thrower() { throw 'T-'; }");
#if 0 // TODO(mstarzinger): Enable once we have exception handlers.
T.CheckCall(T.Val("-A-T-"), T.NewFunction("thrower"));
#endif
CompileRun("function returner() { return 'R-'; }");
T.CheckCall(T.Val("-A-B-R-"), T.NewFunction("returner"));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment