v8/test/cctest/compiler/test-codegen-deopt.cc
jarin@chromium.org d8295050d2 Fix deoptimization address patching in Turbofan to use safepoints.
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
2014-08-25 07:02:19 +00:00

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