[turbofan] Osr value typing + dynamic type checks on entry.

This introduces a new OsrGuard node that is inserted during graph building
to guard the inferred type of the OSR value.

The type of the OSR value is inferred by running the typer before OSR
deconstruction, and then taking the type from the phi that takes the
OSR value. After the deconstruction, we throw the types away.

At the moment we only support the SignedSmall OSR type and we always
pick the tagged representation. Later, we might want to support more
types (such as Number) and pick better representations (int32/float64).

This CL also removes the OSR deconstruction tests because they build
unrealistic graph (no effect chain, no loop termination). I considered
adding the effect chains to the tests, but this would make the tests
even more brittle.

Review-Url: https://codereview.chromium.org/2384113002
Cr-Commit-Position: refs/heads/master@{#39971}
This commit is contained in:
jarin 2016-10-04 22:56:45 -07:00 committed by Commit bot
parent a974970cff
commit 1f5dc90a90
17 changed files with 352 additions and 603 deletions

View File

@ -788,8 +788,10 @@ AstGraphBuilder::Environment::CopyAsUnreachable() {
}
AstGraphBuilder::Environment* AstGraphBuilder::Environment::CopyForOsrEntry() {
return new (zone())
Environment(this, builder_->liveness_analyzer()->NewBlock());
LivenessAnalyzerBlock* copy_block =
liveness_block() == nullptr ? nullptr
: builder_->liveness_analyzer()->NewBlock();
return new (zone()) Environment(this, copy_block);
}
AstGraphBuilder::Environment*
@ -4202,27 +4204,47 @@ void AstGraphBuilder::Environment::PrepareForOsrEntry() {
graph->start(), graph->start());
UpdateControlDependency(osr_loop_entry);
UpdateEffectDependency(osr_loop_entry);
// Set OSR values.
for (int i = 0; i < size; ++i) {
values()->at(i) =
graph->NewNode(builder_->common()->OsrValue(i), osr_loop_entry);
}
// Set the contexts.
// The innermost context is the OSR value, and the outer contexts are
// reconstructed by dynamically walking up the context chain.
Node* osr_context = nullptr;
const Operator* op =
builder_->javascript()->LoadContext(0, Context::PREVIOUS_INDEX, true);
// Set the innermost context.
const Operator* op_inner =
builder_->common()->OsrValue(Linkage::kOsrContextSpillSlotIndex);
contexts()->back() = graph->NewNode(op_inner, osr_loop_entry);
// Create a checkpoint.
Node* frame_state = Checkpoint(builder_->info()->osr_ast_id());
Node* checkpoint = graph->NewNode(common()->Checkpoint(), frame_state,
osr_loop_entry, osr_loop_entry);
UpdateEffectDependency(checkpoint);
// Create the OSR guard nodes.
const Operator* guard_op =
builder_->common()->OsrGuard(OsrGuardType::kUninitialized);
Node* effect = checkpoint;
for (int i = 0; i < size; ++i) {
values()->at(i) = effect =
graph->NewNode(guard_op, values()->at(i), effect, osr_loop_entry);
}
contexts()->back() = effect =
graph->NewNode(guard_op, contexts()->back(), effect, osr_loop_entry);
// The innermost context is the OSR value, and the outer contexts are
// reconstructed by dynamically walking up the context chain.
const Operator* load_op =
builder_->javascript()->LoadContext(0, Context::PREVIOUS_INDEX, true);
Node* osr_context = effect = contexts()->back();
int last = static_cast<int>(contexts()->size() - 1);
for (int i = last; i >= 0; i--) {
osr_context = (i == last) ? graph->NewNode(op_inner, osr_loop_entry)
: graph->NewNode(op, osr_context, osr_context,
osr_loop_entry);
for (int i = last - 1; i >= 0; i--) {
osr_context = effect =
graph->NewNode(load_op, osr_context, effect, osr_loop_entry);
contexts()->at(i) = osr_context;
}
UpdateEffectDependency(effect);
}
void AstGraphBuilder::Environment::PrepareForLoop(BitVector* assigned) {

View File

@ -438,6 +438,24 @@ void BytecodeGraphBuilder::Environment::PrepareForOsrEntry() {
if (i >= accumulator_base()) idx = Linkage::kOsrAccumulatorRegisterIndex;
values()->at(i) = graph()->NewNode(common()->OsrValue(idx), entry);
}
BailoutId loop_id(builder_->bytecode_iterator().current_offset());
Node* frame_state =
Checkpoint(loop_id, OutputFrameStateCombine::Ignore(), false);
Node* checkpoint =
graph()->NewNode(common()->Checkpoint(), frame_state, entry, entry);
UpdateEffectDependency(checkpoint);
// Create the OSR guard nodes.
const Operator* guard_op = common()->OsrGuard(OsrGuardType::kUninitialized);
Node* effect = checkpoint;
for (int i = 0; i < size; i++) {
values()->at(i) = effect =
graph()->NewNode(guard_op, values()->at(i), effect, entry);
}
Node* context = effect = graph()->NewNode(guard_op, Context(), effect, entry);
SetContext(context);
UpdateEffectDependency(effect);
}
bool BytecodeGraphBuilder::Environment::StateValuesRequireUpdate(

View File

@ -210,6 +210,31 @@ std::ostream& operator<<(std::ostream& os,
return os;
}
int OsrValueIndexOf(Operator const* op) {
DCHECK_EQ(IrOpcode::kOsrValue, op->opcode());
return OpParameter<int>(op);
}
size_t hash_value(OsrGuardType type) { return static_cast<size_t>(type); }
std::ostream& operator<<(std::ostream& os, OsrGuardType type) {
switch (type) {
case OsrGuardType::kUninitialized:
return os << "Uninitialized";
case OsrGuardType::kSignedSmall:
return os << "SignedSmall";
case OsrGuardType::kAny:
return os << "Any";
}
UNREACHABLE();
return os;
}
OsrGuardType OsrGuardTypeOf(Operator const* op) {
DCHECK_EQ(IrOpcode::kOsrGuard, op->opcode());
return OpParameter<OsrGuardType>(op);
}
#define CACHED_OP_LIST(V) \
V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1) \
V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
@ -780,7 +805,6 @@ const Operator* CommonOperatorBuilder::Parameter(int index,
ParameterInfo(index, debug_name)); // parameter info
}
const Operator* CommonOperatorBuilder::OsrValue(int index) {
return new (zone()) Operator1<int>( // --
IrOpcode::kOsrValue, Operator::kNoProperties, // opcode
@ -789,6 +813,13 @@ const Operator* CommonOperatorBuilder::OsrValue(int index) {
index); // parameter
}
const Operator* CommonOperatorBuilder::OsrGuard(OsrGuardType type) {
return new (zone()) Operator1<OsrGuardType>( // --
IrOpcode::kOsrGuard, Operator::kNoThrow, // opcode
"OsrGuard", // name
1, 1, 1, 1, 1, 0, // counts
type); // parameter
}
const Operator* CommonOperatorBuilder::Int32Constant(int32_t value) {
return new (zone()) Operator1<int32_t>( // --

View File

@ -171,6 +171,13 @@ std::ostream& operator<<(std::ostream& os,
Type* TypeGuardTypeOf(Operator const*) WARN_UNUSED_RESULT;
int OsrValueIndexOf(Operator const*);
enum class OsrGuardType { kUninitialized, kSignedSmall, kAny };
size_t hash_value(OsrGuardType type);
std::ostream& operator<<(std::ostream&, OsrGuardType);
OsrGuardType OsrGuardTypeOf(Operator const*);
// Interface for building common operators that can be used at any level of IR,
// including JavaScript, mid-level, and low-level.
class CommonOperatorBuilder final : public ZoneObject {
@ -202,6 +209,7 @@ class CommonOperatorBuilder final : public ZoneObject {
const Operator* OsrNormalEntry();
const Operator* OsrLoopEntry();
const Operator* OsrValue(int index);
const Operator* OsrGuard(OsrGuardType type);
const Operator* Int32Constant(int32_t);
const Operator* Int64Constant(int64_t);

View File

@ -1733,7 +1733,7 @@ void InstructionSelector::VisitIfException(Node* node) {
void InstructionSelector::VisitOsrValue(Node* node) {
OperandGenerator g(this);
int index = OpParameter<int>(node);
int index = OsrValueIndexOf(node->op());
Emit(kArchNop,
g.DefineAsLocation(node, linkage()->GetOsrValueLocation(index)));
}

View File

@ -28,7 +28,7 @@ Reduction JSFrameSpecialization::Reduce(Node* node) {
Reduction JSFrameSpecialization::ReduceOsrValue(Node* node) {
DCHECK_EQ(IrOpcode::kOsrValue, node->opcode());
Handle<Object> value;
int const index = OpParameter<int>(node);
int index = OsrValueIndexOf(node->op());
int const parameters_count = frame()->ComputeParametersCount() + 1;
if (index == Linkage::kOsrContextSpillSlotIndex) {
value = handle(frame()->context(), isolate());

View File

@ -369,7 +369,7 @@ MaybeHandle<Context> NodeProperties::GetSpecializationNativeContext(
return handle(context->native_context());
}
case IrOpcode::kOsrValue: {
int const index = OpParameter<int>(node);
int const index = OsrValueIndexOf(node->op());
if (index == Linkage::kOsrContextSpillSlotIndex) {
return native_context;
}

View File

@ -58,6 +58,7 @@
V(Call) \
V(Parameter) \
V(OsrValue) \
V(OsrGuard) \
V(LoopExit) \
V(LoopExitValue) \
V(LoopExitEffect) \

View File

@ -47,13 +47,14 @@ OsrHelper::OsrHelper(CompilationInfo* info)
if (TRACE_COND) PrintF(__VA_ARGS__); \
} while (false)
namespace {
// Peel outer loops and rewire the graph so that control reduction can
// produce a properly formed graph.
static void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
Zone* tmp_zone, Node* dead,
LoopTree* loop_tree, LoopTree::Loop* osr_loop,
Node* osr_normal_entry, Node* osr_loop_entry) {
void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
Zone* tmp_zone, Node* dead, LoopTree* loop_tree,
LoopTree::Loop* osr_loop, Node* osr_normal_entry,
Node* osr_loop_entry) {
const size_t original_count = graph->NodeCount();
AllNodes all(tmp_zone, graph);
NodeVector tmp_inputs(tmp_zone);
@ -93,7 +94,8 @@ static void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
continue;
}
if (orig->InputCount() == 0 || orig->opcode() == IrOpcode::kParameter ||
orig->opcode() == IrOpcode::kOsrValue) {
orig->opcode() == IrOpcode::kOsrValue ||
orig->opcode() == IrOpcode::kOsrGuard) {
// No need to copy leaf nodes or parameters.
mapping->at(orig->id()) = orig;
continue;
@ -255,6 +257,41 @@ static void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
}
}
void SetTypeForOsrValue(Node* osr_value, Node* loop,
CommonOperatorBuilder* common) {
Node* osr_guard = nullptr;
for (Node* use : osr_value->uses()) {
if (use->opcode() == IrOpcode::kOsrGuard) {
DCHECK_EQ(use->InputAt(0), osr_value);
osr_guard = use;
break;
}
}
OsrGuardType guard_type = OsrGuardType::kAny;
// Find the phi that uses the OsrGuard node and get the type from
// there. Skip the search if the OsrGuard does not have value use
// (i.e., if there is other use beyond the effect use).
if (osr_guard->UseCount() > 1) {
Type* type = nullptr;
for (Node* use : osr_guard->uses()) {
if (use->opcode() == IrOpcode::kPhi) {
if (NodeProperties::GetControlInput(use) != loop) continue;
CHECK_NULL(type);
type = NodeProperties::GetType(use);
}
}
CHECK_NOT_NULL(type);
if (type->Is(Type::SignedSmall())) {
guard_type = OsrGuardType::kSignedSmall;
}
}
NodeProperties::ChangeOp(osr_guard, common->OsrGuard(guard_type));
}
} // namespace
void OsrHelper::Deconstruct(JSGraph* jsgraph, CommonOperatorBuilder* common,
Zone* tmp_zone) {
@ -283,6 +320,12 @@ void OsrHelper::Deconstruct(JSGraph* jsgraph, CommonOperatorBuilder* common,
CHECK(osr_loop); // Should have found the OSR loop.
for (Node* use : osr_loop_entry->uses()) {
if (use->opcode() == IrOpcode::kOsrValue) {
SetTypeForOsrValue(use, osr_loop, common);
}
}
// Analyze the graph to determine how deeply nested the OSR loop is.
LoopTree* loop_tree = LoopFinder::BuildLoopTree(graph, tmp_zone);

View File

@ -859,7 +859,20 @@ struct TyperPhase {
}
};
#ifdef DEBUG
struct OsrTyperPhase {
static const char* phase_name() { return "osr typer"; }
void Run(PipelineData* data, Zone* temp_zone) {
NodeVector roots(temp_zone);
data->jsgraph()->GetCachedNodes(&roots);
// Dummy induction variable optimizer: at the moment, we do not try
// to compute loop variable bounds on OSR.
LoopVariableOptimizer induction_vars(data->jsgraph()->graph(),
data->common(), temp_zone);
Typer typer(data->isolate(), data->graph());
typer.Run(roots, &induction_vars);
}
};
struct UntyperPhase {
static const char* phase_name() { return "untyper"; }
@ -876,6 +889,12 @@ struct UntyperPhase {
}
};
NodeVector roots(temp_zone);
data->jsgraph()->GetCachedNodes(&roots);
for (Node* node : roots) {
NodeProperties::RemoveType(node);
}
JSGraphReducer graph_reducer(data->jsgraph(), temp_zone);
RemoveTypeReducer remove_type_reducer;
AddReducer(data, &graph_reducer, &remove_type_reducer);
@ -883,12 +902,15 @@ struct UntyperPhase {
}
};
#endif // DEBUG
struct OsrDeconstructionPhase {
static const char* phase_name() { return "OSR deconstruction"; }
void Run(PipelineData* data, Zone* temp_zone) {
GraphTrimmer trimmer(temp_zone, data->graph());
NodeVector roots(temp_zone);
data->jsgraph()->GetCachedNodes(&roots);
trimmer.TrimGraph(roots.begin(), roots.end());
OsrHelper osr_helper(data->info());
osr_helper.Deconstruct(data->jsgraph(), data->common(), temp_zone);
}
@ -1495,7 +1517,11 @@ bool PipelineImpl::CreateGraph() {
// Perform OSR deconstruction.
if (info()->is_osr()) {
Run<OsrTyperPhase>();
Run<OsrDeconstructionPhase>();
Run<UntyperPhase>();
RunPrintAndVerify("OSR deconstruction", true);
}

View File

@ -161,7 +161,8 @@ void ReplaceEffectControlUses(Node* node, Node* effect, Node* control) {
} else if (NodeProperties::IsEffectEdge(edge)) {
edge.UpdateTo(effect);
} else {
DCHECK(NodeProperties::IsValueEdge(edge));
DCHECK(NodeProperties::IsValueEdge(edge) ||
NodeProperties::IsContextEdge(edge));
}
}
}
@ -1266,6 +1267,30 @@ class RepresentationSelector {
return;
}
void VisitOsrGuard(Node* node) {
VisitInputs(node);
// Insert a dynamic check for the OSR value type if necessary.
switch (OsrGuardTypeOf(node->op())) {
case OsrGuardType::kUninitialized:
// At this point, we should always have a type for the OsrValue.
UNREACHABLE();
break;
case OsrGuardType::kSignedSmall:
if (lower()) {
NodeProperties::ChangeOp(node,
simplified()->CheckedTaggedToTaggedSigned());
}
return SetOutput(node, MachineRepresentation::kTaggedSigned);
case OsrGuardType::kAny: // Nothing to check.
if (lower()) {
DeferReplacement(node, node->InputAt(0));
}
return SetOutput(node, MachineRepresentation::kTagged);
}
UNREACHABLE();
}
// Dispatching routine for visiting the node {node} with the usage {use}.
// Depending on the operator, propagate new usage info to the inputs.
void VisitNode(Node* node, Truncation truncation,
@ -2409,6 +2434,9 @@ class RepresentationSelector {
return;
}
case IrOpcode::kOsrGuard:
return VisitOsrGuard(node);
// Operators with all inputs tagged and no or tagged output have uniform
// handling.
case IrOpcode::kEnd:
@ -2427,9 +2455,9 @@ class RepresentationSelector {
case IrOpcode::kThrow:
case IrOpcode::kBeginRegion:
case IrOpcode::kFinishRegion:
case IrOpcode::kOsrValue:
case IrOpcode::kProjection:
case IrOpcode::kObjectState:
case IrOpcode::kOsrValue:
// All JavaScript operators except JSToNumber have uniform handling.
#define OPCODE_CASE(name) case IrOpcode::k##name:
JS_SIMPLE_BINOP_LIST(OPCODE_CASE)

View File

@ -550,6 +550,19 @@ Type* Typer::Visitor::TypeParameter(Node* node) { return Type::Any(); }
Type* Typer::Visitor::TypeOsrValue(Node* node) { return Type::Any(); }
Type* Typer::Visitor::TypeOsrGuard(Node* node) {
switch (OsrGuardTypeOf(node->op())) {
case OsrGuardType::kUninitialized:
return Type::None();
case OsrGuardType::kSignedSmall:
return Type::SignedSmall();
case OsrGuardType::kAny:
return Type::Any();
}
UNREACHABLE();
return nullptr;
}
Type* Typer::Visitor::TypeRetain(Node* node) {
UNREACHABLE();
return nullptr;

View File

@ -375,6 +375,23 @@ void Verifier::Visitor::Check(Node* node) {
// Type is merged from other values in the graph and could be any.
CheckTypeIs(node, Type::Any());
break;
case IrOpcode::kOsrGuard:
// OSR values have a value and a control input.
CHECK_EQ(1, value_count);
CHECK_EQ(1, effect_count);
CHECK_EQ(1, control_count);
switch (OsrGuardTypeOf(node->op())) {
case OsrGuardType::kUninitialized:
CheckTypeIs(node, Type::None());
break;
case OsrGuardType::kSignedSmall:
CheckTypeIs(node, Type::SignedSmall());
break;
case OsrGuardType::kAny:
CheckTypeIs(node, Type::Any());
break;
}
break;
case IrOpcode::kProjection: {
// Projection has an input that produces enough values.
int index = static_cast<int>(ProjectionIndexOf(node->op()));

View File

@ -40,7 +40,6 @@ v8_executable("cctest") {
"compiler/test-multiple-return.cc",
"compiler/test-node.cc",
"compiler/test-operator.cc",
"compiler/test-osr.cc",
"compiler/test-representation-change.cc",
"compiler/test-run-bytecode-graph-builder.cc",
"compiler/test-run-calls-to-external-references.cc",

View File

@ -60,7 +60,6 @@
'compiler/test-multiple-return.cc',
'compiler/test-node.cc',
'compiler/test-operator.cc',
'compiler/test-osr.cc',
'compiler/test-representation-change.cc',
'compiler/test-run-bytecode-graph-builder.cc',
'compiler/test-run-calls-to-external-references.cc',

View File

@ -1,575 +0,0 @@
// Copyright 2015 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/codegen.h"
#include "src/compiler/all-nodes.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/diamond.h"
#include "src/compiler/graph.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/operator.h"
#include "src/compiler/osr.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
// TODO(titzer): move this method to a common testing place.
static int CheckInputs(Node* node, Node* i0 = NULL, Node* i1 = NULL,
Node* i2 = NULL, Node* i3 = NULL) {
int count = 4;
if (i3 == NULL) count = 3;
if (i2 == NULL) count = 2;
if (i1 == NULL) count = 1;
if (i0 == NULL) count = 0;
CHECK_EQ(count, node->InputCount());
if (i0 != NULL) CHECK_EQ(i0, node->InputAt(0));
if (i1 != NULL) CHECK_EQ(i1, node->InputAt(1));
if (i2 != NULL) CHECK_EQ(i2, node->InputAt(2));
if (i3 != NULL) CHECK_EQ(i3, node->InputAt(3));
return count;
}
static Operator kIntLt(IrOpcode::kInt32LessThan, Operator::kPure,
"Int32LessThan", 2, 0, 0, 1, 0, 0);
static Operator kIntAdd(IrOpcode::kInt32Add, Operator::kPure, "Int32Add", 2, 0,
0, 1, 0, 0);
static const int kMaxOsrValues = 10;
class OsrDeconstructorTester : public HandleAndZoneScope {
public:
explicit OsrDeconstructorTester(int num_values)
: isolate(main_isolate()),
common(main_zone()),
graph(main_zone()),
jsgraph(main_isolate(), &graph, &common, nullptr, nullptr, nullptr),
start(graph.NewNode(common.Start(1))),
p0(graph.NewNode(common.Parameter(0), start)),
end(graph.NewNode(common.End(1), start)),
osr_normal_entry(graph.NewNode(common.OsrNormalEntry(), start, start)),
osr_loop_entry(graph.NewNode(common.OsrLoopEntry(), start, start)),
self(graph.NewNode(common.Int32Constant(0xaabbccdd))) {
CHECK(num_values <= kMaxOsrValues);
graph.SetStart(start);
for (int i = 0; i < num_values; i++) {
osr_values[i] = graph.NewNode(common.OsrValue(i), osr_loop_entry);
}
}
Isolate* isolate;
CommonOperatorBuilder common;
Graph graph;
JSGraph jsgraph;
Node* start;
Node* p0;
Node* end;
Node* osr_normal_entry;
Node* osr_loop_entry;
Node* self;
Node* osr_values[kMaxOsrValues];
Node* NewOsrPhi(Node* loop, Node* incoming, int osr_value, Node* back1 = NULL,
Node* back2 = NULL, Node* back3 = NULL) {
int count = 5;
if (back3 == NULL) count = 4;
if (back2 == NULL) count = 3;
if (back1 == NULL) count = 2;
CHECK_EQ(loop->InputCount(), count);
CHECK_EQ(osr_loop_entry, loop->InputAt(1));
Node* inputs[6];
inputs[0] = incoming;
inputs[1] = osr_values[osr_value];
if (count > 2) inputs[2] = back1;
if (count > 3) inputs[3] = back2;
if (count > 4) inputs[4] = back3;
inputs[count] = loop;
return graph.NewNode(common.Phi(MachineRepresentation::kTagged, count),
count + 1, inputs);
}
Node* NewLoop(bool is_osr, int num_backedges, Node* entry = nullptr) {
if (entry == nullptr) entry = osr_normal_entry;
Node* loop = graph.NewNode(common.Loop(1), entry);
if (is_osr) {
loop->AppendInput(graph.zone(), osr_loop_entry);
}
for (int i = 0; i < num_backedges; i++) {
loop->AppendInput(graph.zone(), loop);
}
NodeProperties::ChangeOp(loop, common.Loop(loop->InputCount()));
return loop;
}
Node* NewOsrLoop(int num_backedges, Node* entry = NULL) {
return NewLoop(true, num_backedges, entry);
}
void DeconstructOsr() {
OsrHelper helper(0, 0);
helper.Deconstruct(&jsgraph, &common, main_zone());
AllNodes nodes(main_zone(), &graph);
// Should be edited out.
CHECK(!nodes.IsLive(osr_normal_entry));
CHECK(!nodes.IsLive(osr_loop_entry));
// No dangling nodes should be left over.
for (Node* const node : nodes.reachable) {
for (Node* const use : node->uses()) {
CHECK(std::find(nodes.reachable.begin(), nodes.reachable.end(), use) !=
nodes.reachable.end());
}
}
}
};
TEST(Deconstruct_osr0) {
OsrDeconstructorTester T(0);
Node* loop = T.NewOsrLoop(1);
T.graph.SetEnd(loop);
T.DeconstructOsr();
CheckInputs(loop, T.start, loop);
}
TEST(Deconstruct_osr1) {
OsrDeconstructorTester T(1);
Node* loop = T.NewOsrLoop(1);
Node* osr_phi =
T.NewOsrPhi(loop, T.jsgraph.OneConstant(), 0, T.jsgraph.ZeroConstant());
Node* ret = T.graph.NewNode(T.common.Return(), osr_phi, T.start, loop);
T.graph.SetEnd(ret);
T.DeconstructOsr();
CheckInputs(loop, T.start, loop);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.ZeroConstant(), loop);
CheckInputs(ret, osr_phi, T.start, loop);
}
TEST(Deconstruct_osr_remove_prologue) {
OsrDeconstructorTester T(1);
Diamond d(&T.graph, &T.common, T.p0);
d.Chain(T.osr_normal_entry);
Node* loop = T.NewOsrLoop(1, d.merge);
Node* osr_phi =
T.NewOsrPhi(loop, T.jsgraph.OneConstant(), 0, T.jsgraph.ZeroConstant());
Node* ret = T.graph.NewNode(T.common.Return(), osr_phi, T.start, loop);
T.graph.SetEnd(ret);
T.DeconstructOsr();
CheckInputs(loop, T.start, loop);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.ZeroConstant(), loop);
CheckInputs(ret, osr_phi, T.start, loop);
// The control before the loop should have been removed.
AllNodes nodes(T.main_zone(), &T.graph);
CHECK(!nodes.IsLive(d.branch));
CHECK(!nodes.IsLive(d.if_true));
CHECK(!nodes.IsLive(d.if_false));
CHECK(!nodes.IsLive(d.merge));
}
TEST(Deconstruct_osr_with_body1) {
OsrDeconstructorTester T(1);
Node* loop = T.NewOsrLoop(1);
Node* branch = T.graph.NewNode(T.common.Branch(), T.p0, loop);
Node* if_true = T.graph.NewNode(T.common.IfTrue(), branch);
Node* if_false = T.graph.NewNode(T.common.IfFalse(), branch);
loop->ReplaceInput(2, if_true);
Node* osr_phi =
T.NewOsrPhi(loop, T.jsgraph.OneConstant(), 0, T.jsgraph.ZeroConstant());
Node* ret = T.graph.NewNode(T.common.Return(), osr_phi, T.start, if_false);
T.graph.SetEnd(ret);
T.DeconstructOsr();
CheckInputs(loop, T.start, if_true);
CheckInputs(branch, T.p0, loop);
CheckInputs(if_true, branch);
CheckInputs(if_false, branch);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.ZeroConstant(), loop);
CheckInputs(ret, osr_phi, T.start, if_false);
}
TEST(Deconstruct_osr_with_body2) {
OsrDeconstructorTester T(1);
Node* loop = T.NewOsrLoop(1);
// Two chained branches in the the body of the loop.
Node* branch1 = T.graph.NewNode(T.common.Branch(), T.p0, loop);
Node* if_true1 = T.graph.NewNode(T.common.IfTrue(), branch1);
Node* if_false1 = T.graph.NewNode(T.common.IfFalse(), branch1);
Node* branch2 = T.graph.NewNode(T.common.Branch(), T.p0, if_true1);
Node* if_true2 = T.graph.NewNode(T.common.IfTrue(), branch2);
Node* if_false2 = T.graph.NewNode(T.common.IfFalse(), branch2);
loop->ReplaceInput(2, if_true2);
Node* osr_phi =
T.NewOsrPhi(loop, T.jsgraph.OneConstant(), 0, T.jsgraph.ZeroConstant());
Node* merge = T.graph.NewNode(T.common.Merge(2), if_false1, if_false2);
Node* ret = T.graph.NewNode(T.common.Return(), osr_phi, T.start, merge);
T.graph.SetEnd(ret);
T.DeconstructOsr();
CheckInputs(loop, T.start, if_true2);
CheckInputs(branch1, T.p0, loop);
CheckInputs(branch2, T.p0, if_true1);
CheckInputs(if_true1, branch1);
CheckInputs(if_false1, branch1);
CheckInputs(if_true2, branch2);
CheckInputs(if_false2, branch2);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.ZeroConstant(), loop);
CheckInputs(ret, osr_phi, T.start, merge);
CheckInputs(merge, if_false1, if_false2);
}
TEST(Deconstruct_osr_with_body3) {
OsrDeconstructorTester T(1);
Node* loop = T.NewOsrLoop(2);
// Two branches that create two different backedges.
Node* branch1 = T.graph.NewNode(T.common.Branch(), T.p0, loop);
Node* if_true1 = T.graph.NewNode(T.common.IfTrue(), branch1);
Node* if_false1 = T.graph.NewNode(T.common.IfFalse(), branch1);
Node* branch2 = T.graph.NewNode(T.common.Branch(), T.p0, if_true1);
Node* if_true2 = T.graph.NewNode(T.common.IfTrue(), branch2);
Node* if_false2 = T.graph.NewNode(T.common.IfFalse(), branch2);
loop->ReplaceInput(2, if_false1);
loop->ReplaceInput(3, if_true2);
Node* osr_phi =
T.NewOsrPhi(loop, T.jsgraph.OneConstant(), 0, T.jsgraph.ZeroConstant(),
T.jsgraph.ZeroConstant());
Node* ret = T.graph.NewNode(T.common.Return(), osr_phi, T.start, if_false2);
T.graph.SetEnd(ret);
T.DeconstructOsr();
CheckInputs(loop, T.start, if_false1, if_true2);
CheckInputs(branch1, T.p0, loop);
CheckInputs(branch2, T.p0, if_true1);
CheckInputs(if_true1, branch1);
CheckInputs(if_false1, branch1);
CheckInputs(if_true2, branch2);
CheckInputs(if_false2, branch2);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.ZeroConstant(),
T.jsgraph.ZeroConstant(), loop);
CheckInputs(ret, osr_phi, T.start, if_false2);
}
struct While {
OsrDeconstructorTester& t;
Node* branch;
Node* if_true;
Node* exit;
Node* loop;
While(OsrDeconstructorTester& R, Node* cond, bool is_osr, int backedges = 1)
: t(R) {
loop = t.NewLoop(is_osr, backedges);
branch = t.graph.NewNode(t.common.Branch(), cond, loop);
if_true = t.graph.NewNode(t.common.IfTrue(), branch);
exit = t.graph.NewNode(t.common.IfFalse(), branch);
loop->ReplaceInput(loop->InputCount() - 1, if_true);
}
void Nest(While& that) {
that.loop->ReplaceInput(that.loop->InputCount() - 1, exit);
this->loop->ReplaceInput(0, that.if_true);
}
Node* Phi(Node* i1, Node* i2, Node* i3) {
if (loop->InputCount() == 2) {
return t.graph.NewNode(t.common.Phi(MachineRepresentation::kTagged, 2),
i1, i2, loop);
} else {
return t.graph.NewNode(t.common.Phi(MachineRepresentation::kTagged, 3),
i1, i2, i3, loop);
}
}
};
static Node* FindSuccessor(Node* node, IrOpcode::Value opcode) {
for (Node* use : node->uses()) {
if (use->opcode() == opcode) return use;
}
UNREACHABLE(); // should have been found.
return nullptr;
}
TEST(Deconstruct_osr_nested1) {
OsrDeconstructorTester T(1);
While outer(T, T.p0, false);
While inner(T, T.p0, true);
inner.Nest(outer);
Node* outer_phi = outer.Phi(T.p0, T.p0, nullptr);
outer.branch->ReplaceInput(0, outer_phi);
Node* osr_phi = inner.Phi(T.jsgraph.TrueConstant(), T.osr_values[0],
T.jsgraph.FalseConstant());
inner.branch->ReplaceInput(0, osr_phi);
outer_phi->ReplaceInput(1, osr_phi);
Node* ret =
T.graph.NewNode(T.common.Return(), outer_phi, T.start, outer.exit);
Node* end = T.graph.NewNode(T.common.End(1), ret);
T.graph.SetEnd(end);
T.DeconstructOsr();
// Check structure of deconstructed graph.
// Check inner OSR loop is directly connected to start.
CheckInputs(inner.loop, T.start, inner.if_true);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.FalseConstant(), inner.loop);
// Check control transfer to copy of outer loop.
Node* new_outer_loop = FindSuccessor(inner.exit, IrOpcode::kLoop);
Node* new_outer_phi = FindSuccessor(new_outer_loop, IrOpcode::kPhi);
CHECK_NE(new_outer_loop, outer.loop);
CHECK_NE(new_outer_phi, outer_phi);
CheckInputs(new_outer_loop, inner.exit, new_outer_loop->InputAt(1));
// Check structure of outer loop.
Node* new_outer_branch = FindSuccessor(new_outer_loop, IrOpcode::kBranch);
CHECK_NE(new_outer_branch, outer.branch);
CheckInputs(new_outer_branch, new_outer_phi, new_outer_loop);
Node* new_outer_exit = FindSuccessor(new_outer_branch, IrOpcode::kIfFalse);
Node* new_outer_if_true = FindSuccessor(new_outer_branch, IrOpcode::kIfTrue);
// Check structure of return.
end = T.graph.end();
Node* new_ret = end->InputAt(0);
CHECK_EQ(IrOpcode::kReturn, new_ret->opcode());
CheckInputs(new_ret, new_outer_phi, T.start, new_outer_exit);
// Check structure of inner loop.
Node* new_inner_loop = FindSuccessor(new_outer_if_true, IrOpcode::kLoop);
Node* new_inner_phi = FindSuccessor(new_inner_loop, IrOpcode::kPhi);
CheckInputs(new_inner_phi, T.jsgraph.TrueConstant(),
T.jsgraph.FalseConstant(), new_inner_loop);
CheckInputs(new_outer_phi, osr_phi, new_inner_phi, new_outer_loop);
}
TEST(Deconstruct_osr_nested2) {
OsrDeconstructorTester T(1);
// Test multiple backedge outer loop.
While outer(T, T.p0, false, 2);
While inner(T, T.p0, true);
inner.Nest(outer);
Node* outer_phi = outer.Phi(T.p0, T.p0, T.p0);
outer.branch->ReplaceInput(0, outer_phi);
Node* osr_phi = inner.Phi(T.jsgraph.TrueConstant(), T.osr_values[0],
T.jsgraph.FalseConstant());
inner.branch->ReplaceInput(0, osr_phi);
outer_phi->ReplaceInput(1, osr_phi);
outer_phi->ReplaceInput(2, T.jsgraph.FalseConstant());
Node* x_branch = T.graph.NewNode(T.common.Branch(), osr_phi, inner.exit);
Node* x_true = T.graph.NewNode(T.common.IfTrue(), x_branch);
Node* x_false = T.graph.NewNode(T.common.IfFalse(), x_branch);
outer.loop->ReplaceInput(1, x_true);
outer.loop->ReplaceInput(2, x_false);
Node* ret =
T.graph.NewNode(T.common.Return(), outer_phi, T.start, outer.exit);
Node* end = T.graph.NewNode(T.common.End(1), ret);
T.graph.SetEnd(end);
T.DeconstructOsr();
// Check structure of deconstructed graph.
// Check inner OSR loop is directly connected to start.
CheckInputs(inner.loop, T.start, inner.if_true);
CheckInputs(osr_phi, T.osr_values[0], T.jsgraph.FalseConstant(), inner.loop);
// Check control transfer to copy of outer loop.
Node* new_merge = FindSuccessor(x_true, IrOpcode::kMerge);
CHECK_EQ(new_merge, FindSuccessor(x_false, IrOpcode::kMerge));
CheckInputs(new_merge, x_true, x_false);
Node* new_outer_loop = FindSuccessor(new_merge, IrOpcode::kLoop);
Node* new_outer_phi = FindSuccessor(new_outer_loop, IrOpcode::kPhi);
CHECK_NE(new_outer_loop, outer.loop);
CHECK_NE(new_outer_phi, outer_phi);
Node* new_entry_phi = FindSuccessor(new_merge, IrOpcode::kPhi);
CheckInputs(new_entry_phi, osr_phi, T.jsgraph.FalseConstant(), new_merge);
CHECK_EQ(new_merge, new_outer_loop->InputAt(0));
// Check structure of outer loop.
Node* new_outer_branch = FindSuccessor(new_outer_loop, IrOpcode::kBranch);
CHECK_NE(new_outer_branch, outer.branch);
CheckInputs(new_outer_branch, new_outer_phi, new_outer_loop);
Node* new_outer_exit = FindSuccessor(new_outer_branch, IrOpcode::kIfFalse);
Node* new_outer_if_true = FindSuccessor(new_outer_branch, IrOpcode::kIfTrue);
// Check structure of return.
end = T.graph.end();
Node* new_ret = end->InputAt(0);
CHECK_EQ(IrOpcode::kReturn, new_ret->opcode());
CheckInputs(new_ret, new_outer_phi, T.start, new_outer_exit);
// Check structure of inner loop.
Node* new_inner_loop = FindSuccessor(new_outer_if_true, IrOpcode::kLoop);
Node* new_inner_phi = FindSuccessor(new_inner_loop, IrOpcode::kPhi);
CheckInputs(new_inner_phi, T.jsgraph.TrueConstant(),
T.jsgraph.FalseConstant(), new_inner_loop);
CheckInputs(new_outer_phi, new_entry_phi, new_inner_phi,
T.jsgraph.FalseConstant(), new_outer_loop);
}
Node* MakeCounter(JSGraph* jsgraph, Node* start, Node* loop) {
int count = loop->InputCount();
NodeVector tmp_inputs(jsgraph->graph()->zone());
for (int i = 0; i < count; i++) {
tmp_inputs.push_back(start);
}
tmp_inputs.push_back(loop);
Node* phi = jsgraph->graph()->NewNode(
jsgraph->common()->Phi(MachineRepresentation::kWord32, count), count + 1,
&tmp_inputs[0]);
Node* inc = jsgraph->graph()->NewNode(&kIntAdd, phi, jsgraph->OneConstant());
for (int i = 1; i < count; i++) {
phi->ReplaceInput(i, inc);
}
return phi;
}
TEST(Deconstruct_osr_nested3) {
OsrDeconstructorTester T(1);
// outermost loop.
While loop0(T, T.p0, false, 1);
Node* loop0_cntr = MakeCounter(&T.jsgraph, T.p0, loop0.loop);
loop0.branch->ReplaceInput(0, loop0_cntr);
// middle loop.
Node* loop1 = T.graph.NewNode(T.common.Loop(1), loop0.if_true);
Node* loop1_phi =
T.graph.NewNode(T.common.Phi(MachineRepresentation::kTagged, 2),
loop0_cntr, loop0_cntr, loop1);
// innermost (OSR) loop.
While loop2(T, T.p0, true, 1);
loop2.loop->ReplaceInput(0, loop1);
Node* loop2_cntr = MakeCounter(&T.jsgraph, loop1_phi, loop2.loop);
loop2_cntr->ReplaceInput(1, T.osr_values[0]);
Node* osr_phi = loop2_cntr;
Node* loop2_inc = loop2_cntr->InputAt(2);
loop2.branch->ReplaceInput(0, loop2_cntr);
loop1_phi->ReplaceInput(1, loop2_cntr);
loop0_cntr->ReplaceInput(1, loop2_cntr);
// Branch to either the outer or middle loop.
Node* branch = T.graph.NewNode(T.common.Branch(), loop2_cntr, loop2.exit);
Node* if_true = T.graph.NewNode(T.common.IfTrue(), branch);
Node* if_false = T.graph.NewNode(T.common.IfFalse(), branch);
loop0.loop->ReplaceInput(1, if_true);
loop1->AppendInput(T.graph.zone(), if_false);
NodeProperties::ChangeOp(loop1, T.common.Loop(2));
Node* ret =
T.graph.NewNode(T.common.Return(), loop0_cntr, T.start, loop0.exit);
Node* end = T.graph.NewNode(T.common.End(1), ret);
T.graph.SetEnd(end);
T.DeconstructOsr();
// Check structure of deconstructed graph.
// Check loop2 (OSR loop) is directly connected to start.
CheckInputs(loop2.loop, T.start, loop2.if_true);
CheckInputs(osr_phi, T.osr_values[0], loop2_inc, loop2.loop);
CheckInputs(loop2.branch, osr_phi, loop2.loop);
CheckInputs(loop2.if_true, loop2.branch);
CheckInputs(loop2.exit, loop2.branch);
CheckInputs(branch, osr_phi, loop2.exit);
CheckInputs(if_true, branch);
CheckInputs(if_false, branch);
// Check structure of new_loop1.
Node* new_loop1_loop = FindSuccessor(if_false, IrOpcode::kLoop);
// TODO(titzer): check the internal copy of loop2.
USE(new_loop1_loop);
// Check structure of new_loop0.
Node* new_loop0_loop_entry = FindSuccessor(if_true, IrOpcode::kMerge);
Node* new_loop0_loop = FindSuccessor(new_loop0_loop_entry, IrOpcode::kLoop);
// TODO(titzer): check the internal copies of loop1 and loop2.
Node* new_loop0_branch = FindSuccessor(new_loop0_loop, IrOpcode::kBranch);
Node* new_loop0_if_true = FindSuccessor(new_loop0_branch, IrOpcode::kIfTrue);
Node* new_loop0_exit = FindSuccessor(new_loop0_branch, IrOpcode::kIfFalse);
USE(new_loop0_if_true);
Node* new_ret = T.graph.end()->InputAt(0);
CHECK_EQ(IrOpcode::kReturn, new_ret->opcode());
Node* new_loop0_phi = new_ret->InputAt(0);
CHECK_EQ(IrOpcode::kPhi, new_loop0_phi->opcode());
CHECK_EQ(new_loop0_loop, NodeProperties::GetControlInput(new_loop0_phi));
CHECK_EQ(new_loop0_phi, FindSuccessor(new_loop0_loop, IrOpcode::kPhi));
// Check that the return returns the phi from the OSR loop and control
// depends on the copy of the outer loop0.
CheckInputs(new_ret, new_loop0_phi, T.graph.start(), new_loop0_exit);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,119 @@
// Copyright 2016 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.
// Flags: --allow-natives-syntax --expose-debug-as debug
var Debug = debug.Debug;
var changed = false;
function listenerSetJToResult(
event, exec_state, event_data, data) {
if (event == Debug.DebugEvent.Break) {
var scope = exec_state.frame(1).scope(0);
var newval = "result";
try {
scope.setVariableValue("j", newval);
changed = true;
} catch(e) {
changed = false;
}
}
}
Debug.setListener(listenerSetJToResult);
function g() { debugger; }
%NeverOptimizeFunction(g);
function ChangeSmiConstantAndOsr() {
var j = 1;
for (var i = 0; i < 4; i++) {
if (i == 2) {
%OptimizeOsr();
g();
}
}
return j;
}
var r1 = ChangeSmiConstantAndOsr();
if (changed) {
assertEquals("result", r1);
} else {
assertEquals(1, r1);
}
function ChangeFloatConstantAndOsr() {
var j = 0.1;
for (var i = 0; i < 4; i++) {
if (i == 2) {
%OptimizeOsr();
g();
}
}
return j;
}
var r2 = ChangeFloatConstantAndOsr();
if (changed) {
assertEquals("result", r2);
} else {
assertEquals(0.1, r2);
}
function ChangeFloatVarAndOsr() {
var j = 0.1;
for (var i = 0; i < 4; i++) {
j = j + 0.1;
if (i == 2) {
%OptimizeOsr();
g();
}
}
return j;
}
var r3 = ChangeFloatVarAndOsr();
if (changed) {
assertEquals("result0.1", r3);
} else {
assertEquals(0.5, r3);
}
var counter = 0;
var o = { toString : function() { counter++; return 100; } };
function listenerSetJToObject(
event, exec_state, event_data, data) {
if (event == Debug.DebugEvent.Break) {
var scope = exec_state.frame(1).scope(0);
try {
scope.setVariableValue("j", o);
changed = true;
} catch(e) {
changed = false;
}
}
}
Debug.setListener(listenerSetJToObject);
function ChangeIntVarAndOsr() {
var j = 1;
for (var i = 0; i < 4; i++) {
j = j + 1|0;
if (i == 2) {
%OptimizeOsr();
g();
}
}
return j;
}
var r4 = ChangeIntVarAndOsr();
if (changed) {
assertEquals(101, r4);
assertEquals(1, counter);
} else {
assertEquals(5, r4);
}