[interpreter] Correctly thread through catch prediction.
This change correctly sets the {CatchPrediction} field in exception handler tables for bytecode and optimized code. It also adds tests independent of promise handling for this prediction, to ensure all our backends are in sync on their prediction. R=rmcilroy@chromium.org,yangguo@chromium.org TEST=mjsunit/compiler/debug-catch-prediction BUG=v8:4674 LOG=n Review URL: https://codereview.chromium.org/1690973002 Cr-Commit-Position: refs/heads/master@{#33906}
This commit is contained in:
parent
82434b1bd1
commit
ba55f5594c
@ -1637,8 +1637,10 @@ void BytecodeGraphBuilder::EnterAndExitExceptionHandlers(int current_offset) {
|
||||
int next_end = table->GetRangeEnd(current_exception_handler_);
|
||||
int next_handler = table->GetRangeHandler(current_exception_handler_);
|
||||
int context_register = table->GetRangeData(current_exception_handler_);
|
||||
CatchPrediction pred =
|
||||
table->GetRangePrediction(current_exception_handler_);
|
||||
exception_handlers_.push(
|
||||
{next_start, next_end, next_handler, context_register});
|
||||
{next_start, next_end, next_handler, context_register, pred});
|
||||
current_exception_handler_++;
|
||||
}
|
||||
}
|
||||
@ -1696,9 +1698,11 @@ Node* BytecodeGraphBuilder::MakeNode(const Operator* op, int value_input_count,
|
||||
if (!result->op()->HasProperty(Operator::kNoThrow) && inside_handler) {
|
||||
int handler_offset = exception_handlers_.top().handler_offset_;
|
||||
int context_index = exception_handlers_.top().context_register_;
|
||||
CatchPrediction prediction = exception_handlers_.top().pred_;
|
||||
interpreter::Register context_register(context_index);
|
||||
// TODO(mstarzinger): Thread through correct prediction!
|
||||
IfExceptionHint hint = IfExceptionHint::kLocallyCaught;
|
||||
IfExceptionHint hint = prediction == CatchPrediction::CAUGHT
|
||||
? IfExceptionHint::kLocallyCaught
|
||||
: IfExceptionHint::kLocallyUncaught;
|
||||
Environment* success_env = environment()->CopyForConditional();
|
||||
const Operator* op = common()->IfException(hint);
|
||||
Node* effect = environment()->GetEffectDependency();
|
||||
|
@ -157,6 +157,9 @@ class BytecodeGraphBuilder {
|
||||
// new nodes.
|
||||
static const int kInputBufferSizeIncrement = 64;
|
||||
|
||||
// The catch prediction from the handler table is reused.
|
||||
typedef HandlerTable::CatchPrediction CatchPrediction;
|
||||
|
||||
// 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
|
||||
@ -166,6 +169,7 @@ class BytecodeGraphBuilder {
|
||||
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.
|
||||
CatchPrediction pred_; // Prediction of whether handler is catching.
|
||||
};
|
||||
|
||||
// Field accessors
|
||||
|
@ -301,7 +301,12 @@ class BytecodeGenerator::ControlScopeForTryCatch final
|
||||
public:
|
||||
ControlScopeForTryCatch(BytecodeGenerator* generator,
|
||||
TryCatchBuilder* try_catch_builder)
|
||||
: ControlScope(generator) {}
|
||||
: ControlScope(generator) {
|
||||
generator->try_catch_nesting_level_++;
|
||||
}
|
||||
virtual ~ControlScopeForTryCatch() {
|
||||
generator()->try_catch_nesting_level_--;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool Execute(Command command, Statement* statement) override {
|
||||
@ -328,7 +333,12 @@ class BytecodeGenerator::ControlScopeForTryFinally final
|
||||
DeferredCommands* commands)
|
||||
: ControlScope(generator),
|
||||
try_finally_builder_(try_finally_builder),
|
||||
commands_(commands) {}
|
||||
commands_(commands) {
|
||||
generator->try_finally_nesting_level_++;
|
||||
}
|
||||
virtual ~ControlScopeForTryFinally() {
|
||||
generator()->try_finally_nesting_level_--;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool Execute(Command command, Statement* statement) override {
|
||||
@ -543,7 +553,9 @@ BytecodeGenerator::BytecodeGenerator(Isolate* isolate, Zone* zone)
|
||||
execution_control_(nullptr),
|
||||
execution_context_(nullptr),
|
||||
execution_result_(nullptr),
|
||||
register_allocator_(nullptr) {
|
||||
register_allocator_(nullptr),
|
||||
try_catch_nesting_level_(0),
|
||||
try_finally_nesting_level_(0) {
|
||||
InitializeAstVisitor(isolate);
|
||||
}
|
||||
|
||||
@ -1153,7 +1165,7 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
|
||||
|
||||
|
||||
void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
|
||||
TryFinallyBuilder try_control_builder(builder());
|
||||
TryFinallyBuilder try_control_builder(builder(), IsInsideTryCatch());
|
||||
Register no_reg;
|
||||
|
||||
// We keep a record of all paths that enter the finally-block to be able to
|
||||
|
@ -124,6 +124,10 @@ class BytecodeGenerator final : public AstVisitor {
|
||||
void RecordStoreToRegister(Register reg);
|
||||
Register LoadFromAliasedRegister(Register reg);
|
||||
|
||||
// Methods for tracking try-block nesting.
|
||||
bool IsInsideTryCatch() const { return try_catch_nesting_level_ > 0; }
|
||||
bool IsInsideTryFinally() const { return try_finally_nesting_level_ > 0; }
|
||||
|
||||
inline void set_builder(BytecodeArrayBuilder* builder) { builder_ = builder; }
|
||||
inline BytecodeArrayBuilder* builder() const { return builder_; }
|
||||
|
||||
@ -170,6 +174,8 @@ class BytecodeGenerator final : public AstVisitor {
|
||||
ContextScope* execution_context_;
|
||||
ExpressionResultScope* execution_result_;
|
||||
RegisterAllocationScope* register_allocator_;
|
||||
int try_catch_nesting_level_;
|
||||
int try_finally_nesting_level_;
|
||||
};
|
||||
|
||||
} // namespace interpreter
|
||||
|
@ -172,7 +172,7 @@ void TryFinallyBuilder::EndTry() {
|
||||
|
||||
void TryFinallyBuilder::BeginHandler() {
|
||||
builder()->Bind(&handler_);
|
||||
builder()->MarkHandler(handler_id_, false);
|
||||
builder()->MarkHandler(handler_id_, will_catch_);
|
||||
}
|
||||
|
||||
|
||||
|
@ -165,10 +165,11 @@ class TryCatchBuilder final : public ControlFlowBuilder {
|
||||
// A class to help with co-ordinating control flow in try-finally statements.
|
||||
class TryFinallyBuilder final : public ControlFlowBuilder {
|
||||
public:
|
||||
explicit TryFinallyBuilder(BytecodeArrayBuilder* builder)
|
||||
explicit TryFinallyBuilder(BytecodeArrayBuilder* builder, bool will_catch)
|
||||
: ControlFlowBuilder(builder),
|
||||
handler_id_(builder->NewHandlerEntry()),
|
||||
finalization_sites_(builder->zone()) {}
|
||||
finalization_sites_(builder->zone()),
|
||||
will_catch_(will_catch) {}
|
||||
|
||||
void BeginTry(Register context);
|
||||
void LeaveTry();
|
||||
@ -183,6 +184,11 @@ class TryFinallyBuilder final : public ControlFlowBuilder {
|
||||
|
||||
// Unbound labels that identify jumps to the finally block in the code.
|
||||
ZoneVector<BytecodeLabel> finalization_sites_;
|
||||
|
||||
// Conservative prediction of whether exceptions thrown into the handler for
|
||||
// this finally block will be caught. Note that such a prediction depends on
|
||||
// whether this try-finally is nested inside a surrounding try-catch.
|
||||
bool will_catch_;
|
||||
};
|
||||
|
||||
} // namespace interpreter
|
||||
|
@ -3410,6 +3410,12 @@ int HandlerTable::GetRangeData(int index) const {
|
||||
return Smi::cast(get(index * kRangeEntrySize + kRangeDataIndex))->value();
|
||||
}
|
||||
|
||||
HandlerTable::CatchPrediction HandlerTable::GetRangePrediction(
|
||||
int index) const {
|
||||
return HandlerPredictionField::decode(
|
||||
Smi::cast(get(index * kRangeEntrySize + kRangeHandlerIndex))->value());
|
||||
}
|
||||
|
||||
void HandlerTable::SetRangeStart(int index, int value) {
|
||||
set(index * kRangeEntrySize + kRangeStartIndex, Smi::FromInt(value));
|
||||
}
|
||||
|
@ -4803,6 +4803,9 @@ class HandlerTable : public FixedArray {
|
||||
// Lookup handler in a table based on return addresses.
|
||||
int LookupReturn(int pc_offset, CatchPrediction* prediction);
|
||||
|
||||
// Returns the conservative catch predication.
|
||||
inline CatchPrediction GetRangePrediction(int index) const;
|
||||
|
||||
// Returns the number of entries in the table.
|
||||
inline int NumberOfRangeEntries() const;
|
||||
|
||||
|
143
test/mjsunit/compiler/debug-catch-prediction.js
Normal file
143
test/mjsunit/compiler/debug-catch-prediction.js
Normal file
@ -0,0 +1,143 @@
|
||||
// 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.
|
||||
|
||||
// Flags: --expose-debug-as debug --allow-natives-syntax
|
||||
|
||||
// Test debug event catch prediction for thrown exceptions. We distinguish
|
||||
// between "caught" and "uncaught" based on the following assumptions:
|
||||
// 1) try-catch : Will always catch the exception.
|
||||
// 2) try-finally : Will always re-throw the exception.
|
||||
|
||||
Debug = debug.Debug;
|
||||
|
||||
var log = [];
|
||||
|
||||
function listener(event, exec_state, event_data, data) {
|
||||
try {
|
||||
if (event == Debug.DebugEvent.Exception) {
|
||||
log.push([event_data.exception(), event_data.uncaught()]);
|
||||
}
|
||||
} catch (e) {
|
||||
%AbortJS(e + "\n" + e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.setBreakOnException();
|
||||
Debug.setListener(listener);
|
||||
|
||||
(function TryCatch() {
|
||||
log = []; // Clear log.
|
||||
function f(a) {
|
||||
try {
|
||||
throw "boom" + a;
|
||||
} catch(e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
assertEquals("boom1", f(1));
|
||||
assertEquals("boom2", f(2));
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
assertEquals("boom3", f(3));
|
||||
print("Collect log:", log);
|
||||
assertEquals([["boom1",false], ["boom2",false], ["boom3",false]], log);
|
||||
})();
|
||||
|
||||
(function TryFinally() {
|
||||
log = []; // Clear log.
|
||||
function f(a) {
|
||||
try {
|
||||
throw "baem" + a;
|
||||
} finally {
|
||||
return a + 10;
|
||||
}
|
||||
}
|
||||
assertEquals(11, f(1));
|
||||
assertEquals(12, f(2));
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
assertEquals(13, f(3));
|
||||
print("Collect log:", log);
|
||||
assertEquals([["baem1",true], ["baem2",true], ["baem3",true]], log);
|
||||
})();
|
||||
|
||||
(function TryCatchFinally() {
|
||||
log = []; // Clear log.
|
||||
function f(a) {
|
||||
try {
|
||||
throw "wosh" + a;
|
||||
} catch(e) {
|
||||
return e + a;
|
||||
} finally {
|
||||
// Nothing.
|
||||
}
|
||||
}
|
||||
assertEquals("wosh11", f(1));
|
||||
assertEquals("wosh22", f(2));
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
assertEquals("wosh33", f(3));
|
||||
print("Collect log:", log);
|
||||
assertEquals([["wosh1",false], ["wosh2",false], ["wosh3",false]], log);
|
||||
})();
|
||||
|
||||
(function TryCatchNestedFinally() {
|
||||
log = []; // Clear log.
|
||||
function f(a) {
|
||||
try {
|
||||
try {
|
||||
throw "bang" + a;
|
||||
} finally {
|
||||
// Nothing.
|
||||
}
|
||||
} catch(e) {
|
||||
return e + a;
|
||||
}
|
||||
}
|
||||
assertEquals("bang11", f(1));
|
||||
assertEquals("bang22", f(2));
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
assertEquals("bang33", f(3));
|
||||
print("Collect log:", log);
|
||||
assertEquals([["bang1",false], ["bang2",false], ["bang3",false]], log);
|
||||
})();
|
||||
|
||||
(function TryFinallyNestedCatch() {
|
||||
log = []; // Clear log.
|
||||
function f(a) {
|
||||
try {
|
||||
try {
|
||||
throw "peng" + a;
|
||||
} catch(e) {
|
||||
return e
|
||||
}
|
||||
} finally {
|
||||
return a + 10;
|
||||
}
|
||||
}
|
||||
assertEquals(11, f(1));
|
||||
assertEquals(12, f(2));
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
assertEquals(13, f(3));
|
||||
print("Collect log:", log);
|
||||
assertEquals([["peng1",false], ["peng2",false], ["peng3",false]], log);
|
||||
})();
|
||||
|
||||
(function TryFinallyNestedFinally() {
|
||||
log = []; // Clear log.
|
||||
function f(a) {
|
||||
try {
|
||||
try {
|
||||
throw "oops" + a;
|
||||
} finally {
|
||||
// Nothing.
|
||||
}
|
||||
} finally {
|
||||
return a + 10;
|
||||
}
|
||||
}
|
||||
assertEquals(11, f(1));
|
||||
assertEquals(12, f(2));
|
||||
%OptimizeFunctionOnNextCall(f);
|
||||
assertEquals(13, f(3));
|
||||
print("Collect log:", log);
|
||||
assertEquals([["oops1",true], ["oops2",true], ["oops3",true]], log);
|
||||
})();
|
Loading…
Reference in New Issue
Block a user