Commit 76bfc16b authored by mstarzinger's avatar mstarzinger Committed by Commit bot

[interpreter] Switch context during stack unwinding.

This implements proper context switching while unwinding the stack due
to an exception being handled in interpreted code. The context under
which the handler is scoped is being preserved in a dedicated register
while the try-block is running. Both, the stack unwinding machinery as
well as the graph builder, restore the context from that register.

R=rmcilroy@chromium.org,bmeurer@chromium.org
BUG=v8:4674
LOG=n

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

Cr-Commit-Position: refs/heads/master@{#33733}
parent a318cb2f
......@@ -1663,7 +1663,11 @@ void BytecodeGraphBuilder::EnterAndExitExceptionHandlers(int current_offset) {
if (current_offset < next_start) break; // Not yet covered by range.
int next_end = table->GetRangeEnd(current_exception_handler_);
int next_handler = table->GetRangeHandler(current_exception_handler_);
exception_handlers_.push({next_start, next_end, next_handler});
// TODO(mstarzinger): We are hijacking the "depth" field in the exception
// handler table to hold the context register. We should rename the field.
int context_register = table->GetRangeDepth(current_exception_handler_);
exception_handlers_.push(
{next_start, next_end, next_handler, context_register});
current_exception_handler_++;
}
}
......@@ -1720,15 +1724,19 @@ Node* BytecodeGraphBuilder::MakeNode(const Operator* op, int value_input_count,
// Add implicit exception continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow) && inside_handler) {
int handler_offset = exception_handlers_.top().handler_offset_;
int context_index = exception_handlers_.top().context_register_;
interpreter::Register context_register(context_index);
// TODO(mstarzinger): Thread through correct prediction!
IfExceptionHint hint = IfExceptionHint::kLocallyCaught;
Environment* success_env = environment()->CopyForConditional();
const Operator* op = common()->IfException(hint);
Node* effect = environment()->GetEffectDependency();
Node* on_exception = graph()->NewNode(op, effect, result);
Node* context = environment()->LookupRegister(context_register);
environment()->UpdateControlDependency(on_exception);
environment()->UpdateEffectDependency(on_exception);
environment()->BindAccumulator(on_exception);
environment()->SetContext(context);
MergeIntoSuccessorEnvironment(handler_offset);
set_environment(success_env);
}
......
......@@ -169,9 +169,10 @@ class BytecodeGraphBuilder {
// underlying bytecode. The exception handlers within the bytecode are
// well scoped, hence will form a stack during iteration.
struct ExceptionHandler {
int start_offset_; // Start offset of the handled area in the bytecode.
int end_offset_; // End offset of the handled area in the bytecode.
int handler_offset_; // Handler entry offset within the bytecode.
int start_offset_; // Start offset of the handled area in the bytecode.
int end_offset_; // End offset of the handled area in the bytecode.
int handler_offset_; // Handler entry offset within the bytecode.
int context_register_; // Index of register holding handler context.
};
// Field accessors
......
......@@ -1163,6 +1163,13 @@ void InterpretedFrame::PatchBytecodeOffset(int new_offset) {
SetExpression(index, Smi::FromInt(raw_offset));
}
Object* InterpretedFrame::GetInterpreterRegister(int register_index) const {
const int index = InterpreterFrameConstants::kRegisterFileExpressionIndex;
DCHECK_EQ(InterpreterFrameConstants::kRegisterFilePointerFromFp,
StandardFrameConstants::kExpressionsOffset - index * kPointerSize);
return GetExpression(index + register_index);
}
void InterpretedFrame::Summarize(List<FrameSummary>* functions) {
DCHECK(functions->length() == 0);
AbstractCode* abstract_code =
......
......@@ -190,6 +190,7 @@ class InterpreterFrameConstants : public AllStatic {
// Expression index for {StandardFrame::GetExpressionAddress}.
static const int kBytecodeOffsetExpressionIndex = 1;
static const int kRegisterFileExpressionIndex = 2;
// Register file pointer relative.
static const int kLastParamFromRegisterPointer =
......@@ -732,6 +733,9 @@ class InterpretedFrame : public JavaScriptFrame {
// unwinding to continue execution at a different bytecode offset.
void PatchBytecodeOffset(int new_offset);
// Access to the interpreter register file for this frame.
Object* GetInterpreterRegister(int register_index) const;
// Build a list with summaries for this frame including all inlined frames.
void Summarize(List<FrameSummary>* frames) override;
......
......@@ -1119,6 +1119,7 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
// when the handler is entered by the stack-unwinding machinery.
// TODO(mstarzinger): Be smarter about register allocation.
Register context = register_allocator()->NewRegister();
builder()->MoveRegister(Register::current_context(), context);
// Evaluate the try-block inside a control scope. This simulates a handler
// that is intercepting 'throw' control commands.
......@@ -1172,6 +1173,7 @@ void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
// when the handler is entered by the stack-unwinding machinery.
// TODO(mstarzinger): Be smarter about register allocation.
Register context = register_allocator()->NewRegister();
builder()->MoveRegister(Register::current_context(), context);
// Evaluate the try-block inside a control scope. This simulates a handler
// that is intercepting all control commands.
......
......@@ -1102,7 +1102,7 @@ Object* Isolate::UnwindAndFindHandler() {
if (frame->is_optimized() && catchable_by_js) {
OptimizedFrame* js_frame = static_cast<OptimizedFrame*>(frame);
int stack_slots = 0; // Will contain stack slot count of frame.
offset = js_frame->LookupExceptionHandlerInTable(&stack_slots, NULL);
offset = js_frame->LookupExceptionHandlerInTable(&stack_slots, nullptr);
if (offset >= 0) {
// Compute the stack pointer from the frame pointer. This ensures that
// argument slots on the stack are dropped as returning would.
......@@ -1121,12 +1121,14 @@ Object* Isolate::UnwindAndFindHandler() {
// For interpreted frame we perform a range lookup in the handler table.
if (frame->is_interpreted() && catchable_by_js) {
InterpretedFrame* js_frame = static_cast<InterpretedFrame*>(frame);
int stack_slots = 0; // Will contain stack slot count of frame.
offset = js_frame->LookupExceptionHandlerInTable(&stack_slots, NULL);
int context_reg = 0; // Will contain register index holding context.
offset = js_frame->LookupExceptionHandlerInTable(&context_reg, nullptr);
if (offset >= 0) {
// Patch the bytecode offset in the interpreted frame to reflect the
// position of the exception handler. The special builtin below will
// take care of continuing to dispatch at that position.
// take care of continuing to dispatch at that position. Also restore
// the correct context for the handler from the interpreter register.
context = Context::cast(js_frame->GetInterpreterRegister(context_reg));
js_frame->PatchBytecodeOffset(static_cast<int>(offset));
offset = 0;
......@@ -1141,15 +1143,15 @@ Object* Isolate::UnwindAndFindHandler() {
// For JavaScript frames we perform a range lookup in the handler table.
if (frame->is_java_script() && catchable_by_js) {
JavaScriptFrame* js_frame = static_cast<JavaScriptFrame*>(frame);
int stack_slots = 0; // Will contain operand stack depth of handler.
offset = js_frame->LookupExceptionHandlerInTable(&stack_slots, NULL);
int stack_depth = 0; // Will contain operand stack depth of handler.
offset = js_frame->LookupExceptionHandlerInTable(&stack_depth, nullptr);
if (offset >= 0) {
// Compute the stack pointer from the frame pointer. This ensures that
// operand stack slots are dropped for nested statements. Also restore
// correct context for the handler which is pushed within the try-block.
Address return_sp = frame->fp() -
StandardFrameConstants::kFixedFrameSizeFromFp -
stack_slots * kPointerSize;
stack_depth * kPointerSize;
STATIC_ASSERT(TryBlockConstant::kElementCount == 1);
context = Context::cast(Memory::Object_at(return_sp - kPointerSize));
......
......@@ -3415,6 +3415,10 @@ int HandlerTable::GetRangeHandler(int index) const {
Smi::cast(get(index * kRangeEntrySize + kRangeHandlerIndex))->value());
}
int HandlerTable::GetRangeDepth(int index) const {
return Smi::cast(get(index * kRangeEntrySize + kRangeDepthIndex))->value();
}
void HandlerTable::SetRangeStart(int index, int value) {
set(index * kRangeEntrySize + kRangeStartIndex, Smi::FromInt(value));
}
......
......@@ -4764,6 +4764,7 @@ class HandlerTable : public FixedArray {
inline int GetRangeStart(int index) const;
inline int GetRangeEnd(int index) const;
inline int GetRangeHandler(int index) const;
inline int GetRangeDepth(int index) const;
// Setters for handler table based on ranges.
inline void SetRangeStart(int index, int value);
......
......@@ -4627,15 +4627,17 @@ TEST(TryCatch) {
BytecodeGeneratorHelper helper;
int closure = Register::function_closure().index();
int context = Register::current_context().index();
// clang-format off
ExpectedSnippet<const char*> snippets[] = {
{"try { return 1; } catch(e) { return 2; }",
5 * kPointerSize,
1,
37,
40,
{
B(StackCheck), //
B(Mov), R(context), R(1), //
B(LdaSmi8), U8(1), //
B(Return), //
B(Star), R(3), //
......@@ -4659,13 +4661,14 @@ TEST(TryCatch) {
1,
{"e"},
1,
{{1, 4, 4}}},
{{4, 7, 7}}},
{"var a; try { a = 1 } catch(e1) {}; try { a = 2 } catch(e2) { a = 3 }",
6 * kPointerSize,
1,
75,
81,
{
B(StackCheck), //
B(Mov), R(context), R(2), //
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(Jump), U8(30), //
......@@ -4681,6 +4684,7 @@ TEST(TryCatch) {
B(Ldar), R(2), //
B(PushContext), R(1), //
B(PopContext), R(1), //
B(Mov), R(context), R(2), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(Jump), U8(34), //
......@@ -4704,7 +4708,7 @@ TEST(TryCatch) {
2,
{"e1", "e2"},
2,
{{1, 5, 7}, {35, 39, 41}}},
{{4, 8, 10}, {41, 45, 47}}},
};
// clang-format on
......@@ -4721,17 +4725,19 @@ TEST(TryFinally) {
BytecodeGeneratorHelper helper;
int closure = Register::function_closure().index();
int context = Register::current_context().index();
// clang-format off
ExpectedSnippet<const char*> snippets[] = {
{"var a = 1; try { a = 2; } finally { a = 3; }",
4 * kPointerSize,
1,
48,
51,
{
B(StackCheck), //
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(Mov), R(context), R(3), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(LdaSmi8), U8(-1), //
......@@ -4759,15 +4765,17 @@ TEST(TryFinally) {
0,
{},
1,
{{5, 9, 15}}},
{{8, 12, 18}}},
{"var a = 1; try { a = 2; } catch(e) { a = 20 } finally { a = 3; }",
9 * kPointerSize,
1,
82,
88,
{
B(StackCheck), //
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(Mov), R(context), R(4), //
B(Mov), R(context), R(5), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(Jump), U8(34), //
......@@ -4810,15 +4818,18 @@ TEST(TryFinally) {
1,
{"e"},
2,
{{5, 43, 49}, {5, 9, 11}}},
{{8, 49, 55}, {11, 15, 17}}},
{"var a; try {"
" try { a = 1 } catch(e) { a = 2 }"
"} catch(e) { a = 20 } finally { a = 3; }",
10 * kPointerSize,
1,
112,
121,
{
B(StackCheck), //
B(StackCheck), //
B(Mov), R(context), R(4), //
B(Mov), R(context), R(5), //
B(Mov), R(context), R(6), //
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(Jump), U8(34), //
......@@ -4876,7 +4887,7 @@ TEST(TryFinally) {
1,
{"e"},
3,
{{1, 73, 79}, {1, 39, 41}, {1, 5, 7}}},
{{4, 82, 88}, {7, 48, 50}, {10, 14, 16}}},
};
// clang-format on
......
......@@ -756,9 +756,6 @@
'regress/debug*': [SKIP],
'regress/regress-debug*': [SKIP],
# TODO(4674): This might be failing because of missing context switch.
'with-leave': [FAIL],
# TODO(4690): Failing in debugger DebuggerInspectableFrame.
'for-in-opt': [FAIL],
......
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