From 77d612691d79b7c9c8dbc6936b406d1dd4a4ba2b Mon Sep 17 00:00:00 2001 From: mstarzinger Date: Tue, 3 Feb 2015 05:09:40 -0800 Subject: [PATCH] First stab at try-catch and try-finally in TurboFan. R=titzer@chromium.org,jarin@chromium.org TEST=cctest/test-run-jsexceptions Review URL: https://codereview.chromium.org/873423004 Cr-Commit-Position: refs/heads/master@{#26398} --- src/compiler/ast-graph-builder.cc | 399 +++++++++++++++++++++++--- src/compiler/ast-graph-builder.h | 54 +--- src/compiler/control-builders.cc | 57 +++- src/compiler/control-builders.h | 59 +++- src/compiler/graph-builder.h | 6 +- src/compiler/js-generic-lowering.cc | 8 +- src/compiler/js-operator.cc | 2 +- src/flag-definitions.h | 1 + test/cctest/compiler/test-run-jsexceptions.cc | 114 ++++++++ 9 files changed, 611 insertions(+), 89 deletions(-) diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index 08f0fa2..98d80e8 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -21,6 +21,244 @@ namespace internal { namespace compiler { +// Scoped class tracking control statements entered by the visitor. There are +// different types of statements participating in this stack to properly track +// local as well as non-local control flow: +// - IterationStatement : Allows proper 'break' and 'continue' behavior. +// - BreakableStatement : Allows 'break' from block and switch statements. +// - TryCatchStatement : Intercepts 'throw' and implicit exceptional edges. +// - TryFinallyStatement: Intercepts 'break', 'continue', 'throw' and 'return'. +class AstGraphBuilder::ControlScope BASE_EMBEDDED { + public: + ControlScope(AstGraphBuilder* builder, int stack_delta) + : builder_(builder), + next_(builder->execution_control()), + stack_delta_(stack_delta) { + builder_->set_execution_control(this); // Push. + } + + virtual ~ControlScope() { + builder_->set_execution_control(next_); // Pop. + } + + // Either 'break' or 'continue' to the target statement. + void BreakTo(BreakableStatement* target); + void ContinueTo(BreakableStatement* target); + + // Either 'return' or 'throw' the given value. + void ReturnValue(Node* return_value); + void ThrowValue(Node* exception_value); + + class DeferredCommands; + + protected: + enum Command { CMD_BREAK, CMD_CONTINUE, CMD_RETURN, CMD_THROW }; + + // Performs one of the above commands on this stack of control scopes. This + // walks through the stack giving each scope a chance to execute or defer the + // given command by overriding the {Execute} method appropriately. Note that + // this also drops extra operands from the environment for each skipped scope. + void PerformCommand(Command cmd, Statement* target, Node* value); + + // Interface to execute a given command in this scope. Returning {true} here + // indicates successful execution whereas {false} requests to skip scope. + virtual bool Execute(Command cmd, Statement* target, Node* value) { + // For function-level control. + switch (cmd) { + case CMD_THROW: + builder()->BuildThrow(value); + return true; + case CMD_RETURN: + builder()->BuildReturn(value); + return true; + case CMD_BREAK: + case CMD_CONTINUE: + break; + } + return false; + } + + Environment* environment() { return builder_->environment(); } + AstGraphBuilder* builder() const { return builder_; } + int stack_delta() const { return stack_delta_; } + + private: + AstGraphBuilder* builder_; + ControlScope* next_; + int stack_delta_; +}; + + +// Helper class for a try-finally control scope. It can record intercepted +// control-flow commands that cause entry into a finally-block, and re-apply +// them after again leaving that block. Special tokens are used to identify +// paths going through the finally-block to dispatch after leaving the block. +class AstGraphBuilder::ControlScope::DeferredCommands : public ZoneObject { + public: + explicit DeferredCommands(AstGraphBuilder* owner) + : owner_(owner), deferred_(owner->zone()) {} + + // One recorded control-flow command. + struct Entry { + Command command; // The command type being applied on this path. + Statement* statement; // The target statement for the command or {NULL}. + Node* value; // The passed value node for the command or {NULL}. + Node* token; // A token identifying this particular path. + }; + + // Records a control-flow command while entering the finally-block. This also + // generates a new dispatch token that identifies one particular path. + Node* RecordCommand(Command cmd, Statement* stmt, Node* value) { + Node* token = NewPathTokenForDeferredCommand(); + deferred_.push_back({cmd, stmt, value, token}); + return token; + } + + // Returns the dispatch token to be used to identify the implicit fall-through + // path at the end of a try-block into the corresponding finally-block. + Node* GetFallThroughToken() { return NewPathTokenForImplicitFallThrough(); } + + // Applies all recorded control-flow commands after the finally-block again. + // This generates a dynamic dispatch on the token from the entry point. + void ApplyDeferredCommands(Node* token) { + SwitchBuilder dispatch(owner_, static_cast(deferred_.size())); + dispatch.BeginSwitch(); + for (size_t i = 0; i < deferred_.size(); ++i) { + Node* condition = NewPathDispatchCondition(token, deferred_[i].token); + dispatch.BeginLabel(static_cast(i), condition); + dispatch.EndLabel(); + } + for (size_t i = 0; i < deferred_.size(); ++i) { + dispatch.BeginCase(static_cast(i)); + owner_->execution_control()->PerformCommand( + deferred_[i].command, deferred_[i].statement, deferred_[i].value); + dispatch.EndCase(); + } + dispatch.EndSwitch(); + } + + protected: + Node* NewPathTokenForDeferredCommand() { + return owner_->jsgraph()->Constant(static_cast(deferred_.size())); + } + Node* NewPathTokenForImplicitFallThrough() { + return owner_->jsgraph()->Constant(-1); + } + Node* NewPathDispatchCondition(Node* t1, Node* t2) { + // TODO(mstarzinger): This should be machine()->WordEqual(), but our Phi + // nodes all have kRepTagged|kTypeAny, which causes representation mismatch. + return owner_->NewNode(owner_->javascript()->StrictEqual(), t1, t2); + } + + private: + AstGraphBuilder* owner_; + ZoneVector deferred_; +}; + + +// Control scope implementation for a BreakableStatement. +class AstGraphBuilder::ControlScopeForBreakable : public ControlScope { + public: + ControlScopeForBreakable(AstGraphBuilder* owner, BreakableStatement* target, + ControlBuilder* control) + : ControlScope(owner, 0), target_(target), control_(control) {} + + protected: + virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE { + if (target != target_) return false; // We are not the command target. + switch (cmd) { + case CMD_BREAK: + control_->Break(); + return true; + case CMD_CONTINUE: + case CMD_THROW: + case CMD_RETURN: + break; + } + return false; + } + + private: + BreakableStatement* target_; + ControlBuilder* control_; +}; + + +// Control scope implementation for an IterationStatement. +class AstGraphBuilder::ControlScopeForIteration : public ControlScope { + public: + ControlScopeForIteration(AstGraphBuilder* owner, IterationStatement* target, + LoopBuilder* control, int stack_delta) + : ControlScope(owner, stack_delta), target_(target), control_(control) {} + + protected: + virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE { + if (target != target_) return false; // We are not the command target. + switch (cmd) { + case CMD_BREAK: + control_->Break(); + return true; + case CMD_CONTINUE: + control_->Continue(); + return true; + case CMD_THROW: + case CMD_RETURN: + break; + } + return false; + } + + private: + BreakableStatement* target_; + LoopBuilder* control_; +}; + + +// Control scope implementation for a TryCatchStatement. +class AstGraphBuilder::ControlScopeForCatch : public ControlScope { + public: + ControlScopeForCatch(AstGraphBuilder* owner, TryCatchBuilder* control) + : ControlScope(owner, 0), control_(control) {} + + protected: + virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE { + switch (cmd) { + case CMD_THROW: + control_->Throw(value); + return true; + case CMD_BREAK: + case CMD_CONTINUE: + case CMD_RETURN: + break; + } + return false; + } + + private: + TryCatchBuilder* control_; +}; + + +// Control scope implementation for a TryFinallyStatement. +class AstGraphBuilder::ControlScopeForFinally : public ControlScope { + public: + ControlScopeForFinally(AstGraphBuilder* owner, DeferredCommands* commands, + TryFinallyBuilder* control) + : ControlScope(owner, 0), commands_(commands), control_(control) {} + + protected: + virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE { + Node* token = commands_->RecordCommand(cmd, target, value); + control_->LeaveTry(token); + return true; + } + + private: + DeferredCommands* commands_; + TryFinallyBuilder* control_; +}; + + AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info, JSGraph* jsgraph, LoopAssignmentAnalysis* loop) : local_zone_(local_zone), @@ -29,7 +267,7 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info, environment_(nullptr), ast_context_(nullptr), globals_(0, local_zone), - breakable_(nullptr), + execution_control_(nullptr), execution_context_(nullptr), input_buffer_size_(0), input_buffer_(nullptr), @@ -70,9 +308,11 @@ bool AstGraphBuilder::CreateGraph() { int parameter_count = info()->num_parameters(); graph()->SetStart(graph()->NewNode(common()->Start(parameter_count))); - Node* start = graph()->start(); + // Initialize control scope. + ControlScope control(this, 0); + // Initialize the top-level environment. - Environment env(this, scope, start); + Environment env(this, scope, graph()->start()); set_environment(&env); if (info()->is_osr()) { @@ -130,8 +370,7 @@ bool AstGraphBuilder::CreateGraph() { } // Return 'undefined' in case we can fall off the end. - Node* control = NewNode(common()->Return(), jsgraph()->UndefinedConstant()); - UpdateControlDependencyToLeaveFunction(control); + BuildReturn(jsgraph()->UndefinedConstant()); // Finish the basic structure of the graph. environment()->UpdateControlDependency(exit_control()); @@ -302,25 +541,39 @@ Node* AstGraphBuilder::AstTestContext::ConsumeValue() { } -AstGraphBuilder::BreakableScope* AstGraphBuilder::BreakableScope::FindBreakable( - BreakableStatement* target) { - BreakableScope* current = this; - while (current != NULL && current->target_ != target) { - owner_->environment()->Drop(current->drop_extra_); +void AstGraphBuilder::ControlScope::PerformCommand(Command command, + Statement* target, + Node* value) { + Environment* env = environment()->CopyAsUnreachable(); + ControlScope* current = this; + while (current != NULL) { + if (current->Execute(command, target, value)) break; + environment()->Drop(current->stack_delta()); current = current->next_; } - DCHECK(current != NULL); // Always found (unless stack is malformed). - return current; + // TODO(mstarzinger): Unconditionally kill environment once throw is control. + if (command != CMD_THROW) builder()->set_environment(env); + DCHECK(current != NULL); // Always handled (unless stack is malformed). +} + + +void AstGraphBuilder::ControlScope::BreakTo(BreakableStatement* stmt) { + PerformCommand(CMD_BREAK, stmt, nullptr); } -void AstGraphBuilder::BreakableScope::BreakTarget(BreakableStatement* stmt) { - FindBreakable(stmt)->control_->Break(); +void AstGraphBuilder::ControlScope::ContinueTo(BreakableStatement* stmt) { + PerformCommand(CMD_CONTINUE, stmt, nullptr); } -void AstGraphBuilder::BreakableScope::ContinueTarget(BreakableStatement* stmt) { - FindBreakable(stmt)->control_->Continue(); +void AstGraphBuilder::ControlScope::ReturnValue(Node* return_value) { + PerformCommand(CMD_RETURN, nullptr, return_value); +} + + +void AstGraphBuilder::ControlScope::ThrowValue(Node* exception_value) { + PerformCommand(CMD_THROW, nullptr, exception_value); } @@ -483,7 +736,7 @@ void AstGraphBuilder::VisitModuleUrl(ModuleUrl* modl) { UNREACHABLE(); } void AstGraphBuilder::VisitBlock(Block* stmt) { BlockBuilder block(this); - BreakableScope scope(this, stmt, &block, 0); + ControlScopeForBreakable scope(this, stmt, &block); if (stmt->labels() != NULL) block.BeginBlock(); if (stmt->scope() == NULL) { // Visit statements in the same scope, no declarations. @@ -528,24 +781,19 @@ void AstGraphBuilder::VisitIfStatement(IfStatement* stmt) { void AstGraphBuilder::VisitContinueStatement(ContinueStatement* stmt) { - Environment* env = environment()->CopyAsUnreachable(); - breakable()->ContinueTarget(stmt->target()); - set_environment(env); + execution_control()->ContinueTo(stmt->target()); } void AstGraphBuilder::VisitBreakStatement(BreakStatement* stmt) { - Environment* env = environment()->CopyAsUnreachable(); - breakable()->BreakTarget(stmt->target()); - set_environment(env); + execution_control()->BreakTo(stmt->target()); } void AstGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) { VisitForValue(stmt->expression()); Node* result = environment()->Pop(); - Node* control = NewNode(common()->Return(), result); - UpdateControlDependencyToLeaveFunction(control); + execution_control()->ReturnValue(result); } @@ -563,7 +811,7 @@ void AstGraphBuilder::VisitWithStatement(WithStatement* stmt) { void AstGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) { ZoneList* clauses = stmt->cases(); SwitchBuilder compare_switch(this, clauses->length()); - BreakableScope scope(this, stmt, &compare_switch, 0); + ControlScopeForBreakable scope(this, stmt, &compare_switch); compare_switch.BeginSwitch(); int default_index = -1; @@ -816,14 +1064,70 @@ void AstGraphBuilder::VisitForOfStatement(ForOfStatement* stmt) { void AstGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) { - // TODO(turbofan): Implement try-catch here. - SetStackOverflow(); + TryCatchBuilder try_control(this); + + // Evaluate the try-block inside a control scope. This simulates a handler + // that is intercepting 'throw' control commands. + try_control.BeginTry(); + { + ControlScopeForCatch scope(this, &try_control); + Visit(stmt->try_block()); + } + try_control.EndTry(); + + // Create a catch scope that binds the exception. + Node* exception = try_control.GetExceptionNode(); + if (exception == NULL) exception = jsgraph()->NullConstant(); + Unique name = MakeUnique(stmt->variable()->name()); + const Operator* op = javascript()->CreateCatchContext(name); + Node* context = NewNode(op, exception, GetFunctionClosure()); + PrepareFrameState(context, BailoutId::None()); + ContextScope scope(this, stmt->scope(), context); + DCHECK(stmt->scope()->declarations()->is_empty()); + + // Evaluate the catch-block. + Visit(stmt->catch_block()); + try_control.EndCatch(); + + // TODO(mstarzinger): Remove bailout once everything works. + if (!FLAG_turbo_exceptions) SetStackOverflow(); } void AstGraphBuilder::VisitTryFinallyStatement(TryFinallyStatement* stmt) { - // TODO(turbofan): Implement try-catch here. - SetStackOverflow(); + TryFinallyBuilder try_control(this); + + // 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 + // finally-block have been evaluated. + // + // The try-finally construct can enter the finally-block in three ways: + // 1. By exiting the try-block normally, falling through at the end. + // 2. By exiting the try-block with a function-local control flow transfer + // (i.e. through break/continue/return statements). + // 3. By exiting the try-block with a thrown exception. + ControlScope::DeferredCommands* commands = + new (zone()) ControlScope::DeferredCommands(this); + + // Evaluate the try-block inside a control scope. This simulates a handler + // that is intercepting all control commands. + try_control.BeginTry(); + { + ControlScopeForFinally scope(this, commands, &try_control); + Visit(stmt->try_block()); + } + try_control.EndTry(commands->GetFallThroughToken()); + + // Evaluate the finally-block. + Visit(stmt->finally_block()); + try_control.EndFinally(); + + // Dynamic dispatch after the finally-block. + Node* token = try_control.GetDispatchTokenNode(); + commands->ApplyDeferredCommands(token); + + // TODO(mstarzinger): Remove bailout once everything works. + if (!FLAG_turbo_exceptions) SetStackOverflow(); } @@ -1387,10 +1691,16 @@ void AstGraphBuilder::VisitYield(Yield* expr) { void AstGraphBuilder::VisitThrow(Throw* expr) { VisitForValue(expr->exception()); Node* exception = environment()->Pop(); - const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1); - Node* value = NewNode(op, exception); - PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine()); - ast_context()->ProduceValue(value); + if (FLAG_turbo_exceptions) { + execution_control()->ThrowValue(exception); + ast_context()->ProduceValue(exception); + } else { + // TODO(mstarzinger): Temporary workaround for bailout-id for debugger. + const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1); + Node* value = NewNode(op, exception); + PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine()); + ast_context()->ProduceValue(value); + } } @@ -1832,8 +2142,8 @@ void AstGraphBuilder::VisitIfNotNull(Statement* stmt) { void AstGraphBuilder::VisitIterationBody(IterationStatement* stmt, - LoopBuilder* loop, int drop_extra) { - BreakableScope scope(this, stmt, loop, drop_extra); + LoopBuilder* loop, int stack_delta) { + ControlScopeForIteration scope(this, stmt, loop, stack_delta); Visit(stmt->body()); } @@ -2360,6 +2670,23 @@ Node* AstGraphBuilder::BuildThrowConstAssignError(BailoutId bailout_id) { } +Node* AstGraphBuilder::BuildReturn(Node* return_value) { + Node* control = NewNode(common()->Return(), return_value); + UpdateControlDependencyToLeaveFunction(control); + return control; +} + + +Node* AstGraphBuilder::BuildThrow(Node* exception_value) { + const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1); + Node* control = NewNode(op, exception_value); + // TODO(mstarzinger): Thread through the correct bailout id to this point. + // PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine()); + PrepareFrameState(control, BailoutId::None()); + return control; +} + + Node* AstGraphBuilder::BuildBinaryOp(Node* left, Node* right, Token::Value op) { const Operator* js_op; switch (op) { diff --git a/src/compiler/ast-graph-builder.h b/src/compiler/ast-graph-builder.h index 07a2a06..390ab23 100644 --- a/src/compiler/ast-graph-builder.h +++ b/src/compiler/ast-graph-builder.h @@ -63,8 +63,12 @@ class AstGraphBuilder : public AstVisitor { class AstEffectContext; class AstValueContext; class AstTestContext; - class BreakableScope; class ContextScope; + class ControlScope; + class ControlScopeForBreakable; + class ControlScopeForIteration; + class ControlScopeForCatch; + class ControlScopeForFinally; class Environment; friend class ControlBuilder; @@ -77,8 +81,8 @@ class AstGraphBuilder : public AstVisitor { // List of global declarations for functions and variables. ZoneVector> globals_; - // Stack of breakable statements entered by the visitor. - BreakableScope* breakable_; + // Stack of control scopes currently entered by the visitor. + ControlScope* execution_control_; // Stack of context objects pushed onto the chain by the visitor. ContextScope* execution_context_; @@ -110,7 +114,7 @@ class AstGraphBuilder : public AstVisitor { Zone* local_zone() const { return local_zone_; } Environment* environment() { return environment_; } AstContext* ast_context() const { return ast_context_; } - BreakableScope* breakable() const { return breakable_; } + ControlScope* execution_control() const { return execution_control_; } ContextScope* execution_context() const { return execution_context_; } CommonOperatorBuilder* common() const { return jsgraph_->common(); } CompilationInfo* info() const { return info_; } @@ -126,7 +130,7 @@ class AstGraphBuilder : public AstVisitor { Node* exit_control() const { return exit_control_; } void set_ast_context(AstContext* ctx) { ast_context_ = ctx; } - void set_breakable(BreakableScope* brk) { breakable_ = brk; } + void set_execution_control(ControlScope* ctrl) { execution_control_ = ctrl; } void set_execution_context(ContextScope* ctx) { execution_context_ = ctx; } void set_exit_control(Node* exit) { exit_control_ = exit; } void set_current_context(Node* ctx) { current_context_ = ctx; } @@ -237,6 +241,10 @@ class AstGraphBuilder : public AstVisitor { Node* BuildHoleCheckThrow(Node* value, Variable* var, Node* not_hole, BailoutId bailout_id); + // Builders for non-local control flow. + Node* BuildReturn(Node* return_value); + Node* BuildThrow(Node* exception_value); + // Builders for binary operations. Node* BuildBinaryOp(Node* left, Node* right, Token::Value op); @@ -529,42 +537,6 @@ class AstGraphBuilder::AstTestContext FINAL : public AstContext { }; -// Scoped class tracking breakable statements entered by the visitor. Allows to -// properly 'break' and 'continue' iteration statements as well as to 'break' -// from blocks within switch statements. -class AstGraphBuilder::BreakableScope BASE_EMBEDDED { - public: - BreakableScope(AstGraphBuilder* owner, BreakableStatement* target, - ControlBuilder* control, int drop_extra) - : owner_(owner), - target_(target), - next_(owner->breakable()), - control_(control), - drop_extra_(drop_extra) { - owner_->set_breakable(this); // Push. - } - - ~BreakableScope() { - owner_->set_breakable(next_); // Pop. - } - - // Either 'break' or 'continue' the target statement. - void BreakTarget(BreakableStatement* target); - void ContinueTarget(BreakableStatement* target); - - private: - AstGraphBuilder* owner_; - BreakableStatement* target_; - BreakableScope* next_; - ControlBuilder* control_; - int drop_extra_; - - // Find the correct scope for the target statement. Note that this also drops - // extra operands from the environment for each scope skipped along the way. - BreakableScope* FindBreakable(BreakableStatement* target); -}; - - // Scoped class tracking context objects created by the visitor. Represents // mutations of the context chain within the function body and allows to // change the current {scope} and {context} during visitation. diff --git a/src/compiler/control-builders.cc b/src/compiler/control-builders.cc index 6cf2905..6dba2d3 100644 --- a/src/compiler/control-builders.cc +++ b/src/compiler/control-builders.cc @@ -147,6 +147,61 @@ void BlockBuilder::EndBlock() { break_environment_->Merge(environment()); set_environment(break_environment_); } + + +void TryCatchBuilder::BeginTry() { + catch_environment_ = environment()->CopyAsUnreachable(); + catch_environment_->Push(nullptr); +} + + +void TryCatchBuilder::Throw(Node* exception) { + environment()->Push(exception); + catch_environment_->Merge(environment()); + environment()->Pop(); + environment()->MarkAsUnreachable(); +} + + +void TryCatchBuilder::EndTry() { + exit_environment_ = environment(); + exception_node_ = catch_environment_->Pop(); + set_environment(catch_environment_); +} + + +void TryCatchBuilder::EndCatch() { + exit_environment_->Merge(environment()); + set_environment(exit_environment_); } + + +void TryFinallyBuilder::BeginTry() { + finally_environment_ = environment()->CopyAsUnreachable(); + finally_environment_->Push(nullptr); +} + + +void TryFinallyBuilder::LeaveTry(Node* token) { + environment()->Push(token); + finally_environment_->Merge(environment()); + environment()->Pop(); +} + + +void TryFinallyBuilder::EndTry(Node* fallthrough_token) { + environment()->Push(fallthrough_token); + finally_environment_->Merge(environment()); + environment()->Pop(); + token_node_ = finally_environment_->Pop(); + set_environment(finally_environment_); +} + + +void TryFinallyBuilder::EndFinally() { + // Nothing to be done here. } -} // namespace v8::internal::compiler + +} // namespace compiler +} // namespace internal +} // namespace v8 diff --git a/src/compiler/control-builders.h b/src/compiler/control-builders.h index 7ab12d8..d190f61 100644 --- a/src/compiler/control-builders.h +++ b/src/compiler/control-builders.h @@ -15,16 +15,15 @@ namespace internal { namespace compiler { // Base class for all control builders. Also provides a common interface for -// control builders to handle 'break' and 'continue' statements when they are -// used to model breakable statements. +// control builders to handle 'break' statements when they are used to model +// breakable statements. class ControlBuilder { public: explicit ControlBuilder(AstGraphBuilder* builder) : builder_(builder) {} virtual ~ControlBuilder() {} - // Interface for break and continue. + // Interface for break. virtual void Break() { UNREACHABLE(); } - virtual void Continue() { UNREACHABLE(); } protected: typedef AstGraphBuilder Builder; @@ -69,11 +68,11 @@ class LoopBuilder FINAL : public ControlBuilder { // Primitive control commands. void BeginLoop(BitVector* assigned, bool is_osr = false); + void Continue(); void EndBody(); void EndLoop(); - // Primitive support for break and continue. - void Continue() FINAL; + // Primitive support for break. void Break() FINAL; // Compound control commands for conditional break. @@ -137,6 +136,54 @@ class BlockBuilder FINAL : public ControlBuilder { Environment* break_environment_; // Environment after the block exits. }; + +// Tracks control flow for a try-catch statement. +class TryCatchBuilder FINAL : public ControlBuilder { + public: + explicit TryCatchBuilder(AstGraphBuilder* builder) + : ControlBuilder(builder), + catch_environment_(NULL), + exit_environment_(NULL), + exception_node_(NULL) {} + + // Primitive control commands. + void BeginTry(); + void Throw(Node* exception); + void EndTry(); + void EndCatch(); + + // Returns the exception value inside the 'catch' body. + Node* GetExceptionNode() const { return exception_node_; } + + private: + Environment* catch_environment_; // Environment for the 'catch' body. + Environment* exit_environment_; // Environment after the statement. + Node* exception_node_; // Node for exception in 'catch' body. +}; + + +// Tracks control flow for a try-finally statement. +class TryFinallyBuilder FINAL : public ControlBuilder { + public: + explicit TryFinallyBuilder(AstGraphBuilder* builder) + : ControlBuilder(builder), + finally_environment_(NULL), + token_node_(NULL) {} + + // Primitive control commands. + void BeginTry(); + void LeaveTry(Node* token); + void EndTry(Node* token); + void EndFinally(); + + // Returns the dispatch token value inside the 'finally' body. + Node* GetDispatchTokenNode() const { return token_node_; } + + private: + Environment* finally_environment_; // Environment for the 'finally' body. + Node* token_node_; // Node for token in 'finally' body. +}; + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/src/compiler/graph-builder.h b/src/compiler/graph-builder.h index 947c576..02d6ff0 100644 --- a/src/compiler/graph-builder.h +++ b/src/compiler/graph-builder.h @@ -77,8 +77,8 @@ class GraphBuilder { Graph* graph_; }; -} -} -} // namespace v8::internal::compiler +} // namespace compiler +} // namespace internal +} // namespace v8 #endif // V8_COMPILER_GRAPH_BUILDER_H__ diff --git a/src/compiler/js-generic-lowering.cc b/src/compiler/js-generic-lowering.cc index 797ddf9..c6f9011 100644 --- a/src/compiler/js-generic-lowering.cc +++ b/src/compiler/js-generic-lowering.cc @@ -104,7 +104,6 @@ REPLACE_COMPARE_IC_CALL(JSGreaterThanOrEqual, Token::GTE) REPLACE_RUNTIME_CALL(JSTypeOf, Runtime::kTypeof) REPLACE_RUNTIME_CALL(JSCreate, Runtime::kAbort) REPLACE_RUNTIME_CALL(JSCreateFunctionContext, Runtime::kNewFunctionContext) -REPLACE_RUNTIME_CALL(JSCreateCatchContext, Runtime::kPushCatchContext) REPLACE_RUNTIME_CALL(JSCreateWithContext, Runtime::kPushWithContext) REPLACE_RUNTIME_CALL(JSCreateBlockContext, Runtime::kPushBlockContext) REPLACE_RUNTIME_CALL(JSCreateModuleContext, Runtime::kPushModuleContext) @@ -364,6 +363,13 @@ void JSGenericLowering::LowerJSStoreContext(Node* node) { } +void JSGenericLowering::LowerJSCreateCatchContext(Node* node) { + Unique name = OpParameter>(node); + PatchInsertInput(node, 0, jsgraph()->HeapConstant(name)); + ReplaceWithRuntimeCall(node, Runtime::kPushCatchContext); +} + + void JSGenericLowering::LowerJSCallConstruct(Node* node) { int arity = OpParameter(node); CallConstructStub stub(isolate(), NO_CALL_CONSTRUCTOR_FLAGS); diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index aa76a3b..195bdbc 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -402,7 +402,7 @@ const Operator* JSOperatorBuilder::CreateCatchContext( return new (zone()) Operator1>( // -- IrOpcode::kJSCreateCatchContext, Operator::kNoProperties, // opcode "JSCreateCatchContext", // name - 1, 1, 1, 1, 1, 0, // counts + 2, 1, 1, 1, 1, 0, // counts name); // parameter } diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 5ccd47f..972cd43 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -416,6 +416,7 @@ DEFINE_BOOL(turbo_verify_allocation, DEBUG_BOOL, DEFINE_BOOL(turbo_move_optimization, true, "optimize gap moves in TurboFan") DEFINE_BOOL(turbo_jt, true, "enable jump threading in TurboFan") DEFINE_BOOL(turbo_osr, false, "enable OSR in TurboFan") +DEFINE_BOOL(turbo_exceptions, false, "enable exception handling in TurboFan") DEFINE_BOOL(turbo_stress_loop_peeling, false, "stress loop peeling optimization") diff --git a/test/cctest/compiler/test-run-jsexceptions.cc b/test/cctest/compiler/test-run-jsexceptions.cc index 0712ab6..43ce91b 100644 --- a/test/cctest/compiler/test-run-jsexceptions.cc +++ b/test/cctest/compiler/test-run-jsexceptions.cc @@ -43,3 +43,117 @@ TEST(ThrowSourcePosition) { CHECK_EQ(4, message->GetLineNumber()); CHECK_EQ(95, message->GetStartPosition()); } + + +// TODO(mstarzinger): Increase test coverage by having similar tests within the +// mjsunit suite to also test integration with other components (e.g. OSR). + + +TEST(Catch) { + i::FLAG_turbo_exceptions = true; + const char* src = + "(function(a,b) {" + " var r = '-';" + " try {" + " r += 'A-';" + " throw 'B-';" + " } catch (e) {" + " r += e;" + " }" + " return r;" + "})"; + FunctionTester T(src); + + T.CheckCall(T.Val("-A-B-")); +} + + +TEST(CatchNested) { + i::FLAG_turbo_exceptions = true; + const char* src = + "(function(a,b) {" + " var r = '-';" + " try {" + " r += 'A-';" + " throw 'C-';" + " } catch (e) {" + " try {" + " throw 'B-';" + " } catch (e) {" + " r += e;" + " }" + " r += e;" + " }" + " return r;" + "})"; + FunctionTester T(src); + + T.CheckCall(T.Val("-A-B-C-")); +} + + +TEST(CatchBreak) { + i::FLAG_turbo_exceptions = true; + const char* src = + "(function(a,b) {" + " var r = '-';" + " L: try {" + " r += 'A-';" + " if (a) break L;" + " r += 'B-';" + " throw 'C-';" + " } catch (e) {" + " if (b) break L;" + " r += e;" + " }" + " r += 'D-';" + " return r;" + "})"; + FunctionTester T(src); + + T.CheckCall(T.Val("-A-D-"), T.true_value(), T.false_value()); + T.CheckCall(T.Val("-A-B-D-"), T.false_value(), T.true_value()); + T.CheckCall(T.Val("-A-B-C-D-"), T.false_value(), T.false_value()); +} + + +TEST(Finally) { + i::FLAG_turbo_exceptions = true; + const char* src = + "(function(a,b) {" + " var r = '-';" + " try {" + " r += 'A-';" + " } finally {" + " r += 'B-';" + " }" + " return r;" + "})"; + FunctionTester T(src); + + T.CheckCall(T.Val("-A-B-")); +} + + +TEST(FinallyBreak) { + i::FLAG_turbo_exceptions = true; + const char* src = + "(function(a,b) {" + " var r = '-';" + " L: try {" + " r += 'A-';" + " if (a) return r;" + " r += 'B-';" + " if (b) break L;" + " r += 'C-';" + " } finally {" + " r += 'D-';" + " }" + " return r;" + "})"; + FunctionTester T(src); + + T.CheckCall(T.Val("-A-"), T.true_value(), T.false_value()); + T.CheckCall(T.Val("-A-B-D-"), T.false_value(), T.true_value()); + T.CheckCall(T.Val("-A-B-C-D-"), T.false_value(), T.false_value()); +} -- 2.7.4