| Index: test/unittests/compiler/liveness-analyzer-unittest.cc
|
| diff --git a/test/unittests/compiler/liveness-analyzer-unittest.cc b/test/unittests/compiler/liveness-analyzer-unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b59c71aeb923c23501af86e82597b92c3109f067
|
| --- /dev/null
|
| +++ b/test/unittests/compiler/liveness-analyzer-unittest.cc
|
| @@ -0,0 +1,373 @@
|
| +// 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/compiler/js-graph.h"
|
| +#include "src/compiler/linkage.h"
|
| +#include "src/compiler/liveness-analyzer.h"
|
| +#include "src/compiler/node-matchers.h"
|
| +#include "src/compiler/state-values-utils.h"
|
| +#include "test/unittests/compiler/graph-unittest.h"
|
| +#include "test/unittests/compiler/node-test-utils.h"
|
| +
|
| +using testing::MakeMatcher;
|
| +using testing::MatcherInterface;
|
| +using testing::MatchResultListener;
|
| +using testing::StringMatchResultListener;
|
| +
|
| +namespace v8 {
|
| +namespace internal {
|
| +namespace compiler {
|
| +
|
| +class LivenessAnalysisTest : public GraphTest {
|
| + public:
|
| + explicit LivenessAnalysisTest(int locals_count = 4)
|
| + : locals_count_(locals_count),
|
| + machine_(zone(), kRepWord32),
|
| + javascript_(zone()),
|
| + jsgraph_(isolate(), graph(), common(), &javascript_, &machine_),
|
| + analyzer_(locals_count, zone()),
|
| + empty_values_(graph()->NewNode(common()->StateValues(0), 0, nullptr)),
|
| + next_checkpoint_id_(0),
|
| + current_block_(nullptr) {}
|
| +
|
| +
|
| + protected:
|
| + JSGraph* jsgraph() { return &jsgraph_; }
|
| +
|
| + LivenessAnalyzer* analyzer() { return &analyzer_; }
|
| + void Run() {
|
| + StateValuesCache cache(jsgraph());
|
| + NonLiveFrameStateSlotReplacer replacer(&cache,
|
| + jsgraph()->UndefinedConstant(),
|
| + analyzer()->local_count(), zone());
|
| + analyzer()->Run(&replacer);
|
| + }
|
| +
|
| + Node* Checkpoint() {
|
| + int ast_num = next_checkpoint_id_++;
|
| + int first_const = intconst_from_bailout_id(ast_num, locals_count_);
|
| +
|
| + const Operator* locals_op = common()->StateValues(locals_count_);
|
| +
|
| + ZoneVector<Node*> local_inputs(locals_count_, nullptr, zone());
|
| + for (int i = 0; i < locals_count_; i++) {
|
| + local_inputs[i] = jsgraph()->Int32Constant(i + first_const);
|
| + }
|
| + Node* locals =
|
| + graph()->NewNode(locals_op, locals_count_, &local_inputs.front());
|
| +
|
| + const Operator* op = common()->FrameState(
|
| + JS_FRAME, BailoutId(ast_num), OutputFrameStateCombine::Ignore());
|
| + Node* result = graph()->NewNode(op, empty_values_, locals, empty_values_,
|
| + jsgraph()->UndefinedConstant(),
|
| + jsgraph()->UndefinedConstant());
|
| +
|
| + current_block_->Checkpoint(result);
|
| + return result;
|
| + }
|
| +
|
| + void Bind(int var) { current_block()->Bind(var); }
|
| + void Lookup(int var) { current_block()->Lookup(var); }
|
| +
|
| + class CheckpointMatcher : public MatcherInterface<Node*> {
|
| + public:
|
| + explicit CheckpointMatcher(const char* liveness, Node* empty_values,
|
| + int locals_count, Node* replacement)
|
| + : liveness_(liveness),
|
| + empty_values_(empty_values),
|
| + locals_count_(locals_count),
|
| + replacement_(replacement) {}
|
| +
|
| + void DescribeTo(std::ostream* os) const OVERRIDE {
|
| + *os << "is a frame state with '" << liveness_
|
| + << "' liveness, empty "
|
| + "parameters and empty expression stack";
|
| + }
|
| +
|
| + bool MatchAndExplain(Node* frame_state,
|
| + MatchResultListener* listener) const OVERRIDE {
|
| + if (frame_state == NULL) {
|
| + *listener << "which is NULL";
|
| + return false;
|
| + }
|
| + DCHECK(frame_state->opcode() == IrOpcode::kFrameState);
|
| +
|
| + FrameStateCallInfo state_info =
|
| + OpParameter<FrameStateCallInfo>(frame_state);
|
| + int ast_num = state_info.bailout_id().ToInt();
|
| + int first_const = intconst_from_bailout_id(ast_num, locals_count_);
|
| +
|
| + if (empty_values_ != frame_state->InputAt(0)) {
|
| + *listener << "whose parameters are " << frame_state->InputAt(0)
|
| + << " but should have been " << empty_values_ << " (empty)";
|
| + return false;
|
| + }
|
| + if (empty_values_ != frame_state->InputAt(2)) {
|
| + *listener << "whose expression stack is " << frame_state->InputAt(2)
|
| + << " but should have been " << empty_values_ << " (empty)";
|
| + return false;
|
| + }
|
| + StateValuesAccess locals(frame_state->InputAt(1));
|
| + if (locals_count_ != static_cast<int>(locals.size())) {
|
| + *listener << "whose number of locals is " << locals.size()
|
| + << " but should have been " << locals_count_;
|
| + return false;
|
| + }
|
| + int i = 0;
|
| + for (Node* value : locals) {
|
| + if (liveness_[i] == 'L') {
|
| + StringMatchResultListener value_listener;
|
| + if (value == replacement_) {
|
| + *listener << "whose local #" << i << " was " << value->opcode()
|
| + << " but should have been 'undefined'";
|
| + return false;
|
| + } else if (!IsInt32Constant(first_const + i)
|
| + .MatchAndExplain(value, &value_listener)) {
|
| + *listener << "whose local #" << i << " does not match";
|
| + if (value_listener.str() != "") {
|
| + *listener << ", " << value_listener.str();
|
| + }
|
| + return false;
|
| + }
|
| + } else if (liveness_[i] == '.') {
|
| + if (value != replacement_) {
|
| + *listener << "whose local #" << i << " is " << value
|
| + << " but should have been " << replacement_
|
| + << " (undefined)";
|
| + return false;
|
| + }
|
| + } else {
|
| + UNREACHABLE();
|
| + }
|
| + i++;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + private:
|
| + const char* liveness_;
|
| + Node* empty_values_;
|
| + int locals_count_;
|
| + Node* replacement_;
|
| + };
|
| +
|
| + Matcher<Node*> IsCheckpointModuloLiveness(const char* liveness) {
|
| + return MakeMatcher(new CheckpointMatcher(liveness, empty_values_,
|
| + locals_count_,
|
| + jsgraph()->UndefinedConstant()));
|
| + }
|
| +
|
| + LivenessAnalyzerBlock* current_block() { return current_block_; }
|
| + void set_current_block(LivenessAnalyzerBlock* block) {
|
| + current_block_ = block;
|
| + }
|
| +
|
| + private:
|
| + static int intconst_from_bailout_id(int ast_num, int locals_count) {
|
| + return (locals_count + 1) * ast_num + 1;
|
| + }
|
| +
|
| + int locals_count_;
|
| + MachineOperatorBuilder machine_;
|
| + JSOperatorBuilder javascript_;
|
| + JSGraph jsgraph_;
|
| + LivenessAnalyzer analyzer_;
|
| + Node* empty_values_;
|
| + int next_checkpoint_id_;
|
| + LivenessAnalyzerBlock* current_block_;
|
| +};
|
| +
|
| +
|
| +TEST_F(LivenessAnalysisTest, EmptyBlock) {
|
| + set_current_block(analyzer()->NewBlock());
|
| +
|
| + Node* c1 = Checkpoint();
|
| +
|
| + Run();
|
| +
|
| + // Nothing is live.
|
| + EXPECT_THAT(c1, IsCheckpointModuloLiveness("...."));
|
| +}
|
| +
|
| +
|
| +TEST_F(LivenessAnalysisTest, SimpleLookup) {
|
| + set_current_block(analyzer()->NewBlock());
|
| +
|
| + Node* c1 = Checkpoint();
|
| + Lookup(1);
|
| + Node* c2 = Checkpoint();
|
| +
|
| + Run();
|
| +
|
| + EXPECT_THAT(c1, IsCheckpointModuloLiveness(".L.."));
|
| + EXPECT_THAT(c2, IsCheckpointModuloLiveness("...."));
|
| +}
|
| +
|
| +
|
| +TEST_F(LivenessAnalysisTest, DiamondLookups) {
|
| + // Start block.
|
| + LivenessAnalyzerBlock* start = analyzer()->NewBlock();
|
| + set_current_block(start);
|
| + Node* c1_start = Checkpoint();
|
| +
|
| + // First branch.
|
| + LivenessAnalyzerBlock* b1 = analyzer()->NewBlock(start);
|
| + set_current_block(b1);
|
| +
|
| + Node* c1_b1 = Checkpoint();
|
| + Lookup(1);
|
| + Node* c2_b1 = Checkpoint();
|
| + Lookup(3);
|
| + Node* c3_b1 = Checkpoint();
|
| +
|
| + // Second branch.
|
| + LivenessAnalyzerBlock* b2 = analyzer()->NewBlock(start);
|
| + set_current_block(b2);
|
| +
|
| + Node* c1_b2 = Checkpoint();
|
| + Lookup(3);
|
| + Node* c2_b2 = Checkpoint();
|
| + Lookup(2);
|
| + Node* c3_b2 = Checkpoint();
|
| +
|
| + // Merge block.
|
| + LivenessAnalyzerBlock* m = analyzer()->NewBlock(b1);
|
| + m->AddPredecessor(b2);
|
| + set_current_block(m);
|
| + Node* c1_m = Checkpoint();
|
| + Lookup(0);
|
| + Node* c2_m = Checkpoint();
|
| +
|
| + Run();
|
| +
|
| + EXPECT_THAT(c1_start, IsCheckpointModuloLiveness("LLLL"));
|
| +
|
| + EXPECT_THAT(c1_b1, IsCheckpointModuloLiveness("LL.L"));
|
| + EXPECT_THAT(c2_b1, IsCheckpointModuloLiveness("L..L"));
|
| + EXPECT_THAT(c3_b1, IsCheckpointModuloLiveness("L..."));
|
| +
|
| + EXPECT_THAT(c1_b2, IsCheckpointModuloLiveness("L.LL"));
|
| + EXPECT_THAT(c2_b2, IsCheckpointModuloLiveness("L.L."));
|
| + EXPECT_THAT(c3_b2, IsCheckpointModuloLiveness("L..."));
|
| +
|
| + EXPECT_THAT(c1_m, IsCheckpointModuloLiveness("L..."));
|
| + EXPECT_THAT(c2_m, IsCheckpointModuloLiveness("...."));
|
| +}
|
| +
|
| +
|
| +TEST_F(LivenessAnalysisTest, DiamondLookupsAndBinds) {
|
| + // Start block.
|
| + LivenessAnalyzerBlock* start = analyzer()->NewBlock();
|
| + set_current_block(start);
|
| + Node* c1_start = Checkpoint();
|
| + Bind(0);
|
| + Node* c2_start = Checkpoint();
|
| +
|
| + // First branch.
|
| + LivenessAnalyzerBlock* b1 = analyzer()->NewBlock(start);
|
| + set_current_block(b1);
|
| +
|
| + Node* c1_b1 = Checkpoint();
|
| + Bind(2);
|
| + Bind(1);
|
| + Node* c2_b1 = Checkpoint();
|
| + Bind(3);
|
| + Node* c3_b1 = Checkpoint();
|
| +
|
| + // Second branch.
|
| + LivenessAnalyzerBlock* b2 = analyzer()->NewBlock(start);
|
| + set_current_block(b2);
|
| +
|
| + Node* c1_b2 = Checkpoint();
|
| + Lookup(2);
|
| + Node* c2_b2 = Checkpoint();
|
| + Bind(2);
|
| + Bind(3);
|
| + Node* c3_b2 = Checkpoint();
|
| +
|
| + // Merge block.
|
| + LivenessAnalyzerBlock* m = analyzer()->NewBlock(b1);
|
| + m->AddPredecessor(b2);
|
| + set_current_block(m);
|
| + Node* c1_m = Checkpoint();
|
| + Lookup(0);
|
| + Lookup(1);
|
| + Lookup(2);
|
| + Lookup(3);
|
| + Node* c2_m = Checkpoint();
|
| +
|
| + Run();
|
| +
|
| + EXPECT_THAT(c1_start, IsCheckpointModuloLiveness(".LL."));
|
| + EXPECT_THAT(c2_start, IsCheckpointModuloLiveness("LLL."));
|
| +
|
| + EXPECT_THAT(c1_b1, IsCheckpointModuloLiveness("L..."));
|
| + EXPECT_THAT(c2_b1, IsCheckpointModuloLiveness("LLL."));
|
| + EXPECT_THAT(c3_b1, IsCheckpointModuloLiveness("LLLL"));
|
| +
|
| + EXPECT_THAT(c1_b2, IsCheckpointModuloLiveness("LLL."));
|
| + EXPECT_THAT(c2_b2, IsCheckpointModuloLiveness("LL.."));
|
| + EXPECT_THAT(c3_b2, IsCheckpointModuloLiveness("LLLL"));
|
| +
|
| + EXPECT_THAT(c1_m, IsCheckpointModuloLiveness("LLLL"));
|
| + EXPECT_THAT(c2_m, IsCheckpointModuloLiveness("...."));
|
| +}
|
| +
|
| +
|
| +TEST_F(LivenessAnalysisTest, SimpleLoop) {
|
| + // Start block.
|
| + LivenessAnalyzerBlock* start = analyzer()->NewBlock();
|
| + set_current_block(start);
|
| + Node* c1_start = Checkpoint();
|
| + Bind(0);
|
| + Bind(1);
|
| + Bind(2);
|
| + Bind(3);
|
| + Node* c2_start = Checkpoint();
|
| +
|
| + // Loop header block.
|
| + LivenessAnalyzerBlock* header = analyzer()->NewBlock(start);
|
| + set_current_block(header);
|
| + Node* c1_header = Checkpoint();
|
| + Lookup(0);
|
| + Bind(2);
|
| + Node* c2_header = Checkpoint();
|
| +
|
| + // Inside-loop block.
|
| + LivenessAnalyzerBlock* in_loop = analyzer()->NewBlock(header);
|
| + set_current_block(in_loop);
|
| + Node* c1_in_loop = Checkpoint();
|
| + Bind(0);
|
| + Lookup(3);
|
| + Node* c2_in_loop = Checkpoint();
|
| +
|
| + // Add back edge.
|
| + header->AddPredecessor(in_loop);
|
| +
|
| + // After-loop block.
|
| + LivenessAnalyzerBlock* end = analyzer()->NewBlock(header);
|
| + set_current_block(end);
|
| + Node* c1_end = Checkpoint();
|
| + Lookup(1);
|
| + Lookup(2);
|
| + Node* c2_end = Checkpoint();
|
| +
|
| + Run();
|
| +
|
| + EXPECT_THAT(c1_start, IsCheckpointModuloLiveness("...."));
|
| + EXPECT_THAT(c2_start, IsCheckpointModuloLiveness("LL.L"));
|
| +
|
| + EXPECT_THAT(c1_header, IsCheckpointModuloLiveness("LL.L"));
|
| + EXPECT_THAT(c2_header, IsCheckpointModuloLiveness(".LLL"));
|
| +
|
| + EXPECT_THAT(c1_in_loop, IsCheckpointModuloLiveness(".L.L"));
|
| + EXPECT_THAT(c2_in_loop, IsCheckpointModuloLiveness("LL.L"));
|
| +
|
| + EXPECT_THAT(c1_end, IsCheckpointModuloLiveness(".LL."));
|
| + EXPECT_THAT(c2_end, IsCheckpointModuloLiveness("...."));
|
| +}
|
| +
|
| +} // namespace compiler
|
| +} // namespace internal
|
| +} // namespace v8
|
|
|