// Copyright 2013 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 #include "src/v8.h" #include "test/cctest/cctest.h" #include "src/compiler/graph.h" #include "src/compiler/node.h" #include "src/compiler/operator.h" using namespace v8::internal; using namespace v8::internal::compiler; #define NONE reinterpret_cast(1) static Operator dummy_operator(IrOpcode::kParameter, Operator::kNoWrite, "dummy", 0, 0, 0, 1, 0, 0); #define CHECK_USES(node, ...) \ do { \ Node* __array[] = {__VA_ARGS__}; \ int __size = \ __array[0] != NONE ? static_cast(arraysize(__array)) : 0; \ CheckUseChain(node, __array, __size); \ } while (false) typedef std::multiset> NodeMSet; static void CheckUseChain(Node* node, Node** uses, int use_count) { // Check ownership. if (use_count == 1) CHECK(node->OwnedBy(uses[0])); if (use_count > 1) { for (int i = 0; i < use_count; i++) { CHECK(!node->OwnedBy(uses[i])); } } // Check the self-reported use count. CHECK_EQ(use_count, node->UseCount()); // Build the expectation set. NodeMSet expect_set; for (int i = 0; i < use_count; i++) { expect_set.insert(uses[i]); } { // Check that iterating over the uses gives the right counts. NodeMSet use_set; for (auto use : node->uses()) { use_set.insert(use); } CHECK(expect_set == use_set); } { // Check that iterating over the use edges gives the right counts, // input indices, from(), and to() pointers. NodeMSet use_set; for (auto edge : node->use_edges()) { CHECK_EQ(node, edge.to()); CHECK_EQ(node, edge.from()->InputAt(edge.index())); use_set.insert(edge.from()); } CHECK(expect_set == use_set); } { // Check the use nodes actually have the node as inputs. for (Node* use : node->uses()) { size_t count = 0; for (Node* input : use->inputs()) { if (input == node) count++; } CHECK_EQ(count, expect_set.count(use)); } } } #define CHECK_INPUTS(node, ...) \ do { \ Node* __array[] = {__VA_ARGS__}; \ int __size = \ __array[0] != NONE ? static_cast(arraysize(__array)) : 0; \ CheckInputs(node, __array, __size); \ } while (false) static void CheckInputs(Node* node, Node** inputs, int input_count) { CHECK_EQ(input_count, node->InputCount()); // Check InputAt(). for (int i = 0; i < static_cast(input_count); i++) { CHECK_EQ(inputs[i], node->InputAt(i)); } // Check input iterator. int index = 0; for (Node* input : node->inputs()) { CHECK_EQ(inputs[index], input); index++; } // Check use lists of inputs. for (int i = 0; i < static_cast(input_count); i++) { Node* input = inputs[i]; if (!input) continue; // skip null inputs bool found = false; // Check regular use list. for (Node* use : input->uses()) { if (use == node) { found = true; break; } } CHECK(found); int count = 0; // Check use edge list. for (auto edge : input->use_edges()) { if (edge.from() == node && edge.to() == input && edge.index() == i) { count++; } } CHECK_EQ(1, count); } } TEST(NodeUseIteratorReplaceUses) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0); Node* n3 = graph.NewNode(&dummy_operator); CHECK_USES(n0, n1, n2); CHECK_INPUTS(n1, n0); CHECK_INPUTS(n2, n0); n0->ReplaceUses(n3); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); CHECK_USES(n2, NONE); CHECK_USES(n3, n1, n2); CHECK_INPUTS(n1, n3); CHECK_INPUTS(n2, n3); } TEST(NodeUseIteratorReplaceUsesSelf) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); CHECK_USES(n0, n1); CHECK_USES(n1, NONE); n1->ReplaceInput(0, n1); // Create self-reference. CHECK_USES(n0, NONE); CHECK_USES(n1, n1); Node* n2 = graph.NewNode(&dummy_operator); n1->ReplaceUses(n2); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); CHECK_USES(n2, n1); } TEST(ReplaceInput) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator); Node* n3 = graph.NewNode(&dummy_operator, n0, n1, n2); Node* n4 = graph.NewNode(&dummy_operator); CHECK_USES(n0, n3); CHECK_USES(n1, n3); CHECK_USES(n2, n3); CHECK_USES(n3, NONE); CHECK_USES(n4, NONE); CHECK_INPUTS(n3, n0, n1, n2); n3->ReplaceInput(1, n4); CHECK_USES(n1, NONE); CHECK_USES(n4, n3); CHECK_INPUTS(n3, n0, n4, n2); } TEST(OwnedBy) { Zone zone; Graph graph(&zone); { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); CHECK(!n0->OwnedBy(n1)); CHECK(!n1->OwnedBy(n0)); Node* n2 = graph.NewNode(&dummy_operator, n0); CHECK(n0->OwnedBy(n2)); CHECK(!n2->OwnedBy(n0)); Node* n3 = graph.NewNode(&dummy_operator, n0); CHECK(!n0->OwnedBy(n2)); CHECK(!n0->OwnedBy(n3)); CHECK(!n2->OwnedBy(n0)); CHECK(!n3->OwnedBy(n0)); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); CHECK(n0->OwnedBy(n1)); CHECK(!n1->OwnedBy(n0)); Node* n2 = graph.NewNode(&dummy_operator, n0); CHECK(!n0->OwnedBy(n1)); CHECK(!n0->OwnedBy(n2)); CHECK(!n1->OwnedBy(n0)); CHECK(!n1->OwnedBy(n2)); CHECK(!n2->OwnedBy(n0)); CHECK(!n2->OwnedBy(n1)); Node* n3 = graph.NewNode(&dummy_operator); n2->ReplaceInput(0, n3); CHECK(n0->OwnedBy(n1)); CHECK(!n1->OwnedBy(n0)); CHECK(!n1->OwnedBy(n0)); CHECK(!n1->OwnedBy(n2)); CHECK(!n2->OwnedBy(n0)); CHECK(!n2->OwnedBy(n1)); CHECK(n3->OwnedBy(n2)); CHECK(!n2->OwnedBy(n3)); } } TEST(Uses) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); CHECK_USES(n0, n1); CHECK_USES(n1, NONE); Node* n2 = graph.NewNode(&dummy_operator, n0); CHECK_USES(n0, n1, n2); CHECK_USES(n2, NONE); Node* n3 = graph.NewNode(&dummy_operator, n0); CHECK_USES(n0, n1, n2, n3); CHECK_USES(n3, NONE); } TEST(Inputs) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0); Node* n3 = graph.NewNode(&dummy_operator, n0, n1, n2); CHECK_INPUTS(n3, n0, n1, n2); Node* n4 = graph.NewNode(&dummy_operator, n0, n1, n2); n3->AppendInput(graph.zone(), n4); CHECK_INPUTS(n3, n0, n1, n2, n4); CHECK_USES(n4, n3); n3->AppendInput(graph.zone(), n4); CHECK_INPUTS(n3, n0, n1, n2, n4, n4); CHECK_USES(n4, n3, n3); Node* n5 = graph.NewNode(&dummy_operator, n4); CHECK_USES(n4, n3, n3, n5); } TEST(RemoveInput) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0, n1); CHECK_INPUTS(n0, NONE); CHECK_INPUTS(n1, n0); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n1, n2); n1->RemoveInput(0); CHECK_INPUTS(n1, NONE); CHECK_USES(n0, n2); n2->RemoveInput(0); CHECK_INPUTS(n2, n1); CHECK_USES(n0, NONE); CHECK_USES(n1, n2); n2->RemoveInput(0); CHECK_INPUTS(n2, NONE); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); CHECK_USES(n2, NONE); } TEST(AppendInputsAndIterator) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0, n1); CHECK_INPUTS(n0, NONE); CHECK_INPUTS(n1, n0); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n1, n2); Node* n3 = graph.NewNode(&dummy_operator); n2->AppendInput(graph.zone(), n3); CHECK_INPUTS(n2, n0, n1, n3); CHECK_USES(n3, n2); } TEST(NullInputsSimple) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0, n1); CHECK_INPUTS(n0, NONE); CHECK_INPUTS(n1, n0); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n1, n2); n2->ReplaceInput(0, nullptr); CHECK_INPUTS(n2, NULL, n1); CHECK_USES(n0, n1); n2->ReplaceInput(1, nullptr); CHECK_INPUTS(n2, NULL, NULL); CHECK_USES(n1, NONE); } TEST(NullInputsAppended) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0); Node* n3 = graph.NewNode(&dummy_operator, n0); n3->AppendInput(graph.zone(), n1); n3->AppendInput(graph.zone(), n2); CHECK_INPUTS(n3, n0, n1, n2); CHECK_USES(n0, n1, n2, n3); CHECK_USES(n1, n3); CHECK_USES(n2, n3); n3->ReplaceInput(1, NULL); CHECK_USES(n1, NONE); CHECK_INPUTS(n3, n0, NULL, n2); } TEST(ReplaceUsesFromAppendedInputs) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2 = graph.NewNode(&dummy_operator, n0); Node* n3 = graph.NewNode(&dummy_operator); CHECK_INPUTS(n2, n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n1, n2); n2->AppendInput(graph.zone(), n0); CHECK_INPUTS(n2, n0, n1, n0); CHECK_USES(n1, n2); CHECK_USES(n0, n2, n1, n2); n0->ReplaceUses(n3); CHECK_USES(n0, NONE); CHECK_INPUTS(n2, n3, n1, n3); CHECK_USES(n3, n2, n1, n2); } TEST(ReplaceInputMultipleUses) { Zone zone; Graph graph(&zone); Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0); n2->ReplaceInput(0, n1); CHECK_EQ(0, n0->UseCount()); CHECK_EQ(1, n1->UseCount()); Node* n3 = graph.NewNode(&dummy_operator, n0); n3->ReplaceInput(0, n1); CHECK_EQ(0, n0->UseCount()); CHECK_EQ(2, n1->UseCount()); } TEST(TrimInputCountInline) { Zone zone; Graph graph(&zone); { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); n1->TrimInputCount(1); CHECK_INPUTS(n1, n0); CHECK_USES(n0, n1); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); n1->TrimInputCount(0); CHECK_INPUTS(n1, NONE); CHECK_USES(n0, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0, n1); n2->TrimInputCount(2); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n2); CHECK_USES(n1, n2); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0, n1); n2->TrimInputCount(1); CHECK_INPUTS(n2, n0); CHECK_USES(n0, n2); CHECK_USES(n1, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0, n1); n2->TrimInputCount(0); CHECK_INPUTS(n2, NONE); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0, n0); n2->TrimInputCount(1); CHECK_INPUTS(n2, n0); CHECK_USES(n0, n2); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0, n0); n2->TrimInputCount(0); CHECK_INPUTS(n2, NONE); CHECK_USES(n0, NONE); } } TEST(TrimInputCountOutOfLine1) { Zone zone; Graph graph(&zone); { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); n1->AppendInput(graph.zone(), n0); CHECK_INPUTS(n1, n0); CHECK_USES(n0, n1); n1->TrimInputCount(1); CHECK_INPUTS(n1, n0); CHECK_USES(n0, n1); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); n1->AppendInput(graph.zone(), n0); CHECK_EQ(1, n1->InputCount()); n1->TrimInputCount(0); CHECK_EQ(0, n1->InputCount()); CHECK_EQ(0, n0->UseCount()); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator); n2->AppendInput(graph.zone(), n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); n2->TrimInputCount(2); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n2); CHECK_USES(n1, n2); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator); n2->AppendInput(graph.zone(), n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); n2->TrimInputCount(1); CHECK_INPUTS(n2, n0); CHECK_USES(n0, n2); CHECK_USES(n1, NONE); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator); n2->AppendInput(graph.zone(), n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); n2->TrimInputCount(0); CHECK_INPUTS(n2, NONE); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator); n2->AppendInput(graph.zone(), n0); n2->AppendInput(graph.zone(), n0); CHECK_INPUTS(n2, n0, n0); CHECK_USES(n0, n2, n2); n2->TrimInputCount(1); CHECK_INPUTS(n2, n0); CHECK_USES(n0, n2); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator); n2->AppendInput(graph.zone(), n0); n2->AppendInput(graph.zone(), n0); CHECK_INPUTS(n2, n0, n0); CHECK_USES(n0, n2, n2); n2->TrimInputCount(0); CHECK_INPUTS(n2, NONE); CHECK_USES(n0, NONE); } } TEST(TrimInputCountOutOfLine2) { Zone zone; Graph graph(&zone); { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); n2->TrimInputCount(2); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n2); CHECK_USES(n1, n2); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); n2->TrimInputCount(1); CHECK_INPUTS(n2, n0); CHECK_USES(n0, n2); CHECK_USES(n1, NONE); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0); n2->AppendInput(graph.zone(), n1); CHECK_INPUTS(n2, n0, n1); n2->TrimInputCount(0); CHECK_INPUTS(n2, NONE); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0); n2->AppendInput(graph.zone(), n0); CHECK_INPUTS(n2, n0, n0); CHECK_USES(n0, n2, n2); n2->TrimInputCount(1); CHECK_INPUTS(n2, n0); CHECK_USES(n0, n2); CHECK_USES(n2, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n2 = graph.NewNode(&dummy_operator, n0); n2->AppendInput(graph.zone(), n0); CHECK_EQ(2, n2->InputCount()); CHECK_EQ(2, n0->UseCount()); n2->TrimInputCount(0); CHECK_EQ(0, n2->InputCount()); CHECK_EQ(0, n0->UseCount()); CHECK_EQ(0, n2->UseCount()); } } TEST(NullAllInputs) { Zone zone; Graph graph(&zone); for (int i = 0; i < 2; i++) { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); Node* n2; if (i == 0) { n2 = graph.NewNode(&dummy_operator, n0, n1); CHECK_INPUTS(n2, n0, n1); } else { n2 = graph.NewNode(&dummy_operator, n0); CHECK_INPUTS(n2, n0); n2->AppendInput(graph.zone(), n1); // with out-of-line input. CHECK_INPUTS(n2, n0, n1); } n0->NullAllInputs(); CHECK_INPUTS(n0, NONE); CHECK_USES(n0, n1, n2); n1->NullAllInputs(); CHECK_INPUTS(n1, NULL); CHECK_INPUTS(n2, n0, n1); CHECK_USES(n0, n2); n2->NullAllInputs(); CHECK_INPUTS(n1, NULL); CHECK_INPUTS(n2, NULL, NULL); CHECK_USES(n0, NONE); } { Node* n0 = graph.NewNode(&dummy_operator); Node* n1 = graph.NewNode(&dummy_operator, n0); n1->ReplaceInput(0, n1); // self-reference. CHECK_INPUTS(n0, NONE); CHECK_INPUTS(n1, n1); CHECK_USES(n0, NONE); CHECK_USES(n1, n1); n1->NullAllInputs(); CHECK_INPUTS(n0, NONE); CHECK_INPUTS(n1, NULL); CHECK_USES(n0, NONE); CHECK_USES(n1, NONE); } } TEST(AppendAndTrim) { Zone zone; Graph graph(&zone); Node* nodes[] = { graph.NewNode(&dummy_operator), graph.NewNode(&dummy_operator), graph.NewNode(&dummy_operator), graph.NewNode(&dummy_operator), graph.NewNode(&dummy_operator)}; int max = static_cast(arraysize(nodes)); Node* last = graph.NewNode(&dummy_operator); for (int i = 0; i < max; i++) { last->AppendInput(graph.zone(), nodes[i]); CheckInputs(last, nodes, i + 1); for (int j = 0; j < max; j++) { if (j <= i) CHECK_USES(nodes[j], last); if (j > i) CHECK_USES(nodes[j], NONE); } CHECK_USES(last, NONE); } for (int i = max; i >= 0; i--) { last->TrimInputCount(i); CheckInputs(last, nodes, i); for (int j = 0; j < i; j++) { if (j < i) CHECK_USES(nodes[j], last); if (j >= i) CHECK_USES(nodes[j], NONE); } CHECK_USES(last, NONE); } }