Index: test/cctest/compiler/test-codegen-deopt.cc |
diff --git a/test/cctest/compiler/test-codegen-deopt.cc b/test/cctest/compiler/test-codegen-deopt.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..243ece901080f4a76b5b701b7dd327cc98f85cb7 |
--- /dev/null |
+++ b/test/cctest/compiler/test-codegen-deopt.cc |
@@ -0,0 +1,331 @@ |
+// Copyright 2014 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. |
+ |
+#include "src/v8.h" |
+#include "test/cctest/cctest.h" |
+ |
+#include "src/compiler/code-generator.h" |
+#include "src/compiler/common-operator.h" |
+#include "src/compiler/graph.h" |
+#include "src/compiler/instruction-selector.h" |
+#include "src/compiler/machine-operator.h" |
+#include "src/compiler/node.h" |
+#include "src/compiler/operator.h" |
+#include "src/compiler/raw-machine-assembler.h" |
+#include "src/compiler/register-allocator.h" |
+#include "src/compiler/schedule.h" |
+ |
+#include "src/full-codegen.h" |
+#include "src/parser.h" |
+#include "src/rewriter.h" |
+ |
+#include "test/cctest/compiler/function-tester.h" |
+ |
+using namespace v8::internal; |
+using namespace v8::internal::compiler; |
+ |
+typedef RawMachineAssembler::Label MLabel; |
+ |
+static Handle<JSFunction> NewFunction(const char* source) { |
+ return v8::Utils::OpenHandle( |
+ *v8::Handle<v8::Function>::Cast(CompileRun(source))); |
+} |
+ |
+ |
+class DeoptCodegenTester { |
+ public: |
+ explicit DeoptCodegenTester(HandleAndZoneScope* scope, const char* src) |
+ : scope_(scope), |
+ function(NewFunction(src)), |
+ info(function, scope->main_zone()), |
+ bailout_id(-1) { |
+ CHECK(Parser::Parse(&info)); |
+ StrictMode strict_mode = info.function()->strict_mode(); |
+ info.SetStrictMode(strict_mode); |
+ info.SetOptimizing(BailoutId::None(), Handle<Code>(function->code())); |
+ CHECK(Rewriter::Rewrite(&info)); |
+ CHECK(Scope::Analyze(&info)); |
+ CHECK_NE(NULL, info.scope()); |
+ |
+ FunctionTester::EnsureDeoptimizationSupport(&info); |
+ |
+ ASSERT(info.shared_info()->has_deoptimization_support()); |
+ |
+ graph = new (scope_->main_zone()) Graph(scope_->main_zone()); |
+ } |
+ |
+ virtual ~DeoptCodegenTester() { delete code; } |
+ |
+ void GenerateCodeFromSchedule(Schedule* schedule) { |
+ OFStream os(stdout); |
+ os << *schedule; |
+ |
+ // Initialize the codegen and generate code. |
+ Linkage* linkage = new (scope_->main_zone()) Linkage(&info); |
+ code = new v8::internal::compiler::InstructionSequence(linkage, graph, |
+ schedule); |
+ SourcePositionTable source_positions(graph); |
+ InstructionSelector selector(code, &source_positions); |
+ selector.SelectInstructions(); |
+ |
+ os << "----- Instruction sequence before register allocation -----\n" |
+ << *code; |
+ |
+ RegisterAllocator allocator(code); |
+ CHECK(allocator.Allocate()); |
+ |
+ os << "----- Instruction sequence after register allocation -----\n" |
+ << *code; |
+ |
+ compiler::CodeGenerator generator(code); |
+ result_code = generator.GenerateCode(); |
+ |
+#ifdef DEBUG |
+ result_code->Print(); |
+#endif |
+ } |
+ |
+ Zone* zone() { return scope_->main_zone(); } |
+ |
+ HandleAndZoneScope* scope_; |
+ Handle<JSFunction> function; |
+ CompilationInfo info; |
+ BailoutId bailout_id; |
+ Handle<Code> result_code; |
+ v8::internal::compiler::InstructionSequence* code; |
+ Graph* graph; |
+}; |
+ |
+ |
+class TrivialDeoptCodegenTester : public DeoptCodegenTester { |
+ public: |
+ explicit TrivialDeoptCodegenTester(HandleAndZoneScope* scope) |
+ : DeoptCodegenTester(scope, |
+ "function foo() { deopt(); return 42; }; foo") {} |
+ |
+ void GenerateCode() { |
+ GenerateCodeFromSchedule(BuildGraphAndSchedule(graph)); |
+ } |
+ |
+ Schedule* BuildGraphAndSchedule(Graph* graph) { |
+ Isolate* isolate = info.isolate(); |
+ CommonOperatorBuilder common(zone()); |
+ |
+ // Manually construct a schedule for the function below: |
+ // function foo() { |
+ // deopt(); |
+ // } |
+ |
+ MachineRepresentation parameter_reps[] = {kMachineTagged}; |
+ MachineCallDescriptorBuilder descriptor_builder(kMachineTagged, 1, |
+ parameter_reps); |
+ |
+ RawMachineAssembler m(graph, &descriptor_builder); |
+ |
+ Handle<Object> undef_object = |
+ Handle<Object>(isolate->heap()->undefined_value(), isolate); |
+ PrintableUnique<Object> undef_constant = |
+ PrintableUnique<Object>::CreateUninitialized(zone(), undef_object); |
+ Node* undef_node = m.NewNode(common.HeapConstant(undef_constant)); |
+ |
+ Handle<JSFunction> deopt_function = |
+ NewFunction("function deopt() { %DeoptimizeFunction(foo); }; deopt"); |
+ PrintableUnique<Object> deopt_fun_constant = |
+ PrintableUnique<Object>::CreateUninitialized(zone(), deopt_function); |
+ Node* deopt_fun_node = m.NewNode(common.HeapConstant(deopt_fun_constant)); |
+ |
+ MLabel deopt, cont; |
+ Node* call = m.CallJS0(deopt_fun_node, undef_node, &cont, &deopt); |
+ |
+ m.Bind(&cont); |
+ m.NewNode(common.Continuation(), call); |
+ m.Return(undef_node); |
+ |
+ m.Bind(&deopt); |
+ m.NewNode(common.LazyDeoptimization(), call); |
+ |
+ bailout_id = GetCallBailoutId(); |
+ FrameStateDescriptor stateDescriptor(bailout_id); |
+ Node* state_node = m.NewNode(common.FrameState(stateDescriptor)); |
+ m.Deoptimize(state_node); |
+ |
+ // Schedule the graph: |
+ Schedule* schedule = m.Export(); |
+ |
+ cont_block = cont.block(); |
+ deopt_block = deopt.block(); |
+ |
+ return schedule; |
+ } |
+ |
+ BailoutId GetCallBailoutId() { |
+ ZoneList<Statement*>* body = info.function()->body(); |
+ for (int i = 0; i < body->length(); i++) { |
+ if (body->at(i)->IsExpressionStatement() && |
+ body->at(i)->AsExpressionStatement()->expression()->IsCall()) { |
+ return body->at(i)->AsExpressionStatement()->expression()->id(); |
+ } |
+ } |
+ CHECK(false); |
+ return BailoutId(-1); |
+ } |
+ |
+ BasicBlock* cont_block; |
+ BasicBlock* deopt_block; |
+}; |
+ |
+ |
+TEST(TurboTrivialDeoptCodegen) { |
+ HandleAndZoneScope scope; |
+ InitializedHandleScope handles; |
+ |
+ FLAG_allow_natives_syntax = true; |
+ FLAG_turbo_deoptimization = true; |
+ |
+ TrivialDeoptCodegenTester t(&scope); |
+ t.GenerateCode(); |
+ |
+ DeoptimizationInputData* data = |
+ DeoptimizationInputData::cast(t.result_code->deoptimization_data()); |
+ |
+ Label* cont_label = t.code->GetLabel(t.cont_block); |
+ Label* deopt_label = t.code->GetLabel(t.deopt_block); |
+ |
+ // Check the patch table. It should patch the continuation address to the |
+ // deoptimization block address. |
+ CHECK_EQ(1, data->ReturnAddressPatchCount()); |
+ CHECK_EQ(cont_label->pos(), data->ReturnAddressPc(0)->value()); |
+ CHECK_EQ(deopt_label->pos(), data->PatchedAddressPc(0)->value()); |
+ |
+ // Check that we deoptimize to the right AST id. |
+ CHECK_EQ(1, data->DeoptCount()); |
+ CHECK_EQ(1, data->DeoptCount()); |
+ CHECK_EQ(t.bailout_id.ToInt(), data->AstId(0).ToInt()); |
+} |
+ |
+ |
+TEST(TurboTrivialDeoptCodegenAndRun) { |
+ HandleAndZoneScope scope; |
+ InitializedHandleScope handles; |
+ |
+ FLAG_allow_natives_syntax = true; |
+ FLAG_turbo_deoptimization = true; |
+ |
+ TrivialDeoptCodegenTester t(&scope); |
+ t.GenerateCode(); |
+ |
+ t.function->ReplaceCode(*t.result_code); |
+ t.info.context()->native_context()->AddOptimizedCode(*t.result_code); |
+ |
+ Isolate* isolate = scope.main_isolate(); |
+ Handle<Object> result; |
+ bool has_pending_exception = |
+ !Execution::Call(isolate, t.function, |
+ isolate->factory()->undefined_value(), 0, NULL, |
+ false).ToHandle(&result); |
+ CHECK(!has_pending_exception); |
+ CHECK(result->SameValue(Smi::FromInt(42))); |
+} |
+ |
+ |
+class TrivialRuntimeDeoptCodegenTester : public DeoptCodegenTester { |
+ public: |
+ explicit TrivialRuntimeDeoptCodegenTester(HandleAndZoneScope* scope) |
+ : DeoptCodegenTester( |
+ scope, |
+ "function foo() { %DeoptimizeFunction(foo); return 42; }; foo") {} |
+ |
+ void GenerateCode() { |
+ GenerateCodeFromSchedule(BuildGraphAndSchedule(graph)); |
+ } |
+ |
+ Schedule* BuildGraphAndSchedule(Graph* graph) { |
+ Isolate* isolate = info.isolate(); |
+ CommonOperatorBuilder common(zone()); |
+ |
+ // Manually construct a schedule for the function below: |
+ // function foo() { |
+ // %DeoptimizeFunction(foo); |
+ // } |
+ |
+ MachineRepresentation parameter_reps[] = {kMachineTagged}; |
+ MachineCallDescriptorBuilder descriptor_builder(kMachineTagged, 2, |
+ parameter_reps); |
+ |
+ RawMachineAssembler m(graph, &descriptor_builder); |
+ |
+ Handle<Object> undef_object = |
+ Handle<Object>(isolate->heap()->undefined_value(), isolate); |
+ PrintableUnique<Object> undef_constant = |
+ PrintableUnique<Object>::CreateUninitialized(zone(), undef_object); |
+ Node* undef_node = m.NewNode(common.HeapConstant(undef_constant)); |
+ |
+ PrintableUnique<Object> this_fun_constant = |
+ PrintableUnique<Object>::CreateUninitialized(zone(), function); |
+ Node* this_fun_node = m.NewNode(common.HeapConstant(this_fun_constant)); |
+ |
+ MLabel deopt, cont; |
+ Node* call = m.CallRuntime1(Runtime::kDeoptimizeFunction, this_fun_node, |
+ &cont, &deopt); |
+ |
+ m.Bind(&cont); |
+ m.NewNode(common.Continuation(), call); |
+ m.Return(undef_node); |
+ |
+ m.Bind(&deopt); |
+ m.NewNode(common.LazyDeoptimization(), call); |
+ |
+ bailout_id = GetCallBailoutId(); |
+ FrameStateDescriptor stateDescriptor(bailout_id); |
+ Node* state_node = m.NewNode(common.FrameState(stateDescriptor)); |
+ m.Deoptimize(state_node); |
+ |
+ // Schedule the graph: |
+ Schedule* schedule = m.Export(); |
+ |
+ cont_block = cont.block(); |
+ deopt_block = deopt.block(); |
+ |
+ return schedule; |
+ } |
+ |
+ BailoutId GetCallBailoutId() { |
+ ZoneList<Statement*>* body = info.function()->body(); |
+ for (int i = 0; i < body->length(); i++) { |
+ if (body->at(i)->IsExpressionStatement() && |
+ body->at(i)->AsExpressionStatement()->expression()->IsCallRuntime()) { |
+ return body->at(i)->AsExpressionStatement()->expression()->id(); |
+ } |
+ } |
+ CHECK(false); |
+ return BailoutId(-1); |
+ } |
+ |
+ BasicBlock* cont_block; |
+ BasicBlock* deopt_block; |
+}; |
+ |
+ |
+TEST(TurboTrivialRuntimeDeoptCodegenAndRun) { |
+ HandleAndZoneScope scope; |
+ InitializedHandleScope handles; |
+ |
+ FLAG_allow_natives_syntax = true; |
+ FLAG_turbo_deoptimization = true; |
+ |
+ TrivialRuntimeDeoptCodegenTester t(&scope); |
+ t.GenerateCode(); |
+ |
+ t.function->ReplaceCode(*t.result_code); |
+ t.info.context()->native_context()->AddOptimizedCode(*t.result_code); |
+ |
+ Isolate* isolate = scope.main_isolate(); |
+ Handle<Object> result; |
+ bool has_pending_exception = |
+ !Execution::Call(isolate, t.function, |
+ isolate->factory()->undefined_value(), 0, NULL, |
+ false).ToHandle(&result); |
+ CHECK(!has_pending_exception); |
+ CHECK(result->SameValue(Smi::FromInt(42))); |
+} |