[ignition] handle catch prediction tracking entirely in BytecodeGenerator

Remove catch prediction tracking from AstNumbering, and replace it with
a similar mechanism in the BytecodeGenerator visitor.

BUG=v8:4483, v8:5855

Change-Id: I6351ba311716102fa55cd9ef29b9955ab4b11027
Reviewed-on: https://chromium-review.googlesource.com/559006
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Cr-Commit-Position: refs/heads/master@{#46419}
This commit is contained in:
Caitlin Potter 2017-07-05 10:13:00 -04:00 committed by Commit Bot
parent 00f4b33a65
commit a7e5abff34
6 changed files with 119 additions and 103 deletions

View File

@ -26,7 +26,6 @@ class AstNumberingVisitor final : public AstVisitor<AstNumberingVisitor> {
slot_cache_(zone),
disable_fullcodegen_reason_(kNoReason),
dont_optimize_reason_(kNoReason),
catch_prediction_(HandlerTable::UNCAUGHT),
collect_type_profile_(collect_type_profile) {
InitializeAstVisitor(stack_limit);
}
@ -102,7 +101,6 @@ class AstNumberingVisitor final : public AstVisitor<AstNumberingVisitor> {
FeedbackSlotCache slot_cache_;
BailoutReason disable_fullcodegen_reason_;
BailoutReason dont_optimize_reason_;
HandlerTable::CatchPrediction catch_prediction_;
bool collect_type_profile_;
DEFINE_AST_VISITOR_SUBCLASS_MEMBERS();
@ -296,34 +294,6 @@ void AstNumberingVisitor::VisitFunctionDeclaration(FunctionDeclaration* node) {
void AstNumberingVisitor::VisitCallRuntime(CallRuntime* node) {
IncrementNodeCount();
VisitArguments(node->arguments());
// To support catch prediction within async/await:
//
// The AstNumberingVisitor is when catch prediction currently occurs, and it
// is the only common point that has access to this information. The parser
// just doesn't know yet. Take the following two cases of catch prediction:
//
// try { await fn(); } catch (e) { }
// try { await fn(); } finally { }
//
// When parsing the await that we want to mark as caught or uncaught, it's
// not yet known whether it will be followed by a 'finally' or a 'catch.
// The AstNumberingVisitor is what learns whether it is caught. To make
// the information available later to the runtime, the AstNumberingVisitor
// has to stash it somewhere. Changing the runtime function into another
// one in ast-numbering seemed like a simple and straightforward solution to
// that problem.
if (node->is_jsruntime() && catch_prediction_ == HandlerTable::ASYNC_AWAIT) {
switch (node->context_index()) {
case Context::ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX:
node->set_context_index(Context::ASYNC_FUNCTION_AWAIT_UNCAUGHT_INDEX);
break;
case Context::ASYNC_GENERATOR_AWAIT_CAUGHT:
node->set_context_index(Context::ASYNC_GENERATOR_AWAIT_UNCAUGHT);
break;
default:
break;
}
}
}
@ -361,18 +331,7 @@ void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
DCHECK(node->scope() == nullptr || !node->scope()->HasBeenRemoved());
IncrementNodeCount();
DisableFullCodegen(kTryCatchStatement);
{
const HandlerTable::CatchPrediction old_prediction = catch_prediction_;
// This node uses its own prediction, unless it's "uncaught", in which case
// we adopt the prediction of the outer try-block.
HandlerTable::CatchPrediction catch_prediction = node->catch_prediction();
if (catch_prediction != HandlerTable::UNCAUGHT) {
catch_prediction_ = catch_prediction;
}
node->set_catch_prediction(catch_prediction_);
Visit(node->try_block());
catch_prediction_ = old_prediction;
}
Visit(node->try_block());
Visit(node->catch_block());
}
@ -380,9 +339,6 @@ void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
void AstNumberingVisitor::VisitTryFinallyStatement(TryFinallyStatement* node) {
IncrementNodeCount();
DisableFullCodegen(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_prediction(catch_prediction_);
Visit(node->try_block());
Visit(node->finally_block());
}

View File

@ -971,30 +971,9 @@ 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.
HandlerTable::CatchPrediction catch_prediction() const {
return catch_prediction_;
}
void set_catch_prediction(HandlerTable::CatchPrediction prediction) {
catch_prediction_ = prediction;
}
protected:
TryStatement(Block* try_block, int pos, NodeType type)
: Statement(pos, type),
catch_prediction_(HandlerTable::UNCAUGHT),
try_block_(try_block) {}
HandlerTable::CatchPrediction catch_prediction_;
: Statement(pos, type), try_block_(try_block) {}
private:
Block* try_block_;
@ -1007,18 +986,52 @@ class TryCatchStatement final : public TryStatement {
Block* catch_block() const { return catch_block_; }
void set_catch_block(Block* b) { catch_block_ = b; }
// The clear_pending_message flag indicates whether or not to clear the
// isolate's pending exception message before executing the catch_block. In
// the normal use case, this flag is always on because the message object
// is not needed anymore when entering the catch block and should not be kept
// alive.
// The use case where the flag is off is when the catch block is guaranteed to
// rethrow the caught exception (using %ReThrow), which reuses the pending
// message instead of generating a new one.
// Prediction of whether exceptions thrown into the handler for this try block
// will be caught.
//
// BytecodeGenerator tracks the state of catch prediction, which can change
// with each TryCatchStatement encountered. The tracked catch prediction is
// 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.
//
// If this try/catch statement is meant to rethrow (HandlerTable::UNCAUGHT),
// the catch prediction value is set to the same value as the surrounding
// catch prediction.
//
// Since it's generally undecidable whether an exception will be caught, our
// prediction is only an approximation.
// ---------------------------------------------------------------------------
inline HandlerTable::CatchPrediction GetCatchPrediction(
HandlerTable::CatchPrediction outer_catch_prediction) const {
if (catch_prediction_ == HandlerTable::UNCAUGHT) {
return outer_catch_prediction;
}
return catch_prediction_;
}
// Indicates whether or not code should be generated to clear the pending
// exception. The pending exception is cleared for cases where the exception
// is not guaranteed to be rethrown, indicated by the value
// HandlerTable::UNCAUGHT. If both the current and surrounding catch handler's
// are predicted uncaught, the exception is not cleared.
//
// If this handler is not going to simply rethrow the exception, this method
// indicates that the isolate's pending exception message should be cleared
// before executing the catch_block.
// In the normal use case, this flag is always on because the message object
// is not needed anymore when entering the catch block and should not be
// kept alive.
// The use case where the flag is off is when the catch block is guaranteed
// to rethrow the caught exception (using %ReThrow), which reuses the
// pending message instead of generating a new one.
// (When the catch block doesn't rethrow but is guaranteed to perform an
// ordinary throw, not clearing the old message is safe but not very useful.)
bool clear_pending_message() const {
return catch_prediction_ != HandlerTable::UNCAUGHT;
// ordinary throw, not clearing the old message is safe but not very
// useful.)
inline bool ShouldClearPendingException(
HandlerTable::CatchPrediction outer_catch_prediction) const {
return catch_prediction_ != HandlerTable::UNCAUGHT ||
outer_catch_prediction != HandlerTable::UNCAUGHT;
}
private:
@ -1028,12 +1041,12 @@ class TryCatchStatement final : public TryStatement {
HandlerTable::CatchPrediction catch_prediction, int pos)
: TryStatement(try_block, pos, kTryCatchStatement),
scope_(scope),
catch_block_(catch_block) {
catch_prediction_ = catch_prediction;
}
catch_block_(catch_block),
catch_prediction_(catch_prediction) {}
Scope* scope_;
Block* catch_block_;
HandlerTable::CatchPrediction catch_prediction_;
};

View File

@ -875,24 +875,9 @@ void AstPrinter::VisitForOfStatement(ForOfStatement* node) {
void AstPrinter::VisitTryCatchStatement(TryCatchStatement* node) {
IndentedScope indent(this, "TRY CATCH", node->position());
PrintTryStatement(node);
PrintLiteralWithModeIndented("CATCHVAR", node->scope()->catch_variable(),
node->scope()->catch_variable()->name());
PrintIndentedVisit("CATCH", node->catch_block());
}
void AstPrinter::VisitTryFinallyStatement(TryFinallyStatement* node) {
IndentedScope indent(this, "TRY FINALLY", node->position());
PrintTryStatement(node);
PrintIndentedVisit("FINALLY", node->finally_block());
}
void AstPrinter::PrintTryStatement(TryStatement* node) {
PrintIndentedVisit("TRY", node->try_block());
PrintIndented("CATCH PREDICTION");
const char* prediction = "";
switch (node->catch_prediction()) {
switch (node->GetCatchPrediction(HandlerTable::UNCAUGHT)) {
case HandlerTable::UNCAUGHT:
prediction = "UNCAUGHT";
break;
@ -911,6 +896,14 @@ void AstPrinter::PrintTryStatement(TryStatement* node) {
UNREACHABLE();
}
Print(" %s\n", prediction);
PrintLiteralWithModeIndented("CATCHVAR", node->scope()->catch_variable(),
node->scope()->catch_variable()->name());
PrintIndentedVisit("CATCH", node->catch_block());
}
void AstPrinter::VisitTryFinallyStatement(TryFinallyStatement* node) {
IndentedScope indent(this, "TRY FINALLY", node->position());
PrintIndentedVisit("FINALLY", node->finally_block());
}
void AstPrinter::VisitDebuggerStatement(DebuggerStatement* node) {

View File

@ -96,7 +96,6 @@ class AstPrinter final : public AstVisitor<AstPrinter> {
void PrintLabelsIndented(ZoneList<const AstRawString*>* labels);
void PrintObjectProperties(ZoneList<ObjectLiteral::Property*>* properties);
void PrintClassProperties(ZoneList<ClassLiteral::Property*>* properties);
void PrintTryStatement(TryStatement* try_statement);
void inc_indent() { indent_++; }
void dec_indent() { indent_--; }

View File

@ -798,7 +798,8 @@ BytecodeGenerator::BytecodeGenerator(CompilationInfo* info)
generator_jump_table_(nullptr),
generator_object_(),
generator_state_(),
loop_depth_(0) {
loop_depth_(0),
catch_prediction_(HandlerTable::UNCAUGHT) {
DCHECK_EQ(closure_scope(), closure_scope()->GetClosureScope());
if (info->is_block_coverage_enabled()) {
DCHECK(FLAG_block_coverage);
@ -1576,7 +1577,13 @@ void BytecodeGenerator::VisitForOfStatement(ForOfStatement* stmt) {
}
void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
TryCatchBuilder try_control_builder(builder(), stmt->catch_prediction());
// Update catch prediction tracking. The updated catch_prediction value lasts
// until the end of the try_block in the AST node, and does not apply to the
// catch_block.
HandlerTable::CatchPrediction outer_catch_prediction = catch_prediction();
set_catch_prediction(stmt->GetCatchPrediction(outer_catch_prediction));
TryCatchBuilder try_control_builder(builder(), catch_prediction());
// Preserve the context in a dedicated register, so that it can be restored
// when the handler is entered by the stack-unwinding machinery.
@ -1590,6 +1597,7 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
{
ControlScopeForTryCatch scope(this, &try_control_builder);
Visit(stmt->try_block());
set_catch_prediction(outer_catch_prediction);
}
try_control_builder.EndTry();
@ -1598,7 +1606,7 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
builder()->StoreAccumulatorInRegister(context);
// If requested, clear message object as we enter the catch block.
if (stmt->clear_pending_message()) {
if (stmt->ShouldClearPendingException(outer_catch_prediction)) {
builder()->LoadTheHole().SetPendingMessage();
}
@ -1611,7 +1619,9 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
}
void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
TryFinallyBuilder try_control_builder(builder(), stmt->catch_prediction());
// We can't know whether the finally block will override ("catch") an
// exception thrown in the try bblock, so we just adopt the outer prediction.
TryFinallyBuilder try_control_builder(builder(), catch_prediction());
// We keep a record of all paths that enter the finally-block to be able to
// dispatch to the correct continuation point after the statements in the
@ -3249,15 +3259,49 @@ void BytecodeGenerator::VisitCallNew(CallNew* expr) {
}
}
int BytecodeGenerator::UpdateRuntimeFunctionForAsyncAwait(int context_index) {
// To support catch prediction within async/await:
//
// BytecodeGenerator models catch prediction (see VisitTryCatchstatement).
// Certain runtime calls need to be rewritten to invoke different functions,
// depending on the currently tracked catch prediction state. Take the
// following two cases of catch prediction:
//
// try { await fn(); } catch (e) { }
// try { await fn(); } finally { }
//
// When parsing the await that we want to mark as caught or uncaught, it's
// not yet known whether it will be followed by a 'finally' or a 'catch.
// The BytecodeGenerator has learned whether or not this Await is caught or
// not, and is responsible for invoking the correct function depending on
// those findings. It does that here.
if (catch_prediction() == HandlerTable::ASYNC_AWAIT) {
switch (context_index) {
case Context::ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX:
context_index = Context::ASYNC_FUNCTION_AWAIT_UNCAUGHT_INDEX;
break;
case Context::ASYNC_GENERATOR_AWAIT_CAUGHT:
context_index = Context::ASYNC_GENERATOR_AWAIT_UNCAUGHT;
break;
default:
break;
}
}
return context_index;
}
void BytecodeGenerator::VisitCallRuntime(CallRuntime* expr) {
if (expr->is_jsruntime()) {
int context_index =
UpdateRuntimeFunctionForAsyncAwait(expr->context_index());
RegisterList args = register_allocator()->NewGrowableRegisterList();
// Allocate a register for the receiver and load it with undefined.
// TODO(leszeks): If CallJSRuntime always has an undefined receiver, use the
// same mechanism as CallUndefinedReceiver.
BuildPushUndefinedIntoRegisterList(&args);
VisitArguments(expr->arguments(), &args);
builder()->CallJSRuntime(expr->context_index(), args);
builder()->CallJSRuntime(context_index, args);
} else {
// Evaluate all arguments to the runtime call.
RegisterList args = register_allocator()->NewGrowableRegisterList();

View File

@ -214,6 +214,8 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Register* out_register));
void VisitInSameTestExecutionScope(Expression* expr);
int UpdateRuntimeFunctionForAsyncAwait(int context_index);
// Returns the runtime function id for a store to super for the function's
// language mode.
inline Runtime::FunctionId StoreToSuperRuntimeId();
@ -258,6 +260,13 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
inline LanguageMode language_mode() const;
int feedback_index(FeedbackSlot slot) const;
inline HandlerTable::CatchPrediction catch_prediction() const {
return catch_prediction_;
}
inline void set_catch_prediction(HandlerTable::CatchPrediction value) {
catch_prediction_ = value;
}
Zone* zone_;
BytecodeArrayBuilder* builder_;
CompilationInfo* info_;
@ -282,6 +291,8 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Register generator_object_;
Register generator_state_;
int loop_depth_;
HandlerTable::CatchPrediction catch_prediction_;
};
} // namespace interpreter