From 3161cb550c8c44560ed7f1ee70f2cc989a8b32d8 Mon Sep 17 00:00:00 2001 From: mstarzinger Date: Tue, 16 Jun 2015 22:40:28 -0700 Subject: [PATCH] [turbofan] Ensure lazy bailout point in exception handler. This ensures there is a lazy bailout point at the entry of every exception handler so that deoptimized code is not re-entered through caught exceptions. R=jarin@chromium.org TEST=cctest/test-run-deopt/DeoptExceptionHandler Review URL: https://codereview.chromium.org/1173253004 Cr-Commit-Position: refs/heads/master@{#29061} --- src/ast-numbering.cc | 2 + src/ast.h | 17 ++++- src/compiler/ast-graph-builder.cc | 18 ++++- src/compiler/code-generator.cc | 2 + src/compiler/instruction.cc | 7 +- src/compiler/instruction.h | 4 +- src/full-codegen.cc | 5 +- test/cctest/compiler/test-jump-threading.cc | 2 +- test/cctest/compiler/test-run-deopt.cc | 75 +++++++++++++++---- .../compiler/instruction-sequence-unittest.cc | 4 +- 10 files changed, 108 insertions(+), 28 deletions(-) diff --git a/src/ast-numbering.cc b/src/ast-numbering.cc index 2a5c5ac65..ce8299888 100644 --- a/src/ast-numbering.cc +++ b/src/ast-numbering.cc @@ -296,6 +296,7 @@ void AstNumberingVisitor::VisitWhileStatement(WhileStatement* node) { void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) { IncrementNodeCount(); DisableOptimization(kTryCatchStatement); + node->set_base_id(ReserveIdRange(TryCatchStatement::num_ids())); Visit(node->try_block()); Visit(node->catch_block()); } @@ -304,6 +305,7 @@ void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) { void AstNumberingVisitor::VisitTryFinallyStatement(TryFinallyStatement* node) { IncrementNodeCount(); DisableOptimization(kTryFinallyStatement); + node->set_base_id(ReserveIdRange(TryFinallyStatement::num_ids())); Visit(node->try_block()); Visit(node->finally_block()); } diff --git a/src/ast.h b/src/ast.h index 0dc9e6066..7bdd520bb 100644 --- a/src/ast.h +++ b/src/ast.h @@ -1163,12 +1163,27 @@ class TryStatement : public Statement { public: Block* try_block() const { return try_block_; } + void set_base_id(int id) { base_id_ = id; } + static int num_ids() { return parent_num_ids() + 1; } + BailoutId HandlerId() const { return BailoutId(local_id(0)); } + protected: TryStatement(Zone* zone, Block* try_block, int pos) - : Statement(zone, pos), try_block_(try_block) {} + : Statement(zone, pos), + try_block_(try_block), + base_id_(BailoutId::None().ToInt()) {} + static int parent_num_ids() { return 0; } + + int base_id() const { + DCHECK(!BailoutId(base_id_).IsNone()); + return base_id_; + } private: + int local_id(int n) const { return base_id() + parent_num_ids() + n; } + Block* try_block_; + int base_id_; }; diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index cf7bbc99a..167d285e6 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -1415,16 +1415,21 @@ void AstGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) { } try_control.EndTry(); + // TODO(mstarzinger): We are only using a runtime call to get a lazy bailout + // point, there is no need to really emit an actual call. Optimize this! + Node* guard = NewNode(javascript()->CallRuntime(Runtime::kMaxSmi, 0)); + PrepareFrameState(guard, stmt->HandlerId()); + + // Clear message object as we enter the catch block. + Node* the_hole = jsgraph()->TheHoleConstant(); + BuildStoreExternal(message_object, kMachAnyTagged, the_hole); + // Create a catch scope that binds the exception. Node* exception = try_control.GetExceptionNode(); Unique name = MakeUnique(stmt->variable()->name()); const Operator* op = javascript()->CreateCatchContext(name); Node* context = NewNode(op, exception, GetFunctionClosureForContext()); - // Clear message object as we enter the catch block. - Node* the_hole = jsgraph()->TheHoleConstant(); - BuildStoreExternal(message_object, kMachAnyTagged, the_hole); - // Evaluate the catch-block. VisitInScope(stmt->catch_block(), stmt->scope(), context); try_control.EndCatch(); @@ -1464,6 +1469,11 @@ void AstGraphBuilder::VisitTryFinallyStatement(TryFinallyStatement* stmt) { } try_control.EndTry(commands->GetFallThroughToken(), fallthrough_result); + // TODO(mstarzinger): We are only using a runtime call to get a lazy bailout + // point, there is no need to really emit an actual call. Optimize this! + Node* guard = NewNode(javascript()->CallRuntime(Runtime::kMaxSmi, 0)); + PrepareFrameState(guard, stmt->HandlerId()); + // The result value semantics depend on how the block was entered: // - ReturnStatement: It represents the return value being returned. // - ThrowStatement: It represents the exception being thrown. diff --git a/src/compiler/code-generator.cc b/src/compiler/code-generator.cc index 374e67743..1854c82cf 100644 --- a/src/compiler/code-generator.cc +++ b/src/compiler/code-generator.cc @@ -92,6 +92,8 @@ Handle CodeGenerator::GenerateCode() { } // Align loop headers on 16-byte boundaries. if (block->IsLoopHeader()) masm()->Align(16); + // Ensure lazy deopt doesn't patch handler entry points. + if (block->IsHandler()) EnsureSpaceForLazyDeopt(); // Bind a label for a block. current_block_ = block->rpo_number(); if (FLAG_code_comments) { diff --git a/src/compiler/instruction.cc b/src/compiler/instruction.cc index 29a31e047..815e91e93 100644 --- a/src/compiler/instruction.cc +++ b/src/compiler/instruction.cc @@ -403,7 +403,7 @@ void PhiInstruction::SetInput(size_t offset, int virtual_register) { InstructionBlock::InstructionBlock(Zone* zone, RpoNumber rpo_number, RpoNumber loop_header, RpoNumber loop_end, - bool deferred) + bool deferred, bool handler) : successors_(zone), predecessors_(zone), phis_(zone), @@ -414,6 +414,7 @@ InstructionBlock::InstructionBlock(Zone* zone, RpoNumber rpo_number, code_start_(-1), code_end_(-1), deferred_(deferred), + handler_(handler), needs_frame_(false), must_construct_frame_(false), must_deconstruct_frame_(false) {} @@ -443,9 +444,11 @@ static RpoNumber GetLoopEndRpo(const BasicBlock* block) { static InstructionBlock* InstructionBlockFor(Zone* zone, const BasicBlock* block) { + bool is_handler = + !block->empty() && block->front()->opcode() == IrOpcode::kIfException; InstructionBlock* instr_block = new (zone) InstructionBlock(zone, GetRpo(block), GetRpo(block->loop_header()), - GetLoopEndRpo(block), block->deferred()); + GetLoopEndRpo(block), block->deferred(), is_handler); // Map successors and precessors instr_block->successors().reserve(block->SuccessorCount()); for (BasicBlock* successor : block->successors()) { diff --git a/src/compiler/instruction.h b/src/compiler/instruction.h index eff5099f9..7f202135a 100644 --- a/src/compiler/instruction.h +++ b/src/compiler/instruction.h @@ -930,7 +930,7 @@ class PhiInstruction final : public ZoneObject { class InstructionBlock final : public ZoneObject { public: InstructionBlock(Zone* zone, RpoNumber rpo_number, RpoNumber loop_header, - RpoNumber loop_end, bool deferred); + RpoNumber loop_end, bool deferred, bool handler); // Instruction indexes (used by the register allocator). int first_instruction_index() const { @@ -953,6 +953,7 @@ class InstructionBlock final : public ZoneObject { void set_code_end(int32_t end) { code_end_ = end; } bool IsDeferred() const { return deferred_; } + bool IsHandler() const { return handler_; } RpoNumber ao_number() const { return ao_number_; } RpoNumber rpo_number() const { return rpo_number_; } @@ -1000,6 +1001,7 @@ class InstructionBlock final : public ZoneObject { int32_t code_start_; // start index of arch-specific code. int32_t code_end_; // end index of arch-specific code. const bool deferred_; // Block contains deferred code. + const bool handler_; // Block is a handler entry point. bool needs_frame_; bool must_construct_frame_; bool must_deconstruct_frame_; diff --git a/src/full-codegen.cc b/src/full-codegen.cc index 16f0b5578..f1b3904da 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -1226,8 +1226,9 @@ void FullCodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { Label try_entry, handler_entry, exit; __ jmp(&try_entry); __ bind(&handler_entry); - + PrepareForBailoutForId(stmt->HandlerId(), NO_REGISTERS); ClearPendingMessage(); + // Exception handler code, the exception is in the result register. // Extend the context before executing the catch block. { Comment cmnt(masm_, "[ Extend catch context"); @@ -1295,6 +1296,8 @@ void FullCodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { // Jump to try-handler setup and try-block code. __ jmp(&try_entry); __ bind(&handler_entry); + PrepareForBailoutForId(stmt->HandlerId(), NO_REGISTERS); + // Exception handler code. This code is only executed when an exception // is thrown. The exception is in the result register, and must be // preserved by the finally block. Call the finally block and then diff --git a/test/cctest/compiler/test-jump-threading.cc b/test/cctest/compiler/test-jump-threading.cc index e0a4a2fc6..9fa3c9f28 100644 --- a/test/cctest/compiler/test-jump-threading.cc +++ b/test/cctest/compiler/test-jump-threading.cc @@ -85,7 +85,7 @@ class TestCode : public HandleAndZoneScope { if (current_ == NULL) { current_ = new (main_zone()) InstructionBlock(main_zone(), rpo_number_, RpoNumber::Invalid(), - RpoNumber::Invalid(), deferred); + RpoNumber::Invalid(), deferred, false); blocks_.push_back(current_); sequence_.StartBlock(rpo_number_); } diff --git a/test/cctest/compiler/test-run-deopt.cc b/test/cctest/compiler/test-run-deopt.cc index 71883e2bd..b08185f38 100644 --- a/test/cctest/compiler/test-run-deopt.cc +++ b/test/cctest/compiler/test-run-deopt.cc @@ -27,47 +27,90 @@ static void InstallIsOptimizedHelper(v8::Isolate* isolate) { } -TEST(TurboSimpleDeopt) { +TEST(DeoptSimple) { FLAG_allow_natives_syntax = true; FunctionTester T( "(function f(a) {" - "var b = 1;" - "if (!IsOptimized()) return 0;" - "%DeoptimizeFunction(f);" - "if (IsOptimized()) return 0;" - "return a + b; })"); + " var b = 1;" + " if (!IsOptimized()) return 0;" + " %DeoptimizeFunction(f);" + " if (IsOptimized()) return 0;" + " return a + b;" + "})"); InstallIsOptimizedHelper(CcTest::isolate()); T.CheckCall(T.Val(2), T.Val(1)); } -TEST(TurboSimpleDeoptInExpr) { +TEST(DeoptSimpleInExpr) { FLAG_allow_natives_syntax = true; FunctionTester T( "(function f(a) {" - "var b = 1;" - "var c = 2;" - "if (!IsOptimized()) return 0;" - "var d = b + (%DeoptimizeFunction(f), c);" - "if (IsOptimized()) return 0;" - "return d + a; })"); + " var b = 1;" + " var c = 2;" + " if (!IsOptimized()) return 0;" + " var d = b + (%DeoptimizeFunction(f), c);" + " if (IsOptimized()) return 0;" + " return d + a;" + "})"); InstallIsOptimizedHelper(CcTest::isolate()); T.CheckCall(T.Val(6), T.Val(3)); } + +TEST(DeoptExceptionHandlerCatch) { + FLAG_allow_natives_syntax = true; + FLAG_turbo_try_catch = true; + + FunctionTester T( + "(function f() {" + " var is_opt = IsOptimized;" + " try {" + " DeoptAndThrow(f);" + " } catch (e) {" + " return is_opt();" + " }" + "})"); + + CompileRun("function DeoptAndThrow(f) { %DeoptimizeFunction(f); throw 0; }"); + InstallIsOptimizedHelper(CcTest::isolate()); + T.CheckCall(T.false_value()); +} + + +TEST(DeoptExceptionHandlerFinally) { + FLAG_allow_natives_syntax = true; + FLAG_turbo_try_finally = true; + + FunctionTester T( + "(function f() {" + " var is_opt = IsOptimized;" + " try {" + " DeoptAndThrow(f);" + " } finally {" + " return is_opt();" + " }" + "})"); + + CompileRun("function DeoptAndThrow(f) { %DeoptimizeFunction(f); throw 0; }"); + InstallIsOptimizedHelper(CcTest::isolate()); + T.CheckCall(T.false_value()); +} + #endif -TEST(TurboTrivialDeopt) { +TEST(DeoptTrivial) { FLAG_allow_natives_syntax = true; FunctionTester T( "(function foo() {" - "%DeoptimizeFunction(foo);" - "return 1; })"); + " %DeoptimizeFunction(foo);" + " return 1;" + "})"); T.CheckCall(T.Val(1)); } diff --git a/test/unittests/compiler/instruction-sequence-unittest.cc b/test/unittests/compiler/instruction-sequence-unittest.cc index f659b0788..1ff441f74 100644 --- a/test/unittests/compiler/instruction-sequence-unittest.cc +++ b/test/unittests/compiler/instruction-sequence-unittest.cc @@ -429,8 +429,8 @@ InstructionBlock* InstructionSequenceTest::NewBlock() { } } // Construct instruction block. - auto instruction_block = - new (zone()) InstructionBlock(zone(), rpo, loop_header, loop_end, false); + auto instruction_block = new (zone()) + InstructionBlock(zone(), rpo, loop_header, loop_end, false, false); instruction_blocks_.push_back(instruction_block); current_block_ = instruction_block; sequence()->StartBlock(rpo); -- 2.34.1