v8/test/cctest/compiler/test-osr.cc
mstarzinger b7990793cf [turbofan] Move SimplifiedOperatorBuilder into JSGraph.
This fixes the lifetime of nodes created by JSGlobalSpecialization that
contain a simplified operator. In the case where this reducer runs as
part of the inliner, the SimplifiedOperatorBuilder was instantiated with
the wrong zone. This led to use-after-free of simplified operators.

To avoid such situations in the future, we decided to move this operator
builder into the JSGraph and make the situation uniform with all other
operator builders.

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

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

Cr-Commit-Position: refs/heads/master@{#31334}
2015-10-16 12:38:52 +00:00

566 lines
19 KiB
C++

// 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"
using namespace v8::internal;
using namespace v8::internal::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(kMachAnyTagged, 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.live) {
for (Node* const use : node->uses()) {
CHECK(std::find(nodes.live.begin(), nodes.live.end(), use) !=
nodes.live.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(kMachAnyTagged, 2), i1, i2, loop);
} else {
return t.graph.NewNode(t.common.Phi(kMachAnyTagged, 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(kMachInt32, 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(kMachAnyTagged, 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);
}