Commit 8f1f1cb1 authored by neis's avatar neis Committed by Commit bot

Move catch prediction into frontend and make it aware of rethrows.

This solves an issue with throws inside for-of always being marked as caught.

BUG=v8:5183

Review-Url: https://codereview.chromium.org/2146493002
Cr-Commit-Position: refs/heads/master@{#37686}
parent ea90556a
......@@ -20,7 +20,8 @@ class AstNumberingVisitor final : public AstVisitor {
yield_count_(0),
properties_(zone),
slot_cache_(zone),
dont_optimize_reason_(kNoReason) {
dont_optimize_reason_(kNoReason),
catch_predicted_(false) {
InitializeAstVisitor(isolate);
}
......@@ -80,6 +81,7 @@ class AstNumberingVisitor final : public AstVisitor {
// The slot cache allows us to reuse certain feedback vector slots.
FeedbackVectorSlotCache slot_cache_;
BailoutReason dont_optimize_reason_;
bool catch_predicted_;
DEFINE_AST_VISITOR_SUBCLASS_MEMBERS();
DISALLOW_COPY_AND_ASSIGN(AstNumberingVisitor);
......@@ -297,7 +299,17 @@ void AstNumberingVisitor::VisitWhileStatement(WhileStatement* node) {
void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
IncrementNodeCount();
DisableCrankshaft(kTryCatchStatement);
Visit(node->try_block());
{
const bool old_catch_predicted = catch_predicted_;
// If the node's clear_pending_message flag is unset, we assume that the
// catch block is a ReThrow and hence predict uncaught (unless caught by
// outer handlers). Otherwise, we predict caught.
const bool not_rethrow = node->clear_pending_message();
catch_predicted_ = catch_predicted_ || not_rethrow;
node->set_catch_predicted(catch_predicted_);
Visit(node->try_block());
catch_predicted_ = old_catch_predicted;
}
Visit(node->catch_block());
}
......@@ -305,6 +317,9 @@ void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
void AstNumberingVisitor::VisitTryFinallyStatement(TryFinallyStatement* node) {
IncrementNodeCount();
DisableCrankshaft(kTryFinallyStatement);
// We can't know whether the finally block will override ("catch") an
// exception thrown in the try block, so we just adopt the outer prediction.
node->set_catch_predicted(catch_predicted_);
Visit(node->try_block());
Visit(node->finally_block());
}
......
......@@ -15,7 +15,7 @@ class Zone;
namespace AstNumbering {
// Assign type feedback IDs, bailout IDs, and generator yield IDs to an AST node
// tree.
// tree; perform catch prediction for TryStatements.
bool Renumber(Isolate* isolate, Zone* zone, FunctionLiteral* function);
}
......
......@@ -1140,12 +1140,26 @@ class TryStatement : public Statement {
Block* try_block() const { return try_block_; }
void set_try_block(Block* b) { try_block_ = b; }
// Prediction of whether exceptions thrown into the handler for this try block
// will be caught.
//
// This is set in ast-numbering and later compiled into the code's handler
// table. The runtime uses this information to implement a feature that
// notifies the debugger when an uncaught exception is thrown, _before_ the
// exception propagates to the top.
//
// Since it's generally undecidable whether an exception will be caught, our
// prediction is only an approximation.
bool catch_predicted() const { return catch_predicted_; }
void set_catch_predicted(bool b) { catch_predicted_ = b; }
protected:
TryStatement(Zone* zone, Block* try_block, int pos)
: Statement(zone, pos), try_block_(try_block) {}
: Statement(zone, pos), try_block_(try_block), catch_predicted_(false) {}
private:
Block* try_block_;
bool catch_predicted_;
};
......
......@@ -1431,7 +1431,7 @@ void AstPrinter::VisitForOfStatement(ForOfStatement* node) {
void AstPrinter::VisitTryCatchStatement(TryCatchStatement* node) {
IndentedScope indent(this, "TRY CATCH", node->position());
PrintIndentedVisit("TRY", node->try_block());
PrintTryStatement(node);
PrintLiteralWithModeIndented("CATCHVAR",
node->variable(),
node->variable()->name());
......@@ -1441,10 +1441,15 @@ void AstPrinter::VisitTryCatchStatement(TryCatchStatement* node) {
void AstPrinter::VisitTryFinallyStatement(TryFinallyStatement* node) {
IndentedScope indent(this, "TRY FINALLY", node->position());
PrintIndentedVisit("TRY", node->try_block());
PrintTryStatement(node);
PrintIndentedVisit("FINALLY", node->finally_block());
}
void AstPrinter::PrintTryStatement(TryStatement* node) {
PrintIndentedVisit("TRY", node->try_block());
PrintIndented("CATCH PREDICTED");
Print(" %d\n", node->catch_predicted());
}
void AstPrinter::VisitDebuggerStatement(DebuggerStatement* node) {
IndentedScope indent(this, "DEBUGGER", node->position());
......
......@@ -131,6 +131,7 @@ class AstPrinter: public PrettyPrinter {
Handle<Object> value);
void PrintLabelsIndented(ZoneList<const AstRawString*>* labels);
void PrintProperties(ZoneList<ObjectLiteral::Property*>* properties);
void PrintTryStatement(TryStatement* try_statement);
void inc_indent() { indent_++; }
void dec_indent() { indent_--; }
......
......@@ -124,9 +124,9 @@ void FullCodeGenerator::PopulateHandlerTable(Handle<Code> code) {
Handle<HandlerTable>::cast(isolate()->factory()->NewFixedArray(
HandlerTable::LengthForRange(handler_table_size), TENURED));
for (int i = 0; i < handler_table_size; ++i) {
HandlerTable::CatchPrediction prediction =
handler_table_[i].try_catch_depth > 0 ? HandlerTable::CAUGHT
: HandlerTable::UNCAUGHT;
HandlerTable::CatchPrediction prediction = handler_table_[i].catch_predicted
? HandlerTable::CAUGHT
: HandlerTable::UNCAUGHT;
table->SetRangeStart(i, handler_table_[i].range_start);
table->SetRangeEnd(i, handler_table_[i].range_end);
table->SetRangeHandler(i, handler_table_[i].handler_offset, prediction);
......@@ -1316,15 +1316,13 @@ void FullCodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
// Try block code. Sets up the exception handler chain.
__ bind(&try_entry);
try_catch_depth_++;
int handler_index = NewHandlerTableEntry();
EnterTryBlock(handler_index, &handler_entry);
EnterTryBlock(handler_index, &handler_entry, stmt->catch_predicted());
{
Comment cmnt_try(masm(), "[ Try block");
Visit(stmt->try_block());
}
ExitTryBlock(handler_index);
try_catch_depth_--;
__ bind(&exit);
}
......@@ -1368,7 +1366,7 @@ void FullCodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
// Set up try handler.
__ bind(&try_entry);
int handler_index = NewHandlerTableEntry();
EnterTryBlock(handler_index, &handler_entry);
EnterTryBlock(handler_index, &handler_entry, stmt->catch_predicted());
{
Comment cmnt_try(masm(), "[ Try block");
TryFinally try_body(this, &deferred);
......@@ -1551,13 +1549,13 @@ void FullCodeGenerator::VisitThrow(Throw* expr) {
if (context()->IsStackValue()) OperandStackDepthIncrement(1);
}
void FullCodeGenerator::EnterTryBlock(int handler_index, Label* handler) {
void FullCodeGenerator::EnterTryBlock(int handler_index, Label* handler,
bool catch_predicted) {
HandlerTableEntry* entry = &handler_table_[handler_index];
entry->range_start = masm()->pc_offset();
entry->handler_offset = handler->pos();
entry->try_catch_depth = try_catch_depth_;
entry->stack_depth = operand_stack_depth_;
entry->catch_predicted = catch_predicted;
// We are using the operand stack depth, check for accuracy.
EmitOperandStackDepthCheck();
......
......@@ -37,7 +37,6 @@ class FullCodeGenerator: public AstVisitor {
scope_(info->scope()),
nesting_stack_(NULL),
loop_depth_(0),
try_catch_depth_(0),
operand_stack_depth_(0),
globals_(NULL),
context_(NULL),
......@@ -687,7 +686,7 @@ class FullCodeGenerator: public AstVisitor {
void RecordPosition(int pos);
// Non-local control flow support.
void EnterTryBlock(int handler_index, Label* handler);
void EnterTryBlock(int handler_index, Label* handler, bool catch_predicted);
void ExitTryBlock(int handler_index);
void EnterFinallyBlock();
void ExitFinallyBlock();
......@@ -774,7 +773,7 @@ class FullCodeGenerator: public AstVisitor {
unsigned range_end;
unsigned handler_offset;
int stack_depth;
int try_catch_depth;
bool catch_predicted;
};
class ExpressionContext BASE_EMBEDDED {
......@@ -969,7 +968,6 @@ class FullCodeGenerator: public AstVisitor {
Label return_label_;
NestedStatement* nesting_stack_;
int loop_depth_;
int try_catch_depth_;
int operand_stack_depth_;
ZoneList<Handle<Object> >* globals_;
const ExpressionContext* context_;
......
......@@ -295,12 +295,7 @@ class BytecodeGenerator::ControlScopeForTryCatch final
public:
ControlScopeForTryCatch(BytecodeGenerator* generator,
TryCatchBuilder* try_catch_builder)
: ControlScope(generator) {
generator->try_catch_nesting_level_++;
}
virtual ~ControlScopeForTryCatch() {
generator()->try_catch_nesting_level_--;
}
: ControlScope(generator) {}
protected:
bool Execute(Command command, Statement* statement) override {
......@@ -326,12 +321,7 @@ class BytecodeGenerator::ControlScopeForTryFinally final
DeferredCommands* commands)
: ControlScope(generator),
try_finally_builder_(try_finally_builder),
commands_(commands) {
generator->try_finally_nesting_level_++;
}
virtual ~ControlScopeForTryFinally() {
generator()->try_finally_nesting_level_--;
}
commands_(commands) {}
protected:
bool Execute(Command command, Statement* statement) override {
......@@ -557,9 +547,7 @@ BytecodeGenerator::BytecodeGenerator(CompilationInfo* info)
execution_result_(nullptr),
register_allocator_(nullptr),
generator_resume_points_(info->literal()->yield_count(), info->zone()),
generator_state_(),
try_catch_nesting_level_(0),
try_finally_nesting_level_(0) {
generator_state_() {
InitializeAstVisitor(isolate());
}
......@@ -1189,7 +1177,7 @@ void BytecodeGenerator::VisitForOfStatement(ForOfStatement* stmt) {
}
void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
TryCatchBuilder try_control_builder(builder());
TryCatchBuilder try_control_builder(builder(), stmt->catch_predicted());
Register no_reg;
// Preserve the context in a dedicated register, so that it can be restored
......@@ -1225,7 +1213,7 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
}
void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
TryFinallyBuilder try_control_builder(builder(), IsInsideTryCatch());
TryFinallyBuilder try_control_builder(builder(), stmt->catch_predicted());
Register no_reg;
// We keep a record of all paths that enter the finally-block to be able to
......
......@@ -165,10 +165,6 @@ 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; }
// Initialize an array of temporary registers with consecutive registers.
template <size_t N>
void InitializeWithConsecutiveRegisters(Register (&registers)[N]);
......@@ -215,8 +211,6 @@ class BytecodeGenerator final : public AstVisitor {
RegisterAllocationScope* register_allocator_;
ZoneVector<BytecodeLabel> generator_resume_points_;
Register generator_state_;
int try_catch_nesting_level_;
int try_finally_nesting_level_;
};
} // namespace interpreter
......
......@@ -142,7 +142,7 @@ void TryCatchBuilder::EndTry() {
builder()->MarkTryEnd(handler_id_);
builder()->Jump(&exit_);
builder()->Bind(&handler_);
builder()->MarkHandler(handler_id_, true);
builder()->MarkHandler(handler_id_, catch_predicted_);
}
......@@ -167,7 +167,7 @@ void TryFinallyBuilder::EndTry() {
void TryFinallyBuilder::BeginHandler() {
builder()->Bind(&handler_);
builder()->MarkHandler(handler_id_, will_catch_);
builder()->MarkHandler(handler_id_, catch_predicted_);
}
......
......@@ -144,8 +144,10 @@ class SwitchBuilder final : public BreakableControlFlowBuilder {
// 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()) {}
explicit TryCatchBuilder(BytecodeArrayBuilder* builder, bool catch_predicted)
: ControlFlowBuilder(builder),
handler_id_(builder->NewHandlerEntry()),
catch_predicted_(catch_predicted) {}
void BeginTry(Register context);
void EndTry();
......@@ -153,6 +155,7 @@ class TryCatchBuilder final : public ControlFlowBuilder {
private:
int handler_id_;
bool catch_predicted_;
BytecodeLabel handler_;
BytecodeLabel exit_;
};
......@@ -161,11 +164,12 @@ 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, bool will_catch)
explicit TryFinallyBuilder(BytecodeArrayBuilder* builder,
bool catch_predicted)
: ControlFlowBuilder(builder),
handler_id_(builder->NewHandlerEntry()),
finalization_sites_(builder->zone()),
will_catch_(will_catch) {}
catch_predicted_(catch_predicted),
finalization_sites_(builder->zone()) {}
void BeginTry(Register context);
void LeaveTry();
......@@ -176,15 +180,11 @@ class TryFinallyBuilder final : public ControlFlowBuilder {
private:
int handler_id_;
bool catch_predicted_;
BytecodeLabel handler_;
// 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
......
......@@ -734,8 +734,7 @@ class Isolate {
// Tries to predict whether an exception will be caught. Note that this can
// only produce an estimate, because it is undecidable whether a finally
// clause will consume or re-throw an exception. We conservatively assume any
// finally clause will behave as if the exception were consumed.
// clause will consume or re-throw an exception.
enum CatchType { NOT_CAUGHT, CAUGHT_BY_JAVASCRIPT, CAUGHT_BY_EXTERNAL };
CatchType PredictExceptionCatcher();
......
// 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
Debug = debug.Debug
let error = false;
let uncaught;
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Exception) return;
try {
uncaught = event_data.uncaught();
} catch (e) {
error = true;
}
}
Debug.setBreakOnException();
Debug.setListener(listener);
function assertCaught(f) {
try {f()} finally {
assertFalse(uncaught);
return;
}
}
function assertUncaught(f) {
try {f()} finally {
assertTrue(uncaught);
return;
}
}
assertUncaught(() => {
for (var a of [1, 2, 3]) {
throw a
}
});
assertUncaught(() => {
for (var a of [1, 2, 3]) {
try {throw a} finally {}
}
});
assertCaught(() => {
for (var a of [1, 2, 3]) {
try {
try {throw a} finally {}
} catch(_) {}
}
});
assertCaught(() => {
try {
for (var a of [1, 2, 3]) {
try {throw a} finally {}
}
} catch(_) {}
});
assertFalse(error);
......@@ -92,6 +92,9 @@
'debug-set-variable-value': [PASS, NO_VARIANTS],
'es6/debug-evaluate-blockscopes': [PASS, NO_VARIANTS],
# TODO(mstarzinger): Use frontend's catch prediction in Turbofan.
'debug-exceptions': [PASS, NO_VARIANTS],
# Assumptions about optimization need investigation in TurboFan.
'regress-sync-optimized-lists': [PASS, NO_VARIANTS],
......
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