v8/test/unittests/compiler/loop-peeling-unittest.cc
Tobias Tebbi 4f48d04f97 [turbofan] introduce a deterministic tick measurement and assert optimization doesn't take too long
This adds a simple counter to Turbofan that's incremented throughout the compilation, hopefully
frequently enough so we can use it to detect divergence and performance bugs.
In addition, we assert that this counter never gets too high. That's the equivalent of a simple
timeout, just more deterministic. The limitations on Turbofan input size should guarantee that
we never exceed this limit. Since we probably do exceed it rarely, this check is only a DCHECK and
intended to detect performance and divergence issues, but not supposed to be performed in release
builds.

In addition, this CL adds UMA stats to observe the real world distribution of the tick measurement.

Bug: v8:9444

Change-Id: I182dac6ecac64715e3f5885ff5c7c17549351cd0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1695475
Commit-Queue: Tobias Tebbi <tebbi@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62754}
2019-07-17 07:00:00 +00:00

533 lines
17 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/compiler/access-builder.h"
#include "src/compiler/graph.h"
#include "src/compiler/graph-visualizer.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/loop-peeling.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "src/compiler/node-properties.h"
#include "test/unittests/compiler/compiler-test-utils.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "testing/gmock-support.h"
using testing::AllOf;
using testing::BitEq;
using testing::Capture;
using testing::CaptureEq;
namespace v8 {
namespace internal {
namespace compiler {
struct While {
Node* loop;
Node* branch;
Node* if_true;
Node* if_false;
Node* exit;
};
// A helper for building branches.
struct Branch {
Node* branch;
Node* if_true;
Node* if_false;
};
// A helper for building counters attached to loops.
struct Counter {
Node* base;
Node* inc;
Node* phi;
Node* add;
Node* exit_marker;
};
class LoopPeelingTest : public GraphTest {
public:
LoopPeelingTest() : GraphTest(1), machine_(zone()) {}
~LoopPeelingTest() override = default;
protected:
MachineOperatorBuilder machine_;
MachineOperatorBuilder* machine() { return &machine_; }
LoopTree* GetLoopTree() {
if (FLAG_trace_turbo_graph) {
StdoutStream{} << AsRPO(*graph());
}
Zone zone(isolate()->allocator(), ZONE_NAME);
return LoopFinder::BuildLoopTree(graph(), tick_counter(), &zone);
}
PeeledIteration* PeelOne() {
LoopTree* loop_tree = GetLoopTree();
LoopTree::Loop* loop = loop_tree->outer_loops()[0];
LoopPeeler peeler(graph(), common(), loop_tree, zone(), source_positions(),
node_origins());
EXPECT_TRUE(peeler.CanPeel(loop));
return Peel(peeler, loop);
}
PeeledIteration* Peel(LoopPeeler peeler, LoopTree::Loop* loop) {
EXPECT_TRUE(peeler.CanPeel(loop));
PeeledIteration* peeled = peeler.Peel(loop);
if (FLAG_trace_turbo_graph) {
StdoutStream{} << AsRPO(*graph());
}
return peeled;
}
Node* InsertReturn(Node* val, Node* effect, Node* control) {
Node* zero = graph()->NewNode(common()->Int32Constant(0));
Node* r = graph()->NewNode(common()->Return(), zero, val, effect, control);
graph()->SetEnd(r);
return r;
}
Node* ExpectPeeled(Node* node, PeeledIteration* iter) {
Node* p = iter->map(node);
EXPECT_NE(node, p);
return p;
}
void ExpectNotPeeled(Node* node, PeeledIteration* iter) {
EXPECT_EQ(node, iter->map(node));
}
While NewWhile(Node* cond, Node* control = nullptr) {
if (control == nullptr) control = start();
While w;
w.loop = graph()->NewNode(common()->Loop(2), control, control);
w.branch = graph()->NewNode(common()->Branch(), cond, w.loop);
w.if_true = graph()->NewNode(common()->IfTrue(), w.branch);
w.if_false = graph()->NewNode(common()->IfFalse(), w.branch);
w.exit = graph()->NewNode(common()->LoopExit(), w.if_false, w.loop);
w.loop->ReplaceInput(1, w.if_true);
return w;
}
void Chain(While* a, Node* control) { a->loop->ReplaceInput(0, control); }
void Nest(While* a, While* b) {
b->loop->ReplaceInput(1, a->exit);
a->loop->ReplaceInput(0, b->if_true);
}
Node* NewPhi(While* w, Node* a, Node* b) {
return graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), a,
b, w->loop);
}
Branch NewBranch(Node* cond, Node* control = nullptr) {
Branch b;
if (control == nullptr) control = start();
b.branch = graph()->NewNode(common()->Branch(), cond, control);
b.if_true = graph()->NewNode(common()->IfTrue(), b.branch);
b.if_false = graph()->NewNode(common()->IfFalse(), b.branch);
return b;
}
Counter NewCounter(While* w, int32_t b, int32_t k) {
Counter c;
c.base = Int32Constant(b);
c.inc = Int32Constant(k);
c.phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
c.base, c.base, w->loop);
c.add = graph()->NewNode(machine()->Int32Add(), c.phi, c.inc);
c.phi->ReplaceInput(1, c.add);
c.exit_marker = graph()->NewNode(common()->LoopExitValue(), c.phi, w->exit);
return c;
}
};
TEST_F(LoopPeelingTest, SimpleLoop) {
Node* p0 = Parameter(0);
While w = NewWhile(p0);
Node* r = InsertReturn(p0, start(), w.exit);
PeeledIteration* peeled = PeelOne();
Node* br1 = ExpectPeeled(w.branch, peeled);
Node* if_true1 = ExpectPeeled(w.if_true, peeled);
Node* if_false1 = ExpectPeeled(w.if_false, peeled);
EXPECT_THAT(br1, IsBranch(p0, start()));
EXPECT_THAT(if_true1, IsIfTrue(br1));
EXPECT_THAT(if_false1, IsIfFalse(br1));
EXPECT_THAT(w.loop, IsLoop(if_true1, w.if_true));
EXPECT_THAT(r, IsReturn(p0, start(), IsMerge(w.if_false, if_false1)));
}
TEST_F(LoopPeelingTest, SimpleLoopWithCounter) {
Node* p0 = Parameter(0);
While w = NewWhile(p0);
Counter c = NewCounter(&w, 0, 1);
Node* r = InsertReturn(c.exit_marker, start(), w.exit);
PeeledIteration* peeled = PeelOne();
Node* br1 = ExpectPeeled(w.branch, peeled);
Node* if_true1 = ExpectPeeled(w.if_true, peeled);
Node* if_false1 = ExpectPeeled(w.if_false, peeled);
EXPECT_THAT(br1, IsBranch(p0, start()));
EXPECT_THAT(if_true1, IsIfTrue(br1));
EXPECT_THAT(if_false1, IsIfFalse(br1));
EXPECT_THAT(w.loop, IsLoop(if_true1, w.if_true));
EXPECT_THAT(peeled->map(c.add), IsInt32Add(c.base, c.inc));
EXPECT_THAT(w.exit, IsMerge(w.if_false, if_false1));
EXPECT_THAT(
r, IsReturn(IsPhi(MachineRepresentation::kTagged, c.phi, c.base, w.exit),
start(), w.exit));
}
TEST_F(LoopPeelingTest, SimpleNestedLoopWithCounter_peel_outer) {
Node* p0 = Parameter(0);
While outer = NewWhile(p0);
While inner = NewWhile(p0);
Nest(&inner, &outer);
Counter c = NewCounter(&outer, 0, 1);
Node* r = InsertReturn(c.exit_marker, start(), outer.exit);
PeeledIteration* peeled = PeelOne();
Node* bro = ExpectPeeled(outer.branch, peeled);
Node* if_trueo = ExpectPeeled(outer.if_true, peeled);
Node* if_falseo = ExpectPeeled(outer.if_false, peeled);
EXPECT_THAT(bro, IsBranch(p0, start()));
EXPECT_THAT(if_trueo, IsIfTrue(bro));
EXPECT_THAT(if_falseo, IsIfFalse(bro));
Node* bri = ExpectPeeled(inner.branch, peeled);
Node* if_truei = ExpectPeeled(inner.if_true, peeled);
Node* if_falsei = ExpectPeeled(inner.if_false, peeled);
Node* exiti = ExpectPeeled(inner.exit, peeled);
EXPECT_THAT(bri, IsBranch(p0, ExpectPeeled(inner.loop, peeled)));
EXPECT_THAT(if_truei, IsIfTrue(bri));
EXPECT_THAT(if_falsei, IsIfFalse(bri));
EXPECT_THAT(outer.loop, IsLoop(exiti, inner.exit));
EXPECT_THAT(peeled->map(c.add), IsInt32Add(c.base, c.inc));
Capture<Node*> merge;
EXPECT_THAT(outer.exit, IsMerge(outer.if_false, if_falseo));
EXPECT_THAT(r, IsReturn(IsPhi(MachineRepresentation::kTagged, c.phi, c.base,
outer.exit),
start(), outer.exit));
}
TEST_F(LoopPeelingTest, SimpleNestedLoopWithCounter_peel_inner) {
Node* p0 = Parameter(0);
While outer = NewWhile(p0);
While inner = NewWhile(p0);
Nest(&inner, &outer);
Counter c = NewCounter(&outer, 0, 1);
Node* r = InsertReturn(c.exit_marker, start(), outer.exit);
LoopTree* loop_tree = GetLoopTree();
LoopTree::Loop* loop = loop_tree->ContainingLoop(inner.loop);
EXPECT_NE(nullptr, loop);
EXPECT_EQ(1u, loop->depth());
LoopPeeler peeler(graph(), common(), loop_tree, zone(), source_positions(),
node_origins());
PeeledIteration* peeled = Peel(peeler, loop);
ExpectNotPeeled(outer.loop, peeled);
ExpectNotPeeled(outer.branch, peeled);
ExpectNotPeeled(outer.if_true, peeled);
ExpectNotPeeled(outer.if_false, peeled);
ExpectNotPeeled(outer.exit, peeled);
Node* bri = ExpectPeeled(inner.branch, peeled);
Node* if_truei = ExpectPeeled(inner.if_true, peeled);
Node* if_falsei = ExpectPeeled(inner.if_false, peeled);
EXPECT_THAT(bri, IsBranch(p0, ExpectPeeled(inner.loop, peeled)));
EXPECT_THAT(if_truei, IsIfTrue(bri));
EXPECT_THAT(if_falsei, IsIfFalse(bri));
EXPECT_THAT(inner.exit, IsMerge(inner.if_false, if_falsei));
EXPECT_THAT(outer.loop, IsLoop(start(), inner.exit));
ExpectNotPeeled(c.add, peeled);
EXPECT_THAT(r, IsReturn(c.exit_marker, start(), outer.exit));
}
TEST_F(LoopPeelingTest, SimpleInnerCounter_peel_inner) {
Node* p0 = Parameter(0);
While outer = NewWhile(p0);
While inner = NewWhile(p0);
Nest(&inner, &outer);
Counter c = NewCounter(&inner, 0, 1);
Node* phi = NewPhi(&outer, Int32Constant(11), c.exit_marker);
Node* r = InsertReturn(phi, start(), outer.exit);
LoopTree* loop_tree = GetLoopTree();
LoopTree::Loop* loop = loop_tree->ContainingLoop(inner.loop);
EXPECT_NE(nullptr, loop);
EXPECT_EQ(1u, loop->depth());
LoopPeeler peeler(graph(), common(), loop_tree, zone(), source_positions(),
node_origins());
PeeledIteration* peeled = Peel(peeler, loop);
ExpectNotPeeled(outer.loop, peeled);
ExpectNotPeeled(outer.branch, peeled);
ExpectNotPeeled(outer.if_true, peeled);
ExpectNotPeeled(outer.if_false, peeled);
ExpectNotPeeled(outer.exit, peeled);
Node* bri = ExpectPeeled(inner.branch, peeled);
Node* if_truei = ExpectPeeled(inner.if_true, peeled);
Node* if_falsei = ExpectPeeled(inner.if_false, peeled);
EXPECT_THAT(bri, IsBranch(p0, ExpectPeeled(inner.loop, peeled)));
EXPECT_THAT(if_truei, IsIfTrue(bri));
EXPECT_THAT(if_falsei, IsIfFalse(bri));
EXPECT_THAT(inner.exit, IsMerge(inner.if_false, if_falsei));
EXPECT_THAT(outer.loop, IsLoop(start(), inner.exit));
EXPECT_THAT(peeled->map(c.add), IsInt32Add(c.base, c.inc));
EXPECT_THAT(c.exit_marker,
IsPhi(MachineRepresentation::kTagged, c.phi, c.base, inner.exit));
EXPECT_THAT(phi, IsPhi(MachineRepresentation::kTagged, IsInt32Constant(11),
c.exit_marker, outer.loop));
EXPECT_THAT(r, IsReturn(phi, start(), outer.exit));
}
TEST_F(LoopPeelingTest, TwoBackedgeLoop) {
Node* p0 = Parameter(0);
Node* loop = graph()->NewNode(common()->Loop(3), start(), start(), start());
Branch b1 = NewBranch(p0, loop);
Branch b2 = NewBranch(p0, b1.if_true);
loop->ReplaceInput(1, b2.if_true);
loop->ReplaceInput(2, b2.if_false);
Node* exit = graph()->NewNode(common()->LoopExit(), b1.if_false, loop);
Node* r = InsertReturn(p0, start(), exit);
PeeledIteration* peeled = PeelOne();
Node* b1b = ExpectPeeled(b1.branch, peeled);
Node* b1t = ExpectPeeled(b1.if_true, peeled);
Node* b1f = ExpectPeeled(b1.if_false, peeled);
EXPECT_THAT(b1b, IsBranch(p0, start()));
EXPECT_THAT(ExpectPeeled(b1.if_true, peeled), IsIfTrue(b1b));
EXPECT_THAT(b1f, IsIfFalse(b1b));
Node* b2b = ExpectPeeled(b2.branch, peeled);
Node* b2t = ExpectPeeled(b2.if_true, peeled);
Node* b2f = ExpectPeeled(b2.if_false, peeled);
EXPECT_THAT(b2b, IsBranch(p0, b1t));
EXPECT_THAT(b2t, IsIfTrue(b2b));
EXPECT_THAT(b2f, IsIfFalse(b2b));
EXPECT_THAT(loop, IsLoop(IsMerge(b2t, b2f), b2.if_true, b2.if_false));
EXPECT_THAT(exit, IsMerge(b1.if_false, b1f));
EXPECT_THAT(r, IsReturn(p0, start(), exit));
}
TEST_F(LoopPeelingTest, TwoBackedgeLoopWithPhi) {
Node* p0 = Parameter(0);
Node* loop = graph()->NewNode(common()->Loop(3), start(), start(), start());
Branch b1 = NewBranch(p0, loop);
Branch b2 = NewBranch(p0, b1.if_true);
Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 3),
Int32Constant(0), Int32Constant(1),
Int32Constant(2), loop);
loop->ReplaceInput(1, b2.if_true);
loop->ReplaceInput(2, b2.if_false);
Node* exit = graph()->NewNode(common()->LoopExit(), b1.if_false, loop);
Node* exit_marker = graph()->NewNode(common()->LoopExitValue(), phi, exit);
Node* r = InsertReturn(exit_marker, start(), exit);
PeeledIteration* peeled = PeelOne();
Node* b1b = ExpectPeeled(b1.branch, peeled);
Node* b1t = ExpectPeeled(b1.if_true, peeled);
Node* b1f = ExpectPeeled(b1.if_false, peeled);
EXPECT_THAT(b1b, IsBranch(p0, start()));
EXPECT_THAT(ExpectPeeled(b1.if_true, peeled), IsIfTrue(b1b));
EXPECT_THAT(b1f, IsIfFalse(b1b));
Node* b2b = ExpectPeeled(b2.branch, peeled);
Node* b2t = ExpectPeeled(b2.if_true, peeled);
Node* b2f = ExpectPeeled(b2.if_false, peeled);
EXPECT_THAT(b2b, IsBranch(p0, b1t));
EXPECT_THAT(b2t, IsIfTrue(b2b));
EXPECT_THAT(b2f, IsIfFalse(b2b));
EXPECT_THAT(loop, IsLoop(IsMerge(b2t, b2f), b2.if_true, b2.if_false));
EXPECT_THAT(phi,
IsPhi(MachineRepresentation::kTagged,
IsPhi(MachineRepresentation::kTagged, IsInt32Constant(1),
IsInt32Constant(2), IsMerge(b2t, b2f)),
IsInt32Constant(1), IsInt32Constant(2), loop));
EXPECT_THAT(exit, IsMerge(b1.if_false, b1f));
EXPECT_THAT(exit_marker, IsPhi(MachineRepresentation::kTagged, phi,
IsInt32Constant(0), exit));
EXPECT_THAT(r, IsReturn(exit_marker, start(), exit));
}
TEST_F(LoopPeelingTest, TwoBackedgeLoopWithCounter) {
Node* p0 = Parameter(0);
Node* loop = graph()->NewNode(common()->Loop(3), start(), start(), start());
Branch b1 = NewBranch(p0, loop);
Branch b2 = NewBranch(p0, b1.if_true);
Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 3),
Int32Constant(0), Int32Constant(1),
Int32Constant(2), loop);
phi->ReplaceInput(
1, graph()->NewNode(machine()->Int32Add(), phi, Int32Constant(1)));
phi->ReplaceInput(
2, graph()->NewNode(machine()->Int32Add(), phi, Int32Constant(2)));
loop->ReplaceInput(1, b2.if_true);
loop->ReplaceInput(2, b2.if_false);
Node* exit = graph()->NewNode(common()->LoopExit(), b1.if_false, loop);
Node* exit_marker = graph()->NewNode(common()->LoopExitValue(), phi, exit);
Node* r = InsertReturn(exit_marker, start(), exit);
PeeledIteration* peeled = PeelOne();
Node* b1b = ExpectPeeled(b1.branch, peeled);
Node* b1t = ExpectPeeled(b1.if_true, peeled);
Node* b1f = ExpectPeeled(b1.if_false, peeled);
EXPECT_THAT(b1b, IsBranch(p0, start()));
EXPECT_THAT(ExpectPeeled(b1.if_true, peeled), IsIfTrue(b1b));
EXPECT_THAT(b1f, IsIfFalse(b1b));
Node* b2b = ExpectPeeled(b2.branch, peeled);
Node* b2t = ExpectPeeled(b2.if_true, peeled);
Node* b2f = ExpectPeeled(b2.if_false, peeled);
EXPECT_THAT(b2b, IsBranch(p0, b1t));
EXPECT_THAT(b2t, IsIfTrue(b2b));
EXPECT_THAT(b2f, IsIfFalse(b2b));
Capture<Node*> entry;
EXPECT_THAT(loop, IsLoop(AllOf(CaptureEq(&entry), IsMerge(b2t, b2f)),
b2.if_true, b2.if_false));
Node* eval = phi->InputAt(0);
EXPECT_THAT(eval, IsPhi(MachineRepresentation::kTagged,
IsInt32Add(IsInt32Constant(0), IsInt32Constant(1)),
IsInt32Add(IsInt32Constant(0), IsInt32Constant(2)),
CaptureEq(&entry)));
EXPECT_THAT(phi, IsPhi(MachineRepresentation::kTagged, eval,
IsInt32Add(phi, IsInt32Constant(1)),
IsInt32Add(phi, IsInt32Constant(2)), loop));
EXPECT_THAT(exit, IsMerge(b1.if_false, b1f));
EXPECT_THAT(exit_marker, IsPhi(MachineRepresentation::kTagged, phi,
IsInt32Constant(0), exit));
EXPECT_THAT(r, IsReturn(exit_marker, start(), exit));
}
TEST_F(LoopPeelingTest, TwoExitLoop) {
Node* p0 = Parameter(0);
Node* loop = graph()->NewNode(common()->Loop(2), start(), start());
Branch b1 = NewBranch(p0, loop);
Branch b2 = NewBranch(p0, b1.if_true);
loop->ReplaceInput(1, b2.if_true);
Node* exit1 = graph()->NewNode(common()->LoopExit(), b1.if_false, loop);
Node* exit2 = graph()->NewNode(common()->LoopExit(), b2.if_false, loop);
Node* merge = graph()->NewNode(common()->Merge(2), exit1, exit2);
Node* r = InsertReturn(p0, start(), merge);
PeeledIteration* peeled = PeelOne();
Node* b1p = ExpectPeeled(b1.branch, peeled);
Node* if_true1p = ExpectPeeled(b1.if_true, peeled);
Node* if_false1p = ExpectPeeled(b1.if_false, peeled);
Node* b2p = ExpectPeeled(b2.branch, peeled);
Node* if_true2p = ExpectPeeled(b2.if_true, peeled);
Node* if_false2p = ExpectPeeled(b2.if_false, peeled);
EXPECT_THAT(b1p, IsBranch(p0, start()));
EXPECT_THAT(if_true1p, IsIfTrue(b1p));
EXPECT_THAT(if_false1p, IsIfFalse(b1p));
EXPECT_THAT(b2p, IsBranch(p0, if_true1p));
EXPECT_THAT(if_true2p, IsIfTrue(b2p));
EXPECT_THAT(if_false2p, IsIfFalse(b2p));
EXPECT_THAT(exit1, IsMerge(b1.if_false, if_false1p));
EXPECT_THAT(exit2, IsMerge(b2.if_false, if_false2p));
EXPECT_THAT(loop, IsLoop(if_true2p, b2.if_true));
EXPECT_THAT(merge, IsMerge(exit1, exit2));
EXPECT_THAT(r, IsReturn(p0, start(), merge));
}
TEST_F(LoopPeelingTest, SimpleLoopWithUnmarkedExit) {
Node* p0 = Parameter(0);
Node* loop = graph()->NewNode(common()->Loop(2), start(), start());
Branch b = NewBranch(p0, loop);
loop->ReplaceInput(1, b.if_true);
InsertReturn(p0, start(), b.if_false);
{
LoopTree* loop_tree = GetLoopTree();
LoopTree::Loop* loop = loop_tree->outer_loops()[0];
LoopPeeler peeler(graph(), common(), loop_tree, zone(), source_positions(),
node_origins());
EXPECT_FALSE(peeler.CanPeel(loop));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8