d8295050d2
Since the deopt patch address needs to be available during GC to resolve safepoints, we need to move it to the code object (instead of the deoptimization input data) - accessing a separate fixed array is not safe during GC. This CL adds a deoptimization_pc field to each safepoint. The fields points to the deoptimization block. The CL also fixes wrong register allocator constraints for frame states on calls. These should always live on the stack because registers are not preserved during a call. BUG= R=bmeurer@chromium.org Review URL: https://codereview.chromium.org/504493002 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23334 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
348 lines
11 KiB
C++
348 lines
11 KiB
C++
// 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<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());
|
|
Handle<ScopeInfo> 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<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();
|
|
// }
|
|
|
|
MachineType parameter_reps[] = {kMachAnyTagged};
|
|
MachineCallDescriptorBuilder descriptor_builder(kMachAnyTagged, 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();
|
|
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<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 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(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);
|
|
// }
|
|
|
|
MachineType parameter_reps[] = {kMachAnyTagged};
|
|
MachineCallDescriptorBuilder descriptor_builder(kMachAnyTagged, 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();
|
|
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<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)));
|
|
}
|
|
|
|
#endif
|