// 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; #if V8_TURBOFAN_TARGET typedef RawMachineAssembler::Label MLabel; static Handle NewFunction(const char* source) { return v8::Utils::OpenHandle( *v8::Handle::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(function->code())); CHECK(Rewriter::Rewrite(&info)); CHECK(Scope::Analyze(&info)); CHECK_NE(NULL, info.scope()); Handle scope_info = ScopeInfo::Create(info.scope(), info.zone()); info.shared_info()->set_scope_info(*scope_info); FunctionTester::EnsureDeoptimizationSupport(&info); DCHECK(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 function; CompilationInfo info; BailoutId bailout_id; Handle 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(); // } MachineType parameter_reps[] = {kMachAnyTagged}; MachineCallDescriptorBuilder descriptor_builder(kMachAnyTagged, 1, parameter_reps); RawMachineAssembler m(graph, &descriptor_builder); Handle undef_object = Handle(isolate->heap()->undefined_value(), isolate); PrintableUnique undef_constant = PrintableUnique::CreateUninitialized(zone(), undef_object); Node* undef_node = m.NewNode(common.HeapConstant(undef_constant)); Handle deopt_function = NewFunction("function deopt() { %DeoptimizeFunction(foo); }; deopt"); PrintableUnique deopt_fun_constant = PrintableUnique::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(); Node* parameters = m.NewNode(common.StateValues(1), undef_node); Node* locals = m.NewNode(common.StateValues(0)); Node* stack = m.NewNode(common.StateValues(0)); Node* state_node = m.NewNode(common.FrameState(bailout_id), parameters, locals, stack); m.Deoptimize(state_node); // Schedule the graph: Schedule* schedule = m.Export(); cont_block = cont.block(); deopt_block = deopt.block(); return schedule; } BailoutId GetCallBailoutId() { ZoneList* 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 safepoint - it should contain an entry for the call // with the right deoptimization address. SafepointEntry entry = t.result_code->GetSafepointEntry( t.result_code->instruction_start() + cont_label->pos()); CHECK(entry.is_valid()); CHECK_EQ(deopt_label->pos(), entry.deoptimization_pc()); // Check that we deoptimize to the right AST id. 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 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); // } MachineType parameter_reps[] = {kMachAnyTagged}; MachineCallDescriptorBuilder descriptor_builder(kMachAnyTagged, 2, parameter_reps); RawMachineAssembler m(graph, &descriptor_builder); Handle undef_object = Handle(isolate->heap()->undefined_value(), isolate); PrintableUnique undef_constant = PrintableUnique::CreateUninitialized(zone(), undef_object); Node* undef_node = m.NewNode(common.HeapConstant(undef_constant)); PrintableUnique this_fun_constant = PrintableUnique::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(); Node* parameters = m.NewNode(common.StateValues(1), undef_node); Node* locals = m.NewNode(common.StateValues(0)); Node* stack = m.NewNode(common.StateValues(0)); Node* state_node = m.NewNode(common.FrameState(bailout_id), parameters, locals, stack); m.Deoptimize(state_node); // Schedule the graph: Schedule* schedule = m.Export(); cont_block = cont.block(); deopt_block = deopt.block(); return schedule; } BailoutId GetCallBailoutId() { ZoneList* 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 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))); } #endif