v8/test/cctest/compiler/test-codegen-deopt.cc
jarin b7dc9c580a [turbofan] Remember types for deoptimization during simplified lowering.
With this change, we remember the types of frame state inputs (in a new
operator, called TypedStateValues). Instead of inferring the value types
when building translations, we used the recorded types.

The original approach was not reliable because the passes after
simplified lowering can change node types, and this in turn confuses
the translation builder.

BUG=chromium:468727
LOG=n
R=bmeurer@chromium.org

Review URL: https://codereview.chromium.org/1015423002

Cr-Commit-Position: refs/heads/master@{#27310}
2015-03-19 14:00:33 +00:00

302 lines
9.2 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/ast-numbering.h"
#include "src/full-codegen.h"
#include "src/parser.h"
#include "src/rewriter.h"
#include "test/cctest/compiler/c-signature.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;
typedef v8::internal::compiler::InstructionSequence TestInstrSeq;
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)),
parse_info(scope->main_zone(), function),
info(&parse_info),
bailout_id(-1),
tagged_type(1, kMachAnyTagged, zone()),
empty_types(zone()) {
CHECK(Parser::ParseStatic(&parse_info));
info.SetOptimizing(BailoutId::None(), Handle<Code>(function->code()));
CHECK(Compiler::Analyze(&parse_info));
CHECK(Compiler::EnsureDeoptimizationSupport(&info));
DCHECK(info.shared_info()->has_deoptimization_support());
graph = new (scope_->main_zone()) Graph(scope_->main_zone());
}
virtual ~DeoptCodegenTester() {}
void GenerateCodeFromSchedule(Schedule* schedule) {
OFStream os(stdout);
if (FLAG_trace_turbo) {
os << *schedule;
}
result_code = Pipeline::GenerateCodeForTesting(&info, graph, schedule);
#ifdef OBJECT_PRINT
if (FLAG_print_opt_code || FLAG_trace_turbo) {
result_code->Print();
}
#endif
}
Zone* zone() { return scope_->main_zone(); }
Isolate* isolate() { return scope_->main_isolate(); }
HandleAndZoneScope* scope_;
Handle<JSFunction> function;
ParseInfo parse_info;
CompilationInfo info;
BailoutId bailout_id;
Handle<Code> result_code;
TestInstrSeq* code;
Graph* graph;
ZoneVector<MachineType> tagged_type;
ZoneVector<MachineType> empty_types;
};
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) {
CommonOperatorBuilder common(zone());
// Manually construct a schedule for the function below:
// function foo() {
// deopt();
// }
CSignature1<Object*, Object*> sig;
RawMachineAssembler m(isolate(), graph, &sig);
Handle<JSFunction> deopt_function =
NewFunction("function deopt() { %DeoptimizeFunction(foo); }; deopt");
Unique<JSFunction> deopt_fun_constant =
Unique<JSFunction>::CreateUninitialized(deopt_function);
Node* deopt_fun_node = m.NewNode(common.HeapConstant(deopt_fun_constant));
Handle<Context> caller_context(function->context(), CcTest::i_isolate());
Unique<Context> caller_context_constant =
Unique<Context>::CreateUninitialized(caller_context);
Node* caller_context_node =
m.NewNode(common.HeapConstant(caller_context_constant));
bailout_id = GetCallBailoutId();
Node* parameters =
m.NewNode(common.TypedStateValues(&tagged_type), m.UndefinedConstant());
Node* locals = m.NewNode(common.TypedStateValues(&empty_types));
Node* stack = m.NewNode(common.TypedStateValues(&empty_types));
Node* state_node = m.NewNode(
common.FrameState(JS_FRAME, bailout_id,
OutputFrameStateCombine::Ignore()),
parameters, locals, stack, caller_context_node, m.UndefinedConstant());
Handle<Context> context(deopt_function->context(), CcTest::i_isolate());
Unique<Context> context_constant =
Unique<Context>::CreateUninitialized(context);
Node* context_node = m.NewNode(common.HeapConstant(context_constant));
m.CallJS0(deopt_fun_node, m.UndefinedConstant(), context_node, state_node);
m.Return(m.UndefinedConstant());
// Schedule the graph:
Schedule* schedule = m.Export();
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);
}
};
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());
// TODO(jarin) Find a way to test the safepoint.
// 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<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) {
CommonOperatorBuilder common(zone());
// Manually construct a schedule for the function below:
// function foo() {
// %DeoptimizeFunction(foo);
// }
CSignature1<Object*, Object*> sig;
RawMachineAssembler m(isolate(), graph, &sig);
Unique<HeapObject> this_fun_constant =
Unique<HeapObject>::CreateUninitialized(function);
Node* this_fun_node = m.NewNode(common.HeapConstant(this_fun_constant));
Handle<Context> context(function->context(), CcTest::i_isolate());
Unique<HeapObject> context_constant =
Unique<HeapObject>::CreateUninitialized(context);
Node* context_node = m.NewNode(common.HeapConstant(context_constant));
bailout_id = GetCallBailoutId();
Node* parameters =
m.NewNode(common.TypedStateValues(&tagged_type), m.UndefinedConstant());
Node* locals = m.NewNode(common.TypedStateValues(&empty_types));
Node* stack = m.NewNode(common.TypedStateValues(&empty_types));
Node* state_node = m.NewNode(
common.FrameState(JS_FRAME, bailout_id,
OutputFrameStateCombine::Ignore()),
parameters, locals, stack, context_node, m.UndefinedConstant());
m.CallRuntime1(Runtime::kDeoptimizeFunction, this_fun_node, context_node,
state_node);
m.Return(m.UndefinedConstant());
// Schedule the graph:
Schedule* schedule = m.Export();
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);
}
};
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