Commit 8bfa1ea3 authored by mstarzinger's avatar mstarzinger Committed by Commit bot

[interpreter] Translate exception handlers into graph.

This translates the exception handler table attached to a bytecode array
correctly into exceptional projections within the TurboFan graph. We
perform an abstract simulation of handlers that are being entered and
exited by the bytecode iteration to track the correct handler for each
node.

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

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

Cr-Commit-Position: refs/heads/master@{#33580}
parent 55438d60
......@@ -55,6 +55,7 @@ BytecodeBranchAnalysis::BytecodeBranchAnalysis(
void BytecodeBranchAnalysis::Analyze() {
interpreter::BytecodeArrayIterator iterator(bytecode_array());
AnalyzeExceptionHandlers();
bool reachable = true;
while (!iterator.done()) {
interpreter::Bytecode bytecode = iterator.current_bytecode();
......@@ -63,6 +64,10 @@ void BytecodeBranchAnalysis::Analyze() {
// and may also be backward reachable. Hence if there's a forward
// branch targetting here the code becomes reachable.
reachable = reachable || forward_branches_target(current_offset);
// Some bytecode basic blocks are reachable through a side-entry
// (e.g. exception handler), which has been represented in the
// bit-vector by a corresponding pre-pass.
reachable = reachable || reachable_.Contains(current_offset);
if (reachable) {
reachable_.Add(current_offset);
if (interpreter::Bytecodes::IsConditionalJump(bytecode)) {
......@@ -106,6 +111,12 @@ const ZoneVector<int>* BytecodeBranchAnalysis::ForwardBranchesTargetting(
}
}
void BytecodeBranchAnalysis::AddExceptionalBranch(int throw_offset,
int handler_offset) {
DCHECK(is_reachable(handler_offset)); // Handler was marked reachable.
DCHECK_LT(throw_offset, handler_offset); // Always a forward branch.
AddBranch(throw_offset, handler_offset);
}
void BytecodeBranchAnalysis::AddBranch(int source_offset, int target_offset) {
BytecodeBranchInfo* branch_info = nullptr;
......@@ -119,6 +130,12 @@ void BytecodeBranchAnalysis::AddBranch(int source_offset, int target_offset) {
branch_info->AddBranch(source_offset, target_offset);
}
void BytecodeBranchAnalysis::AnalyzeExceptionHandlers() {
HandlerTable* table = HandlerTable::cast(bytecode_array()->handler_table());
for (int i = 0; i < table->NumberOfRangeEntries(); ++i) {
reachable_.Add(table->GetRangeHandler(i));
}
}
} // namespace compiler
} // namespace internal
......
......@@ -57,8 +57,14 @@ class BytecodeBranchAnalysis BASE_EMBEDDED {
return sites != nullptr && sites->size() > 0;
}
// Adds an additional implicit branch from a throw-site at {throw_offset} to
// the corresponding exception handler at {handler_offset}. Note that such a
// branch must be a forward branch and has to target a known handler.
void AddExceptionalBranch(int throw_offset, int handler_offset);
private:
void AddBranch(int origin_offset, int target_offset);
void AnalyzeExceptionHandlers();
Zone* zone() const { return zone_; }
Handle<BytecodeArray> bytecode_array() const { return bytecode_array_; }
......
......@@ -376,7 +376,6 @@ bool BytecodeGraphBuilder::Environment::StateValuesAreUpToDate(
1, output_poke_start, output_poke_end);
}
BytecodeGraphBuilder::BytecodeGraphBuilder(Zone* local_zone,
CompilationInfo* compilation_info,
JSGraph* jsgraph)
......@@ -384,6 +383,8 @@ BytecodeGraphBuilder::BytecodeGraphBuilder(Zone* local_zone,
info_(compilation_info),
jsgraph_(jsgraph),
bytecode_array_(handle(info()->shared_info()->bytecode_array())),
exception_handler_table_(
handle(HandlerTable::cast(bytecode_array()->handler_table()))),
frame_state_function_info_(common()->CreateFrameStateFunctionInfo(
FrameStateType::kInterpretedFunction,
bytecode_array()->parameter_count(),
......@@ -391,11 +392,12 @@ BytecodeGraphBuilder::BytecodeGraphBuilder(Zone* local_zone,
CALL_MAINTAINS_NATIVE_CONTEXT)),
merge_environments_(local_zone),
loop_header_environments_(local_zone),
exception_handlers_(local_zone),
current_exception_handler_(0),
input_buffer_size_(0),
input_buffer_(nullptr),
exit_controls_(local_zone) {}
Node* BytecodeGraphBuilder::GetNewTarget() {
if (!new_target_.is_set()) {
int params = bytecode_array()->parameter_count();
......@@ -528,6 +530,7 @@ void BytecodeGraphBuilder::VisitBytecodes() {
while (!iterator.done()) {
int current_offset = iterator.current_offset();
if (analysis.is_reachable(current_offset)) {
EnterAndExitExceptionHandlers(current_offset);
MergeEnvironmentsOfForwardBranches(current_offset);
BuildLoopHeaderForBackwardBranches(current_offset);
......@@ -544,6 +547,7 @@ void BytecodeGraphBuilder::VisitBytecodes() {
}
set_branch_analysis(nullptr);
set_bytecode_iterator(nullptr);
DCHECK(exception_handlers_.empty());
}
......@@ -1950,6 +1954,27 @@ Node** BytecodeGraphBuilder::EnsureInputBufferSize(int size) {
return input_buffer_;
}
void BytecodeGraphBuilder::EnterAndExitExceptionHandlers(int current_offset) {
Handle<HandlerTable> table = exception_handler_table();
int num_entries = table->NumberOfRangeEntries();
// Potentially exit exception handlers.
while (!exception_handlers_.empty()) {
int current_end = exception_handlers_.top().end_offset_;
if (current_offset < current_end) break; // Still covered by range.
exception_handlers_.pop();
}
// Potentially enter exception handlers.
while (current_exception_handler_ < num_entries) {
int next_start = table->GetRangeStart(current_exception_handler_);
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});
current_exception_handler_++;
}
}
void BytecodeGraphBuilder::PrepareEntryFrameState(Node* node) {
DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op()));
......@@ -1977,6 +2002,7 @@ Node* BytecodeGraphBuilder::MakeNode(const Operator* op, int value_input_count,
if (!has_context && frame_state_count == 0 && !has_control && !has_effect) {
result = graph()->NewNode(op, value_input_count, value_inputs, incomplete);
} else {
bool inside_handler = !exception_handlers_.empty();
int input_count_with_deps = value_input_count;
if (has_context) ++input_count_with_deps;
input_count_with_deps += frame_state_count;
......@@ -2010,11 +2036,32 @@ Node* BytecodeGraphBuilder::MakeNode(const Operator* op, int value_input_count,
if (result->op()->EffectOutputCount() > 0) {
environment()->UpdateEffectDependency(result);
}
// Add implicit exception continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow) && inside_handler) {
int throw_offset = bytecode_iterator()->current_offset();
int handler_offset = exception_handlers_.top().handler_offset_;
// TODO(mstarzinger): Thread through correct prediction!
IfExceptionHint hint = IfExceptionHint::kLocallyCaught;
// TODO(mstarzinger): For now we mutate the branch analysis result and
// add the artificial control flow from throw-site to handler-entry.
// This can be simplified by pushing environment forward along the
// direction of the data-flow.
branch_analysis_->AddExceptionalBranch(throw_offset, handler_offset);
Environment* success_env = environment()->CopyForConditional();
const Operator* op = common()->IfException(hint);
Node* effect = environment()->GetEffectDependency();
Node* on_exception = graph()->NewNode(op, effect, result);
environment()->UpdateControlDependency(on_exception);
environment()->UpdateEffectDependency(on_exception);
environment()->BindAccumulator(on_exception);
BuildJump(throw_offset, handler_offset);
set_environment(success_env);
}
// Add implicit success continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow)) {
const Operator* if_success = common()->IfSuccess();
Node* on_success = graph()->NewNode(if_success, result);
environment_->UpdateControlDependency(on_success);
environment()->UpdateControlDependency(on_success);
}
}
}
......
......@@ -175,6 +175,9 @@ class BytecodeGraphBuilder {
void MergeEnvironmentsOfForwardBranches(int source_offset);
void BuildLoopHeaderForBackwardBranches(int source_offset);
// Simulates entry and exit of exception handlers.
void EnterAndExitExceptionHandlers(int current_offset);
// Attaches a frame state to |node| for the entry to the function.
void PrepareEntryFrameState(Node* node);
......@@ -182,6 +185,16 @@ class BytecodeGraphBuilder {
// new nodes.
static const int kInputBufferSizeIncrement = 64;
// An abstract representation for an exception handler that is being
// entered and exited while the graph builder is iterating over the
// 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.
};
// Field accessors
CommonOperatorBuilder* common() const { return jsgraph_->common(); }
Zone* graph_zone() const { return graph()->zone(); }
......@@ -192,6 +205,9 @@ class BytecodeGraphBuilder {
const Handle<BytecodeArray>& bytecode_array() const {
return bytecode_array_;
}
const Handle<HandlerTable>& exception_handler_table() const {
return exception_handler_table_;
}
const FrameStateFunctionInfo* frame_state_function_info() const {
return frame_state_function_info_;
}
......@@ -214,7 +230,7 @@ class BytecodeGraphBuilder {
return branch_analysis_;
}
void set_branch_analysis(const BytecodeBranchAnalysis* branch_analysis) {
void set_branch_analysis(BytecodeBranchAnalysis* branch_analysis) {
branch_analysis_ = branch_analysis;
}
......@@ -227,12 +243,12 @@ class BytecodeGraphBuilder {
CompilationInfo* info_;
JSGraph* jsgraph_;
Handle<BytecodeArray> bytecode_array_;
Handle<HandlerTable> exception_handler_table_;
const FrameStateFunctionInfo* frame_state_function_info_;
const interpreter::BytecodeArrayIterator* bytecode_iterator_;
const BytecodeBranchAnalysis* branch_analysis_;
BytecodeBranchAnalysis* branch_analysis_; // TODO(mstarzinger): Make const.
Environment* environment_;
// Merge environments are snapshots of the environment at a particular
// bytecode offset to be merged into a later environment.
ZoneMap<int, Environment*> merge_environments_;
......@@ -241,6 +257,10 @@ class BytecodeGraphBuilder {
// where it is known there are back branches, ie a loop header.
ZoneMap<int, Environment*> loop_header_environments_;
// Exception handlers currently entered by the iteration.
ZoneStack<ExceptionHandler> exception_handlers_;
int current_exception_handler_;
// Temporary storage for building node input lists.
int input_buffer_size_;
Node** input_buffer_;
......
......@@ -3417,6 +3417,18 @@ int LiteralsArray::literals_count() const {
return length() - kFirstLiteralIndex;
}
int HandlerTable::GetRangeStart(int index) const {
return Smi::cast(get(index * kRangeEntrySize + kRangeStartIndex))->value();
}
int HandlerTable::GetRangeEnd(int index) const {
return Smi::cast(get(index * kRangeEntrySize + kRangeEndIndex))->value();
}
int HandlerTable::GetRangeHandler(int index) const {
return HandlerOffsetField::decode(
Smi::cast(get(index * kRangeEntrySize + kRangeHandlerIndex))->value());
}
void HandlerTable::SetRangeStart(int index, int value) {
set(index * kRangeEntrySize + kRangeStartIndex, Smi::FromInt(value));
......@@ -3453,6 +3465,9 @@ void HandlerTable::SetReturnHandler(int index, int offset,
set(index * kReturnEntrySize + kReturnHandlerIndex, Smi::FromInt(value));
}
int HandlerTable::NumberOfRangeEntries() const {
return length() / kRangeEntrySize;
}
#define MAKE_STRUCT_CAST(NAME, Name, name) CAST_ACCESSOR(Name)
STRUCT_LIST(MAKE_STRUCT_CAST)
......
......@@ -4761,13 +4761,18 @@ class HandlerTable : public FixedArray {
// undecidable it is merely an approximation (e.g. useful for debugger).
enum CatchPrediction { UNCAUGHT, CAUGHT };
// Accessors for handler table based on ranges.
// Getters for handler table based on ranges.
inline int GetRangeStart(int index) const;
inline int GetRangeEnd(int index) const;
inline int GetRangeHandler(int index) const;
// Setters for handler table based on ranges.
inline void SetRangeStart(int index, int value);
inline void SetRangeEnd(int index, int value);
inline void SetRangeHandler(int index, int offset, CatchPrediction pred);
inline void SetRangeDepth(int index, int value);
// Accessors for handler table based on return addresses.
// Setters for handler table based on return addresses.
inline void SetReturnOffset(int index, int value);
inline void SetReturnHandler(int index, int offset, CatchPrediction pred);
......@@ -4777,6 +4782,9 @@ class HandlerTable : public FixedArray {
// Lookup handler in a table based on return addresses.
int LookupReturn(int pc_offset, CatchPrediction* prediction);
// Returns the number of entries in the table.
inline int NumberOfRangeEntries() const;
// Returns the required length of the underlying fixed array.
static int LengthForRange(int entries) { return entries * kRangeEntrySize; }
static int LengthForReturn(int entries) { return entries * kReturnEntrySize; }
......
......@@ -515,11 +515,9 @@
# TODO(mstarzinger,4674): Support exception handlers in BytecodeGraphBuilder.
'test-run-deopt/DeoptExceptionHandlerCatch': [FAIL],
'test-run-jsexceptions/Catch': [FAIL],
'test-run-jsexceptions/CatchBreak': [FAIL],
'test-run-jsexceptions/CatchCall': [FAIL],
'test-run-jsexceptions/CatchNested': [FAIL],
'test-run-jsexceptions/DeoptCatch': [FAIL],
# TODO(mstarzinger,4674): Bug in control flow modeling. Investigate and fix.
'test-run-jsexceptions/ThrowMessageIndirectly': [FAIL],
'test-api-interceptors/InterceptorCallICInvalidatedConstantFunctionViaGlobal': [SKIP],
'test-api-interceptors/InterceptorLoadICInvalidatedCallbackViaGlobal': [SKIP],
......@@ -560,7 +558,6 @@
'test-profile-generator/RecordStackTraceAtStartProfiling': [SKIP],
'test-run-inlining/InlineTwice': [SKIP],
'test-run-jscalls/LookupCall': [SKIP],
'test-run-jsexceptions/DeoptFinallyReturn': [SKIP],
'test-run-jsexceptions/ThrowMessageDirectly': [SKIP],
'test-run-jsexceptions/ThrowMessagePosition': [SKIP],
'test-run-jsobjects/ArgumentsRest': [SKIP],
......
......@@ -1412,6 +1412,103 @@ TEST(BytecodeGraphBuilderTestInstanceOf) {
}
}
TEST(BytecodeGraphBuilderTryCatch) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
ExpectedSnippet<0> snippets[] = {
// TODO(mstarzinger): Fix cases where nothing throws.
// {"var a = 1; try { a = 2 } catch(e) { a = 3 }; return a;",
// {handle(Smi::FromInt(2), isolate)}},
{"var a; try { undef.x } catch(e) { a = 2 }; return a;",
{handle(Smi::FromInt(2), isolate)}},
{"var a; try { throw 1 } catch(e) { a = e + 2 }; return a;",
{handle(Smi::FromInt(3), isolate)}},
{"var a; try { throw 1 } catch(e) { a = e + 2 };"
" try { throw a } catch(e) { a = e + 3 }; return a;",
{handle(Smi::FromInt(6), isolate)}},
};
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
for (size_t i = 0; i < num_snippets; i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s }\n%s();", kFunctionName,
snippets[i].code_snippet, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*snippets[i].return_value()));
}
}
TEST(BytecodeGraphBuilderTryFinally1) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
ExpectedSnippet<0> snippets[] = {
{"var a = 1; try { a = a + 1; } finally { a = a + 2; }; return a;",
{handle(Smi::FromInt(4), isolate)}},
// TODO(mstarzinger): Fix cases where nothing throws.
// {"var a = 1; try { a = 2; return 23; } finally { a = 3 }; return a;",
// {handle(Smi::FromInt(23), isolate)}},
{"var a = 1; try { a = 2; throw 23; } finally { return a; };",
{handle(Smi::FromInt(2), isolate)}},
// {"var a = 1; for (var i = 10; i < 20; i += 5) {"
// " try { a = 2; break; } finally { a = 3; }"
// "} return a + i;",
// {handle(Smi::FromInt(13), isolate)}},
// {"var a = 1; for (var i = 10; i < 20; i += 5) {"
// " try { a = 2; continue; } finally { a = 3; }"
// "} return a + i;",
// {handle(Smi::FromInt(23), isolate)}},
// {"var a = 1; try { a = 2;"
// " try { a = 3; throw 23; } finally { a = 4; }"
// "} catch(e) { a = a + e; } return a;",
// {handle(Smi::FromInt(27), isolate)}},
};
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
for (size_t i = 0; i < num_snippets; i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s }\n%s();", kFunctionName,
snippets[i].code_snippet, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*snippets[i].return_value()));
}
}
TEST(BytecodeGraphBuilderTryFinally2) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
ExpectedSnippet<0, const char*> snippets[] = {
{"var a = 1; try { a = 2; throw 23; } finally { a = 3 }; return a;",
{"Uncaught 23"}},
{"var a = 1; try { a = 2; throw 23; } finally { throw 42; };",
{"Uncaught 42"}},
};
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
for (size_t i = 0; i < num_snippets; i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s }\n%s();", kFunctionName,
snippets[i].code_snippet, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
v8::Local<v8::String> message = tester.CheckThrowsReturnMessage()->Get();
v8::Local<v8::String> expected_string = v8_str(snippets[i].return_value());
CHECK(
message->Equals(CcTest::isolate()->GetCurrentContext(), expected_string)
.FromJust());
}
}
TEST(BytecodeGraphBuilderThrow) {
HandleAndZoneScope scope;
......@@ -1425,8 +1522,7 @@ TEST(BytecodeGraphBuilderThrow) {
{"throw 1;", {"Uncaught 1"}},
{"throw 'Error';", {"Uncaught Error"}},
{"throw 'Error1'; throw 'Error2'", {"Uncaught Error1"}},
// TODO(mythria): Enable these tests when JumpIfTrue is supported.
// {"var a = true; if (a) { throw 'Error'; }", {"Error"}},
{"var a = true; if (a) { throw 'Error'; }", {"Uncaught Error"}},
};
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
......@@ -1434,6 +1530,7 @@ TEST(BytecodeGraphBuilderThrow) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s }\n%s();", kFunctionName,
snippets[i].code_snippet, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
v8::Local<v8::String> message = tester.CheckThrowsReturnMessage()->Get();
v8::Local<v8::String> expected_string = v8_str(snippets[i].return_value());
......
......@@ -212,19 +212,12 @@ static void CheckBytecodeArrayEqual(const ExpectedSnippet<T, C>& expected,
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());
CHECK_EQ(expected.handler_count, table->NumberOfRangeEntries());
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);
CHECK_EQ(expected.handlers[i].start, table->GetRangeStart(i));
CHECK_EQ(expected.handlers[i].end, table->GetRangeEnd(i));
CHECK_EQ(expected.handlers[i].handler, table->GetRangeHandler(i));
}
}
......
......@@ -789,8 +789,6 @@
'compiler/regress-stacktrace-methods': [SKIP],
'compiler/rotate': [SKIP],
'compiler/strict-recompile': [SKIP],
'compiler/try-binop': [SKIP],
'compiler/try-deopt': [SKIP],
'compiler/uint32': [SKIP],
'compiler/variables': [SKIP],
'contextual-calls': [SKIP],
......@@ -957,7 +955,6 @@
'stack-traces': [SKIP],
'strict-mode': [SKIP],
'tools/profviz': [SKIP],
'try-binop': [SKIP],
'undetectable-compare': [SKIP],
'unused-context-in-with': [SKIP],
'value-wrapper': [SKIP],
......
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