Commit 82716f1c authored by mstarzinger's avatar mstarzinger Committed by Commit bot

[interpreter] Implement exception handler table building.

This implements a first version of exception handler table construction
within the interpreter. Note that the local control flow for try-catch
and try-finally statements is still off, and also stack unwinding does
not yet respect interpreter frames. But generated handler tables should
be populated correctly already.

R=oth@chromium.org
BUG=v8:4674
LOG=n

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

Cr-Commit-Position: refs/heads/master@{#33400}
parent 98cd565f
......@@ -1104,6 +1104,8 @@ source_set("v8_base") {
"src/interpreter/constant-array-builder.h",
"src/interpreter/control-flow-builders.cc",
"src/interpreter/control-flow-builders.h",
"src/interpreter/handler-table-builder.cc",
"src/interpreter/handler-table-builder.h",
"src/interpreter/interpreter.cc",
"src/interpreter/interpreter.h",
"src/isolate-inl.h",
......
......@@ -70,6 +70,7 @@ BytecodeArrayBuilder::BytecodeArrayBuilder(Isolate* isolate, Zone* zone)
bytecodes_(zone),
bytecode_generated_(false),
constant_array_builder_(isolate, zone),
handler_table_builder_(isolate, zone),
last_block_end_(0),
last_bytecode_start_(~0),
exit_seen_in_block_(false),
......@@ -152,9 +153,11 @@ Handle<BytecodeArray> BytecodeArrayBuilder::ToBytecodeArray() {
Factory* factory = isolate_->factory();
Handle<FixedArray> constant_pool =
constant_array_builder()->ToFixedArray(factory);
Handle<FixedArray> handler_table = handler_table_builder()->ToHandlerTable();
Handle<BytecodeArray> output =
factory->NewBytecodeArray(bytecode_size, &bytecodes_.front(), frame_size,
parameter_count(), constant_pool);
output->set_handler_table(*handler_table);
bytecode_generated_ = true;
return output;
}
......@@ -1010,6 +1013,28 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::ForInStep(Register index) {
}
BytecodeArrayBuilder& BytecodeArrayBuilder::MarkHandler(int handler_id,
bool will_catch) {
handler_table_builder()->SetHandlerTarget(handler_id, bytecodes()->size());
handler_table_builder()->SetPrediction(handler_id, will_catch);
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::MarkTryBegin(int handler_id,
Register context) {
handler_table_builder()->SetTryRegionStart(handler_id, bytecodes()->size());
handler_table_builder()->SetContextRegister(handler_id, context);
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::MarkTryEnd(int handler_id) {
handler_table_builder()->SetTryRegionEnd(handler_id, bytecodes()->size());
return *this;
}
void BytecodeArrayBuilder::LeaveBasicBlock() {
last_block_end_ = bytecodes()->size();
exit_seen_in_block_ = false;
......
......@@ -8,6 +8,7 @@
#include "src/ast/ast.h"
#include "src/interpreter/bytecodes.h"
#include "src/interpreter/constant-array-builder.h"
#include "src/interpreter/handler-table-builder.h"
#include "src/zone-containers.h"
namespace v8 {
......@@ -225,6 +226,15 @@ class BytecodeArrayBuilder final {
Register cache_type_array_pair);
BytecodeArrayBuilder& ForInStep(Register index);
// Exception handling.
BytecodeArrayBuilder& MarkHandler(int handler_id, bool will_catch);
BytecodeArrayBuilder& MarkTryBegin(int handler_id, Register context);
BytecodeArrayBuilder& MarkTryEnd(int handler_id);
// Creates a new handler table entry and returns a {hander_id} identifying the
// entry, so that it can be referenced by above exception handling support.
int NewHandlerEntry() { return handler_table_builder()->NewHandlerEntry(); }
// Accessors
Zone* zone() const { return zone_; }
......@@ -315,12 +325,16 @@ class BytecodeArrayBuilder final {
const ConstantArrayBuilder* constant_array_builder() const {
return &constant_array_builder_;
}
HandlerTableBuilder* handler_table_builder() {
return &handler_table_builder_;
}
Isolate* isolate_;
Zone* zone_;
ZoneVector<uint8_t> bytecodes_;
bool bytecode_generated_;
ConstantArrayBuilder constant_array_builder_;
HandlerTableBuilder handler_table_builder_;
size_t last_block_end_;
size_t last_bytecode_start_;
bool exit_seen_in_block_;
......
......@@ -899,21 +899,65 @@ void BytecodeGenerator::VisitForOfStatement(ForOfStatement* stmt) {
void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
if (FLAG_ignition_fake_try_catch) {
Visit(stmt->try_block());
return;
}
UNIMPLEMENTED();
TryCatchBuilder try_control_builder(builder());
if (!FLAG_ignition_fake_try_catch) UNIMPLEMENTED();
// Preserve the context in a dedicated register, so that it can be restored
// when the handler is entered by the stack-unwinding machinery.
// TODO(mstarzinger): Be smarter about register allocation.
Register context = register_allocator()->NewRegister();
// Evaluate the try-block inside a control scope. This simulates a handler
// that is intercepting 'throw' control commands.
try_control_builder.BeginTry(context);
// TODO(mstarzinger): Control scope is missing!
Visit(stmt->try_block());
try_control_builder.EndTry();
// Clear message object as we enter the catch block.
// TODO(mstarzinger): Implement this!
// Create a catch scope that binds the exception.
register_allocator()->PrepareForConsecutiveAllocations(3);
Register name = register_allocator()->NextConsecutiveRegister();
Register exception = register_allocator()->NextConsecutiveRegister();
Register closure = register_allocator()->NextConsecutiveRegister();
builder()
->StoreAccumulatorInRegister(exception)
.LoadLiteral(stmt->variable()->name())
.StoreAccumulatorInRegister(name);
VisitFunctionClosureForContext();
builder()->StoreAccumulatorInRegister(closure).CallRuntime(
Runtime::kPushCatchContext, name, 3);
// Evaluate the catch-block.
Visit(stmt->catch_block());
try_control_builder.EndCatch();
}
void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
if (FLAG_ignition_fake_try_catch) {
Visit(stmt->try_block());
Visit(stmt->finally_block());
return;
}
UNIMPLEMENTED();
TryFinallyBuilder try_control_builder(builder());
if (!FLAG_ignition_fake_try_catch) UNIMPLEMENTED();
// Preserve the context in a dedicated register, so that it can be restored
// when the handler is entered by the stack-unwinding machinery.
// TODO(mstarzinger): Be smarter about register allocation.
Register context = register_allocator()->NewRegister();
// Evaluate the try-block inside a control scope. This simulates a handler
// that is intercepting all control commands.
try_control_builder.BeginTry(context);
// TODO(mstarzinger): Control scope is missing!
Visit(stmt->try_block());
try_control_builder.EndTry();
// Clear message object as we enter the finally block.
// TODO(mstarzinger): Implement this!
// Evaluate the finally-block.
Visit(stmt->finally_block());
try_control_builder.EndFinally();
}
......
......@@ -137,6 +137,39 @@ void SwitchBuilder::SetCaseTarget(int index) {
builder()->Bind(&site);
}
void TryCatchBuilder::BeginTry(Register context) {
builder()->MarkTryBegin(handler_id_, context);
}
void TryCatchBuilder::EndTry() {
builder()->MarkTryEnd(handler_id_);
builder()->Jump(&exit_);
builder()->Bind(&handler_);
builder()->MarkHandler(handler_id_, true);
}
void TryCatchBuilder::EndCatch() { builder()->Bind(&exit_); }
void TryFinallyBuilder::BeginTry(Register context) {
builder()->MarkTryBegin(handler_id_, context);
}
void TryFinallyBuilder::EndTry() {
builder()->MarkTryEnd(handler_id_);
builder()->Bind(&handler_);
builder()->MarkHandler(handler_id_, false);
}
void TryFinallyBuilder::EndFinally() {
// Nothing to be done here.
}
} // namespace interpreter
} // namespace internal
} // namespace v8
......@@ -144,6 +144,39 @@ class SwitchBuilder final : public BreakableControlFlowBuilder {
ZoneVector<BytecodeLabel> case_sites_;
};
// A class to help with co-ordinating control flow in try-catch statements.
class TryCatchBuilder final : public ControlFlowBuilder {
public:
explicit TryCatchBuilder(BytecodeArrayBuilder* builder)
: ControlFlowBuilder(builder), handler_id_(builder->NewHandlerEntry()) {}
void BeginTry(Register context);
void EndTry();
void EndCatch();
private:
int handler_id_;
BytecodeLabel handler_;
BytecodeLabel exit_;
};
// A class to help with co-ordinating control flow in try-finally statements.
class TryFinallyBuilder final : public ControlFlowBuilder {
public:
explicit TryFinallyBuilder(BytecodeArrayBuilder* builder)
: ControlFlowBuilder(builder), handler_id_(builder->NewHandlerEntry()) {}
void BeginTry(Register context);
void EndTry();
void EndFinally();
private:
int handler_id_;
BytecodeLabel handler_;
};
} // namespace interpreter
} // namespace internal
} // namespace v8
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/interpreter/handler-table-builder.h"
#include "src/factory.h"
#include "src/isolate.h"
#include "src/objects-inl.h"
namespace v8 {
namespace internal {
namespace interpreter {
HandlerTableBuilder::HandlerTableBuilder(Isolate* isolate, Zone* zone)
: isolate_(isolate), entries_(zone) {}
Handle<HandlerTable> HandlerTableBuilder::ToHandlerTable() {
int handler_table_size = static_cast<int>(entries_.size());
Handle<HandlerTable> table =
Handle<HandlerTable>::cast(isolate_->factory()->NewFixedArray(
HandlerTable::LengthForRange(handler_table_size), TENURED));
for (int i = 0; i < handler_table_size; ++i) {
Entry& entry = entries_[i];
HandlerTable::CatchPrediction pred =
entry.will_catch ? HandlerTable::CAUGHT : HandlerTable::UNCAUGHT;
table->SetRangeStart(i, static_cast<int>(entry.offset_start));
table->SetRangeEnd(i, static_cast<int>(entry.offset_end));
table->SetRangeHandler(i, static_cast<int>(entry.offset_target), pred);
table->SetRangeDepth(i, entry.context.index());
}
return table;
}
int HandlerTableBuilder::NewHandlerEntry() {
int handler_id = static_cast<int>(entries_.size());
Entry entry = {0, 0, 0, Register(), false};
entries_.push_back(entry);
return handler_id;
}
void HandlerTableBuilder::SetTryRegionStart(int handler_id, size_t offset) {
DCHECK(Smi::IsValid(offset)); // Encoding of handler table requires this.
entries_[handler_id].offset_start = offset;
}
void HandlerTableBuilder::SetTryRegionEnd(int handler_id, size_t offset) {
DCHECK(Smi::IsValid(offset)); // Encoding of handler table requires this.
entries_[handler_id].offset_end = offset;
}
void HandlerTableBuilder::SetHandlerTarget(int handler_id, size_t offset) {
DCHECK(Smi::IsValid(offset)); // Encoding of handler table requires this.
entries_[handler_id].offset_target = offset;
}
void HandlerTableBuilder::SetPrediction(int handler_id, bool will_catch) {
entries_[handler_id].will_catch = will_catch;
}
void HandlerTableBuilder::SetContextRegister(int handler_id, Register reg) {
entries_[handler_id].context = reg;
}
} // namespace interpreter
} // namespace internal
} // namespace v8
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_INTERPRETER_HANDLER_TABLE_BUILDER_H_
#define V8_INTERPRETER_HANDLER_TABLE_BUILDER_H_
#include "src/handles.h"
#include "src/interpreter/bytecodes.h"
#include "src/zone-containers.h"
namespace v8 {
namespace internal {
class HandlerTable;
class Isolate;
namespace interpreter {
// A helper class for constructing exception handler tables for the interpreter.
class HandlerTableBuilder final BASE_EMBEDDED {
public:
HandlerTableBuilder(Isolate* isolate, Zone* zone);
// Builds the actual handler table by copying the current values into a heap
// object. Any further mutations to the builder won't be reflected.
Handle<HandlerTable> ToHandlerTable();
// Creates a new handler table entry and returns a {hander_id} identifying the
// entry, so that it can be referenced by below setter functions.
int NewHandlerEntry();
// Setter functions that modify certain values within the handler table entry
// being referenced by the given {handler_id}. All values will be encoded by
// the resulting {HandlerTable} class when copied into the heap.
void SetTryRegionStart(int handler_id, size_t offset);
void SetTryRegionEnd(int handler_id, size_t offset);
void SetHandlerTarget(int handler_id, size_t offset);
void SetPrediction(int handler_id, bool will_catch);
void SetContextRegister(int handler_id, Register reg);
private:
struct Entry {
size_t offset_start; // Bytecode offset starting try-region.
size_t offset_end; // Bytecode offset ending try-region.
size_t offset_target; // Bytecode offset of handler target.
Register context; // Register holding context for handler.
bool will_catch; // Optimistic prediction for handler.
};
Isolate* isolate_;
ZoneVector<Entry> entries_;
DISALLOW_COPY_AND_ASSIGN(HandlerTableBuilder);
};
} // namespace interpreter
} // namespace internal
} // namespace v8
#endif // V8_INTERPRETER_HANDLER_TABLE_BUILDER_H_
......@@ -15012,8 +15012,17 @@ void BytecodeArray::Disassemble(std::ostream& os) {
os << "\n";
}
os << "Constant pool (size = " << constant_pool()->length() << ")\n";
constant_pool()->Print();
if (constant_pool()->length() > 0) {
os << "Constant pool (size = " << constant_pool()->length() << ")\n";
constant_pool()->Print();
}
#ifdef ENABLE_DISASSEMBLER
if (handler_table()->length() > 0) {
os << "Handler Table (size = " << handler_table()->Size() << ")\n";
HandlerTable::cast(handler_table())->HandlerTableRangePrint(os);
}
#endif
}
......
......@@ -4696,7 +4696,7 @@ class DeoptimizationOutputData: public FixedArray {
DECLARE_CAST(DeoptimizationOutputData)
#if defined(OBJECT_PRINT) || defined(ENABLE_DISASSEMBLER)
#ifdef ENABLE_DISASSEMBLER
void DeoptimizationOutputDataPrint(std::ostream& os); // NOLINT
#endif
};
......@@ -4774,7 +4774,7 @@ class HandlerTable : public FixedArray {
DECLARE_CAST(HandlerTable)
#if defined(OBJECT_PRINT) || defined(ENABLE_DISASSEMBLER)
#ifdef ENABLE_DISASSEMBLER
void HandlerTableRangePrint(std::ostream& os); // NOLINT
void HandlerTableReturnPrint(std::ostream& os); // NOLINT
#endif
......
......@@ -161,6 +161,12 @@ struct ExpectedSnippet {
const uint8_t bytecode[2048];
int constant_count;
T constants[C];
int handler_count;
struct {
int start;
int end;
int handler;
} handlers[C];
};
......@@ -205,6 +211,24 @@ static void CheckBytecodeArrayEqual(const ExpectedSnippet<T, C>& expected,
CheckConstant(expected.constants[i], actual->constant_pool()->get(i));
}
}
if (expected.handler_count == 0) {
CHECK_EQ(CcTest::heap()->empty_fixed_array(), actual->handler_table());
} else {
static const int kHTSize = 4; // see HandlerTable::kRangeEntrySize
static const int kHTStart = 0; // see HandlerTable::kRangeStartIndex
static const int kHTEnd = 1; // see HandlerTable::kRangeEndIndex
static const int kHTHandler = 2; // see HandlerTable::kRangeHandlerIndex
HandlerTable* table = HandlerTable::cast(actual->handler_table());
CHECK_EQ(expected.handler_count * kHTSize, table->length());
for (int i = 0; i < expected.handler_count; i++) {
int start = Smi::cast(table->get(i * kHTSize + kHTStart))->value();
int end = Smi::cast(table->get(i * kHTSize + kHTEnd))->value();
int handler = Smi::cast(table->get(i * kHTSize + kHTHandler))->value();
CHECK_EQ(expected.handlers[i].start, start);
CHECK_EQ(expected.handlers[i].end, end);
CHECK_EQ(expected.handlers[i].handler, handler >> 1);
}
}
BytecodeArrayIterator iterator(actual);
int i = 0;
......@@ -4143,17 +4167,64 @@ TEST(TryCatch) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
// TODO(rmcilroy): modify tests when we have real try catch support.
ExpectedSnippet<int> snippets[] = {
int closure = Register::function_closure().index();
ExpectedSnippet<const char*> snippets[] = {
{"try { return 1; } catch(e) { return 2; }",
kPointerSize,
5 * kPointerSize,
1,
3,
23,
{
B(LdaSmi8), U8(1), //
B(Return), //
B(LdaSmi8), U8(1), //
B(Return), //
B(Star), R(3), //
B(LdaConstant), U8(0), //
B(Star), R(2), //
B(Ldar), R(closure), //
B(Star), R(4), //
B(CallRuntime), U16(Runtime::kPushCatchContext), R(2), U8(3), //
B(LdaSmi8), U8(2), //
B(Return), //
// TODO(mstarzinger): Potential optimization, elide next bytes.
B(LdaUndefined), //
B(Return), //
},
0},
1,
{"e"},
1,
{{0, 3, 3}}},
{"var a; try { a = 1 } catch(e1) {}; try { a = 2 } catch(e2) { a = 3 }",
6 * kPointerSize,
1,
48,
{
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(Jump), U8(17), //
B(Star), R(4), //
B(LdaConstant), U8(0), //
B(Star), R(3), //
B(Ldar), R(closure), //
B(Star), R(5), //
B(CallRuntime), U16(Runtime::kPushCatchContext), R(3), U8(3), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(Jump), U8(21), //
B(Star), R(4), //
B(LdaConstant), U8(1), //
B(Star), R(3), //
B(Ldar), R(closure), //
B(Star), R(5), //
B(CallRuntime), U16(Runtime::kPushCatchContext), R(3), U8(3), //
B(LdaSmi8), U8(3), //
B(Star), R(0), //
B(LdaUndefined), //
B(Return), //
},
2,
{"e1", "e2"},
2,
{{0, 4, 6}, {21, 25, 27}}},
};
for (size_t i = 0; i < arraysize(snippets); i++) {
......@@ -4168,10 +4239,11 @@ TEST(TryFinally) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
// TODO(rmcilroy): modify tests when we have real try finally support.
ExpectedSnippet<int> snippets[] = {
int closure = Register::function_closure().index();
ExpectedSnippet<const char*> snippets[] = {
{"var a = 1; try { a = 2; } finally { a = 3; }",
kPointerSize,
2 * kPointerSize,
1,
14,
{
......@@ -4184,22 +4256,73 @@ TEST(TryFinally) {
B(LdaUndefined), //
B(Return), //
},
0},
0,
{},
1,
{{4, 8, 8}}},
{"var a = 1; try { a = 2; } catch(e) { a = 20 } finally { a = 3; }",
2 * kPointerSize,
7 * kPointerSize,
1,
14,
35,
{
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(LdaSmi8), U8(3), //
B(Star), R(0), //
B(LdaUndefined), //
B(Return), //
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(Jump), U8(21), //
B(Star), R(5), //
B(LdaConstant), U8(0), //
B(Star), R(4), //
B(Ldar), R(closure), //
B(Star), R(6), //
B(CallRuntime), U16(Runtime::kPushCatchContext), R(4), U8(3), //
B(LdaSmi8), U8(20), //
B(Star), R(0), //
B(LdaSmi8), U8(3), //
B(Star), R(0), //
B(LdaUndefined), //
B(Return), //
},
0},
1,
{"e"},
2,
{{4, 29, 29}, {4, 8, 10}}},
{"var a; try {"
" try { a = 1 } catch(e) { a = 2 }"
"} catch(e) { a = 20 } finally { a = 3; }",
8 * kPointerSize,
1,
52,
{
B(LdaSmi8), U8(1), //
B(Star), R(0), //
B(Jump), U8(21), //
B(Star), R(6), //
B(LdaConstant), U8(0), //
B(Star), R(5), //
B(Ldar), R(closure), //
B(Star), R(7), //
B(CallRuntime), U16(Runtime::kPushCatchContext), R(5), U8(3), //
B(LdaSmi8), U8(2), //
B(Star), R(0), //
B(Jump), U8(21), //
B(Star), R(5), //
B(LdaConstant), U8(0), //
B(Star), R(4), //
B(Ldar), R(closure), //
B(Star), R(6), //
B(CallRuntime), U16(Runtime::kPushCatchContext), R(4), U8(3), //
B(LdaSmi8), U8(20), //
B(Star), R(0), //
B(LdaSmi8), U8(3), //
B(Star), R(0), //
B(LdaUndefined), //
B(Return), //
},
1,
{"e"},
3,
{{0, 46, 46}, {0, 25, 27}, {0, 4, 6}}},
};
for (size_t i = 0; i < arraysize(snippets); i++) {
......@@ -4214,7 +4337,6 @@ TEST(Throw) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
// TODO(rmcilroy): modify tests when we have real try catch support.
ExpectedSnippet<const char*> snippets[] = {
{"throw 1;",
0,
......
......@@ -872,6 +872,8 @@
'../../src/interpreter/constant-array-builder.h',
'../../src/interpreter/control-flow-builders.cc',
'../../src/interpreter/control-flow-builders.h',
'../../src/interpreter/handler-table-builder.cc',
'../../src/interpreter/handler-table-builder.h',
'../../src/interpreter/interpreter.cc',
'../../src/interpreter/interpreter.h',
'../../src/isolate-inl.h',
......
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