//===--------- WaitingOnGraphTest.cpp - Test WaitingOnGraph APIs ----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ExecutionEngine/Orc/WaitingOnGraph.h" #include "llvm/ExecutionEngine/Orc/WaitingOnGraphOpReplay.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" namespace llvm::orc::detail { class ElementSetTest : public testing::Test { public: using TestElementSet = WaitingOnGraph::ElementSet; bool merge(TestElementSet &S, const TestElementSet &Other) { return S.merge(Other); } bool remove(TestElementSet &S, const TestElementSet &Other) { return S.remove(Other); } template bool remove_if(TestElementSet &S, Pred &&P) { return S.remove_if(std::forward(P)); } }; class ContainerElementsMapTest : public testing::Test { public: using TestContainerElementsMap = WaitingOnGraph::ContainerElementsMap; bool merge(TestContainerElementsMap &M, const TestContainerElementsMap &Other) { return M.merge(Other); } bool remove(TestContainerElementsMap &M, const TestContainerElementsMap &Other) { return M.remove(Other); } template bool visit(TestContainerElementsMap &M, Visitor &&V) { return M.visit(std::forward(V)); } }; class WaitingOnGraphTest : public testing::Test { public: using TestGraph = WaitingOnGraph; protected: using SuperNode = TestGraph::SuperNode; using SuperNodeBuilder = TestGraph::SuperNodeBuilder; using ContainerElementsMap = TestGraph::ContainerElementsMap; using ElemToSuperNodeMap = TestGraph::ElemToSuperNodeMap; using SimplifyResult = TestGraph::SimplifyResult; using EmitResult = TestGraph::EmitResult; static const ContainerElementsMap &getDefs(SuperNode &SN) { return SN.Defs; } static const ContainerElementsMap &getDeps(SuperNode &SN) { return SN.Deps; } static std::vector> &getSNs(SimplifyResult &SR) { return SR.SNs; } static ElemToSuperNodeMap &getElemToSN(SimplifyResult &SR) { return SR.ElemToSN; } static std::vector> &getPendingSNs(TestGraph &G) { return G.PendingSNs; } static ContainerElementsMap merge(ContainerElementsMap M1, const ContainerElementsMap &M2) { M1.merge(M2); return M1; } ContainerElementsMap collapseDefs(std::vector> &SNs, bool DepsMustMatch = true) { if (SNs.empty()) return ContainerElementsMap(); ContainerElementsMap Result = SNs[0]->defs(); #ifndef NDEBUG const ContainerElementsMap &Deps = SNs[0]->deps(); #endif // NDEBUG for (size_t I = 1; I != SNs.size(); ++I) { assert(!DepsMustMatch || SNs[I]->deps() == Deps); Result.merge(SNs[I]->defs()); } return Result; } EmitResult integrate(EmitResult ER) { for (auto &SN : ER.Ready) Ready.merge(SN->defs()); for (auto &SN : ER.Failed) Failed.merge(SN->defs()); return ER; } EmitResult emit(SimplifyResult SR) { return integrate(G.emit(std::move(SR), GetExternalState)); } TestGraph G; ContainerElementsMap Ready; ContainerElementsMap Failed; class ExternalStateGetter { public: ExternalStateGetter(WaitingOnGraphTest &T) : T(T) {} TestGraph::ExternalState operator()(TestGraph::ContainerId C, TestGraph::ElementId E) { { auto I = T.Failed.find(C); if (I != T.Failed.end()) if (I->second.count(E)) return TestGraph::ExternalState::Failed; } { auto I = T.Ready.find(C); if (I != T.Ready.end()) if (I->second.count(E)) return TestGraph::ExternalState::Ready; } return TestGraph::ExternalState::None; } private: WaitingOnGraphTest &T; }; ExternalStateGetter GetExternalState{*this}; }; } // namespace llvm::orc::detail using namespace llvm; using namespace llvm::orc; using namespace llvm::orc::detail; TEST_F(ElementSetTest, Merge) { // Merge into empty set. TestElementSet S; TestElementSet Other({1, 2, 3}); EXPECT_TRUE(merge(S, Other)); EXPECT_EQ(S, Other); // Merge with all-duplicate elements -- no change. EXPECT_FALSE(merge(S, Other)); EXPECT_EQ(S, Other); // Merge empty into non-empty -- no change. EXPECT_FALSE(merge(S, TestElementSet())); EXPECT_EQ(S, Other); // Merge with partial overlap. EXPECT_TRUE(merge(S, TestElementSet({3, 4, 5}))); EXPECT_EQ(S, TestElementSet({1, 2, 3, 4, 5})); } TEST_F(ElementSetTest, Remove) { // Remove from empty set. TestElementSet S; EXPECT_FALSE(remove(S, TestElementSet({1, 2}))); EXPECT_TRUE(S.empty()); // Remove empty from non-empty -- no change. S = TestElementSet({1, 2, 3}); EXPECT_FALSE(remove(S, TestElementSet())); EXPECT_EQ(S, TestElementSet({1, 2, 3})); // Remove with no overlap -- no change. EXPECT_FALSE(remove(S, TestElementSet({4, 5}))); EXPECT_EQ(S, TestElementSet({1, 2, 3})); // Remove with partial overlap (|this| > |Other| path). S = TestElementSet({1, 2, 3, 4, 5}); EXPECT_TRUE(remove(S, TestElementSet({2, 4}))); EXPECT_EQ(S, TestElementSet({1, 3, 5})); // Remove with partial overlap (|this| <= |Other| path). S = TestElementSet({1, 2}); EXPECT_TRUE(remove(S, TestElementSet({2, 3, 4, 5}))); EXPECT_EQ(S, TestElementSet({1})); // Remove all elements. S = TestElementSet({1, 2}); EXPECT_TRUE(remove(S, TestElementSet({1, 2}))); EXPECT_TRUE(S.empty()); } TEST_F(ElementSetTest, RemoveIf) { // RemoveIf on empty set. TestElementSet S; EXPECT_FALSE(remove_if(S, [](const auto &) { return true; })); // RemoveIf with predicate matching nothing. S = TestElementSet({1, 2, 3}); EXPECT_FALSE(remove_if(S, [](const auto &) { return false; })); EXPECT_EQ(S, TestElementSet({1, 2, 3})); // RemoveIf with predicate matching some elements. EXPECT_TRUE(remove_if(S, [](const auto &E) { return E % 2 == 0; })); EXPECT_EQ(S, TestElementSet({1, 3})); // RemoveIf with predicate matching all elements. EXPECT_TRUE(remove_if(S, [](const auto &) { return true; })); EXPECT_TRUE(S.empty()); } TEST_F(ContainerElementsMapTest, Merge) { // Merge into empty map. TestContainerElementsMap M; TestContainerElementsMap Other({{0, {1, 2}}, {1, {3}}}); EXPECT_TRUE(merge(M, Other)); EXPECT_EQ(M, Other); // Merge with all-duplicate entries -- no change. EXPECT_FALSE(merge(M, Other)); EXPECT_EQ(M, Other); // Merge empty -- no change. EXPECT_FALSE(merge(M, TestContainerElementsMap())); EXPECT_EQ(M, Other); // Merge with disjoint containers. EXPECT_TRUE(merge(M, TestContainerElementsMap({{2, {4}}}))); EXPECT_EQ(M, TestContainerElementsMap({{0, {1, 2}}, {1, {3}}, {2, {4}}})); // Merge with overlapping container, new elements. EXPECT_TRUE(merge(M, TestContainerElementsMap({{0, {3}}}))); EXPECT_EQ(M, TestContainerElementsMap({{0, {1, 2, 3}}, {1, {3}}, {2, {4}}})); } TEST_F(ContainerElementsMapTest, Remove) { // Remove from empty map. TestContainerElementsMap M; EXPECT_FALSE(remove(M, TestContainerElementsMap({{0, {1}}}))); EXPECT_TRUE(M.empty()); // Remove with no matching container. M = TestContainerElementsMap({{0, {1, 2}}, {1, {3}}}); EXPECT_FALSE(remove(M, TestContainerElementsMap({{2, {1}}}))); EXPECT_EQ(M, TestContainerElementsMap({{0, {1, 2}}, {1, {3}}})); // Remove with no overlap within matching container. EXPECT_FALSE(remove(M, TestContainerElementsMap({{0, {5}}}))); EXPECT_EQ(M, TestContainerElementsMap({{0, {1, 2}}, {1, {3}}})); // Remove partial elements from a container. EXPECT_TRUE(remove(M, TestContainerElementsMap({{0, {1}}}))); EXPECT_EQ(M, TestContainerElementsMap({{0, {2}}, {1, {3}}})); // Remove all elements from a container -- container should be cleaned up. EXPECT_TRUE(remove(M, TestContainerElementsMap({{0, {2}}}))); EXPECT_EQ(M, TestContainerElementsMap({{1, {3}}})); } TEST_F(ContainerElementsMapTest, Visit) { // Visit empty map -- no-op. TestContainerElementsMap M; EXPECT_FALSE(visit(M, [](auto &, auto &) { return false; })); // Visit with no modifications. M = TestContainerElementsMap({{0, {1, 2}}, {1, {3}}}); EXPECT_FALSE(visit(M, [](auto &, auto &) { return false; })); EXPECT_EQ(M, TestContainerElementsMap({{0, {1, 2}}, {1, {3}}})); // Visit that removes some elements from one container. M = TestContainerElementsMap({{0, {1, 2, 3}}, {1, {4}}}); EXPECT_TRUE(visit(M, [](auto &Container, auto &Elements) { if (Container == 0) { Elements.erase(2); return true; } return false; })); EXPECT_EQ(M, TestContainerElementsMap({{0, {1, 3}}, {1, {4}}})); // Visit that empties a container -- container should be removed. M = TestContainerElementsMap({{0, {1}}, {1, {2}}}); EXPECT_TRUE(visit(M, [](auto &Container, auto &Elements) { if (Container == 0) { Elements.clear(); return true; } return false; })); EXPECT_EQ(M, TestContainerElementsMap({{1, {2}}})); } TEST_F(WaitingOnGraphTest, ConstructAndDestroyEmpty) { // Nothing to do here -- we're just testing construction and destruction // of the WaitingOnGraphTest::G member. } TEST_F(WaitingOnGraphTest, Build_TrivialSingleSuperNode) { // Add one set of trivial defs and empty deps to the builder, make sure that // they're passed through to the resulting super-node. SuperNodeBuilder B; ContainerElementsMap Defs({{0, {0}}}); ContainerElementsMap Deps; B.add(Defs, Deps); auto SNs = B.takeSuperNodes(); EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs[0]), Defs); EXPECT_EQ(getDeps(*SNs[0]), Deps); } TEST_F(WaitingOnGraphTest, Build_EmptyDefs) { // Adding empty def sets is ok, but should not result in creation of a // SuperNode. SuperNodeBuilder B; ContainerElementsMap Empty; B.add(Empty, Empty); auto SNs = B.takeSuperNodes(); EXPECT_TRUE(SNs.empty()); } TEST_F(WaitingOnGraphTest, Build_NonTrivialSingleSuperNode) { // Add one non-trivwial set of defs and deps. Make sure that they're passed // through to the resulting super-node. SuperNodeBuilder B; ContainerElementsMap Defs({{0, {0, 1, 2}}}); ContainerElementsMap Deps({{1, {3, 4, 5}}}); B.add(Defs, Deps); auto SNs = B.takeSuperNodes(); EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs[0]), Defs); EXPECT_EQ(getDeps(*SNs[0]), Deps); } TEST_F(WaitingOnGraphTest, Build_CoalesceEmptyDeps) { // Add two trivial defs both with empty deps to the builder. Check that // they're coalesced into a single super-node. SuperNodeBuilder B; ContainerElementsMap Defs1({{0, {0}}}); ContainerElementsMap Defs2({{0, {1}}}); ContainerElementsMap Deps; B.add(Defs1, Deps); B.add(Defs2, Deps); auto SNs = B.takeSuperNodes(); EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs[0]), merge(Defs1, Defs2)); EXPECT_EQ(getDeps(*SNs[0]), Deps); } TEST_F(WaitingOnGraphTest, Build_CoalesceNonEmptyDeps) { // Add two sets trivial of trivial defs with empty deps to the builder. Check // that the two coalesce into a single super node. SuperNodeBuilder B; ContainerElementsMap Defs1({{0, {0}}}); ContainerElementsMap Defs2({{0, {1}}}); ContainerElementsMap Deps({{1, {1}}}); B.add(Defs1, Deps); B.add(Defs2, Deps); auto SNs = B.takeSuperNodes(); EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs[0]), merge(Defs1, Defs2)); EXPECT_EQ(getDeps(*SNs[0]), Deps); } TEST_F(WaitingOnGraphTest, Build_CoalesceInterleaved) { // Add multiple sets of defs, some with the same dep sets. Check that nodes // are still coalesced as expected. SuperNodeBuilder B; ContainerElementsMap DefsA1({{0, {0}}}); ContainerElementsMap DefsA2({{0, {1}}}); ContainerElementsMap DefsB1({{1, {0}}}); ContainerElementsMap DefsB2({{1, {1}}}); ContainerElementsMap DepsA({{2, {0}}, {3, {0}}}); ContainerElementsMap DepsB({{4, {0}}, {5, {0}}}); B.add(DefsA1, DepsA); B.add(DefsB1, DepsB); B.add(DefsA2, DepsA); B.add(DefsB2, DepsB); auto SNs = B.takeSuperNodes(); EXPECT_EQ(SNs.size(), 2U); EXPECT_EQ(getDefs(*SNs[0]), merge(DefsA1, DefsA2)); EXPECT_EQ(getDeps(*SNs[0]), DepsA); EXPECT_EQ(getDefs(*SNs[1]), merge(DefsB1, DefsB2)); EXPECT_EQ(getDeps(*SNs[1]), DepsB); } TEST_F(WaitingOnGraphTest, Build_SelfDepRemoval) { // Add multiple sets of defs, some with the same dep sets. Check that nodes // are still coalesced as expected. SuperNodeBuilder B; ContainerElementsMap Defs({{0, {0, 1}}}); ContainerElementsMap Deps({{0, {1}}}); ContainerElementsMap Empty; B.add(Defs, Deps); auto SNs = B.takeSuperNodes(); EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs[0]), Defs); EXPECT_EQ(getDeps(*SNs[0]), Empty); } TEST_F(WaitingOnGraphTest, Simplification_EmptySimplification) { auto SR = TestGraph::simplify({}); auto &SNs = getSNs(SR); EXPECT_EQ(SNs.size(), 0U); EXPECT_EQ(getElemToSN(SR), ElemToSuperNodeMap()); } TEST_F(WaitingOnGraphTest, Simplification_TrivialSingleSuperNode) { // Test trivial call to simplify. SuperNodeBuilder B; ContainerElementsMap Defs({{0, {0}}}); ContainerElementsMap Deps({{0, {0}}}); B.add(Defs, Deps); auto SR = TestGraph::simplify(B.takeSuperNodes()); ContainerElementsMap Empty; // Check SNs. auto &SNs = getSNs(SR); EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs.at(0)), Defs); EXPECT_EQ(getDeps(*SNs.at(0)), Empty); // Check ElemToSNs. ElemToSuperNodeMap ExpectedElemToSNs; ExpectedElemToSNs[0][0] = SNs[0].get(); EXPECT_EQ(getElemToSN(SR), ExpectedElemToSNs); } TEST_F(WaitingOnGraphTest, Simplification_SimplifySingleContainerSimpleCycle) { // Test trivial simplification call with two nodes and one internal // dependence cycle within a single container: // N0: (0, 0) -> (0, 1) // N1: (0, 1) -> (0, 0) // We expect intra-simplify cycle elimination to clear both dependence sets, // and coalescing to join them into one supernode covering both defs. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1}}}); B.add(Defs0, Deps0); ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{0, {0}}}); B.add(Defs1, Deps1); auto SR = TestGraph::simplify(B.takeSuperNodes()); // Check SNs. auto &SNs = getSNs(SR); ContainerElementsMap Empty; EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs.at(0)), merge(Defs0, Defs1)); EXPECT_EQ(getDeps(*SNs.at(0)), Empty); // Check ElemToSNs. ElemToSuperNodeMap ExpectedElemToSNs; ExpectedElemToSNs[0][0] = SNs[0].get(); ExpectedElemToSNs[0][1] = SNs[0].get(); EXPECT_EQ(getElemToSN(SR), ExpectedElemToSNs); } TEST_F(WaitingOnGraphTest, Simplification_SimplifySingleContainerNElementCycle) { // Test trivial simplification call with M nodes and one internal // dependence cycle within a single container: // N0: (0, 0) -> (0, 1) // N1: (0, 1) -> (0, 2) // ... // NM: (0, M) -> (0, 0) // We expect intra-simplify cycle elimination to clear all dependence sets, // and coalescing to join them into one supernode covering all defs. SuperNodeBuilder B; constexpr size_t M = 10; for (size_t I = 0; I != M; ++I) { ContainerElementsMap Defs({{0, {I}}}); ContainerElementsMap Deps({{0, {(I + 1) % M}}}); B.add(Defs, Deps); } auto InitSNs = B.takeSuperNodes(); EXPECT_EQ(InitSNs.size(), M); auto SR = TestGraph::simplify(std::move(InitSNs)); // Check SNs. auto &SNs = getSNs(SR); ContainerElementsMap ExpectedDefs; for (size_t I = 0; I != M; ++I) ExpectedDefs[0].insert(I); ContainerElementsMap Empty; EXPECT_EQ(SNs.size(), 1U); EXPECT_EQ(getDefs(*SNs.at(0)), ExpectedDefs); EXPECT_EQ(getDeps(*SNs.at(0)), Empty); // Check ElemToSNs. ElemToSuperNodeMap ExpectedElemToSNs; for (size_t I = 0; I != M; ++I) ExpectedElemToSNs[0][I] = SNs[0].get(); EXPECT_EQ(getElemToSN(SR), ExpectedElemToSNs); } TEST_F(WaitingOnGraphTest, Simplification_SimplifyIntraSimplifyPropagateDeps) { // Test trivial simplification call with two nodes and one internal // dependence cycle within a single container: // N0: (0, 0) -> (0, {1, 2}) // N1: (0, 1) -> (0, {3}) // We expect intra-simplify cycle elimination to replace the dependence of // (0, 0) on (0, 1) with a dependence on (0, 3) instead. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1, 2}}}); B.add(Defs0, Deps0); ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{0, {3}}}); B.add(Defs1, Deps1); auto SR = TestGraph::simplify(B.takeSuperNodes()); // Check SNs. auto &SNs = getSNs(SR); EXPECT_EQ(SNs.size(), 2U); // ContainerElementsMap ExpectedDefs0({{0, {0}}}); // ContainerElementsMap ExpectedDeps0({{0, {1, 3}}}); EXPECT_EQ(getDefs(*SNs.at(0)), ContainerElementsMap({{0, {0}}})); EXPECT_EQ(getDeps(*SNs.at(0)), ContainerElementsMap({{0, {2, 3}}})); EXPECT_EQ(getDefs(*SNs.at(1)), ContainerElementsMap({{0, {1}}})); EXPECT_EQ(getDeps(*SNs.at(1)), ContainerElementsMap({{0, {3}}})); // Check ElemToSNs. ElemToSuperNodeMap ExpectedElemToSNs; ExpectedElemToSNs[0][0] = SNs[0].get(); ExpectedElemToSNs[0][1] = SNs[1].get(); EXPECT_EQ(getElemToSN(SR), ExpectedElemToSNs); } TEST_F(WaitingOnGraphTest, Emit_EmptyEmit) { // Check that empty emits work as expected. auto ER = G.emit(TestGraph::simplify({}), GetExternalState); EXPECT_EQ(ER.Ready.size(), 0U); EXPECT_EQ(ER.Failed.size(), 0U); } TEST_F(WaitingOnGraphTest, Emit_TrivialSingleNode) { // Check that emitting a single node behaves as expected. SuperNodeBuilder B; ContainerElementsMap Defs({{0, {0}}}); B.add(Defs, ContainerElementsMap()); auto ER = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER.Ready), Defs); EXPECT_EQ(ER.Failed.size(), 0U); } TEST_F(WaitingOnGraphTest, Emit_TrivialSequence) { // Perform a sequence of two emits where the second emit depends on the // first. Check that nodes become ready after each emit. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Empty; B.add(Defs0, Empty); auto ER0 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER0.Ready), Defs0); EXPECT_EQ(ER0.Failed.size(), 0U); ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{0, {0}}}); B.add(Defs1, Deps1); auto ER1 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER1.Ready), Defs1); EXPECT_EQ(ER1.Failed.size(), 0U); } TEST_F(WaitingOnGraphTest, Emit_SingleContainerSimpleCycle) { // Test an emit of two nodes with a dependence cycle within a single // container: // N0: (0, 0) -> (0, 1) // N1: (0, 1) -> (0, 0) // We expect intra-simplify cycle elimination to clear both dependence sets, // and coalescing to join them into one supernode covering both defs. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1}}}); B.add(Defs0, Deps0); auto ER0 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER0.Ready.size(), 0U); EXPECT_EQ(ER0.Failed.size(), 0U); ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{0, {0}}}); B.add(Defs1, Deps1); auto ER1 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER1.Ready), merge(Defs0, Defs1)); EXPECT_EQ(ER1.Failed.size(), 0U); } TEST_F(WaitingOnGraphTest, Emit_TrivialReverseSequence) { // Perform a sequence of two emits where the first emit depends on the // second. Check that both nodes become ready after the second emit. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1}}}); B.add(Defs0, Deps0); auto ER0 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER0.Ready.size(), 0U); EXPECT_EQ(ER0.Failed.size(), 0U); ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Empty; B.add(Defs1, Empty); auto ER1 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER1.Ready), merge(Defs0, Defs1)); EXPECT_EQ(ER1.Failed.size(), 0U); } TEST_F(WaitingOnGraphTest, Emit_Coalescing) { SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{1, {0}}}); B.add(Defs0, Deps0); auto ER0 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER0.Ready.size(), 0U); EXPECT_EQ(ER0.Failed.size(), 0U); ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{1, {0}}}); B.add(Defs1, Deps1); auto ER1 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER1.Ready.size(), 0U); EXPECT_EQ(ER1.Failed.size(), 0U); // Check that after emitting two nodes with the same dep set we have only one // pending supernode whose defs are the union of the defs in the two emits. auto &PendingSNs = getPendingSNs(G); EXPECT_EQ(PendingSNs.size(), 1U); EXPECT_EQ(getDefs(*PendingSNs.at(0)), merge(Defs0, Defs1)); ContainerElementsMap Defs2({{1, {0}}}); ContainerElementsMap Empty; B.add(Defs2, Empty); auto ER2 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER2.Ready), merge(merge(Defs0, Defs1), Defs2)); EXPECT_EQ(ER2.Failed.size(), 0U); } TEST_F(WaitingOnGraphTest, Emit_ZigZag) { // Perform a sequence of four emits, where the first three contain a zig-zag // pattern: // 1. (0, 0) -> (0, 1) // 2. (0, 2) -> (0, 3) // ^ -- At this point we expect two pending supernodes. // 3. (0, 1) -> (0, 2) // ^ -- Resolution of (0, 1) should cause all three emitted nodes to coalsce // into one supernode defining (0, {0, 1, 2}). // 4. (0, 3) // ^ -- Should cause all four nodes to become ready. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1}}}); B.add(Defs0, Deps0); auto ER0 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER0.Ready.size(), 0U); EXPECT_EQ(ER0.Failed.size(), 0U); ContainerElementsMap Defs1({{0, {2}}}); ContainerElementsMap Deps1({{0, {3}}}); B.add(Defs1, Deps1); auto ER1 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER1.Ready.size(), 0U); EXPECT_EQ(ER1.Failed.size(), 0U); // Check that after emitting two nodes with the same dep set we have only one // pending supernode whose defs are the union of the defs in the two emits. auto &PendingSNs = getPendingSNs(G); EXPECT_EQ(PendingSNs.size(), 2U); EXPECT_EQ(getDefs(*PendingSNs.at(0)), Defs0); EXPECT_EQ(getDeps(*PendingSNs.at(0)), Deps0); EXPECT_EQ(getDefs(*PendingSNs.at(1)), Defs1); EXPECT_EQ(getDeps(*PendingSNs.at(1)), Deps1); ContainerElementsMap Defs2({{0, {1}}}); ContainerElementsMap Deps2({{0, {2}}}); B.add(Defs2, Deps2); auto ER2 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER2.Ready.size(), 0U); EXPECT_EQ(ER2.Failed.size(), 0U); // Check that after emitting the third node we've coalesced all three. EXPECT_EQ(PendingSNs.size(), 1U); EXPECT_EQ(getDefs(*PendingSNs.at(0)), merge(merge(Defs0, Defs1), Defs2)); EXPECT_EQ(getDeps(*PendingSNs.at(0)), Deps1); ContainerElementsMap Defs3({{0, {3}}}); ContainerElementsMap Empty; B.add(Defs3, Empty); auto ER3 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(collapseDefs(ER3.Ready), merge(merge(merge(Defs0, Defs1), Defs2), Defs3)); EXPECT_EQ(ER2.Failed.size(), 0U); EXPECT_TRUE(PendingSNs.empty()); } TEST_F(WaitingOnGraphTest, Emit_ReEmit) { // Test for the bug in https://github.com/llvm/llvm-project/issues/169135, // which was caused by stale entries in the ElemsToPendingSNs map. // // To trigger the bug we need to: // 1. Create a SuperNode with an unmet dependence, causing it to be added to // ElemsToPendingSNs. // 2. Cause that SuperNode to become ready (bug left stale entries in map) // 3. Remove the node from the Ready map (this is equivalent to removal of a // symbol in an ORC session, and allows new SuperNodes to depend on the // stale entry). // 4. Add a new node that references the previously emitted/removed SuperNode // This triggers access of the stale entry, and should error out in // sanitizer builds. SuperNodeBuilder B; // 1. Create SuperNode with unmet dependence. ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1}}}); B.add(Defs0, Deps0); emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_TRUE(Ready.empty()); // 2. Cause previous SuperNode to become ready. ContainerElementsMap Defs1({{0, {1}}}); B.add(Defs1, ContainerElementsMap()); emit(TestGraph::simplify(B.takeSuperNodes())); // Check that both nodes have become ready. EXPECT_EQ(Ready, merge(Defs0, Defs1)); // 3. Erase Ready nodes to simulate removal from the graph. Ready.clear(); // 4. Emit a new dependence on the original def. ContainerElementsMap Defs2({{0, {2}}}); ContainerElementsMap Deps2({{0, {0}}}); B.add(Defs2, Deps2); auto ER = emit(TestGraph::simplify(B.takeSuperNodes())); // We expect the new dependence to remain pending. EXPECT_TRUE(ER.Ready.empty()); } TEST_F(WaitingOnGraphTest, Fail_Empty) { // Check that failing an empty set is a no-op. auto FR = G.fail(ContainerElementsMap()); EXPECT_EQ(FR.size(), 0U); } TEST_F(WaitingOnGraphTest, Fail_Single) { // Check that failing a set with no existing dependencies works. auto FR = G.fail({{0, {0}}}); EXPECT_EQ(FR.size(), 0U); } TEST_F(WaitingOnGraphTest, Fail_EmitDependenceOnFailure) { // Check that emitted nodes that directly depend on failed nodes also fail. Failed = {{0, {0}}}; SuperNodeBuilder B; ContainerElementsMap Defs({{0, {1}}}); ContainerElementsMap Deps({{0, {0}}}); B.add(Defs, Deps); auto ER = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER.Ready.size(), 0U); EXPECT_EQ(collapseDefs(ER.Failed, false), Defs); } TEST_F(WaitingOnGraphTest, Fail_ZigZag) { // Check that if an emit introduces a transitive dependence of a failed // node, then all nodes that depend on the failed node are also failed. SuperNodeBuilder B; ContainerElementsMap Defs0({{0, {0}}}); ContainerElementsMap Deps0({{0, {1}}}); B.add(Defs0, Deps0); auto ER0 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER0.Ready.size(), 0U); EXPECT_EQ(ER0.Failed.size(), 0U); Failed = {{0, {2}}}; ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{0, {2}}}); B.add(Defs1, Deps1); auto ER1 = emit(TestGraph::simplify(B.takeSuperNodes())); EXPECT_EQ(ER1.Ready.size(), 0U); EXPECT_EQ(collapseDefs(ER1.Failed, false), merge(Defs0, Defs1)); } TEST_F(WaitingOnGraphTest, RecordAndReplay) { // Record a sequence of operations, then replay them on a fresh graph. std::string RecordBuf; raw_string_ostream RecordOS(RecordBuf); WaitingOnGraphOpStreamRecorder Rec(RecordOS); SuperNodeBuilder B; // Emit a node with no deps -- becomes Ready immediately. ContainerElementsMap Defs0({{0, {0}}}); B.add(Defs0, ContainerElementsMap()); auto ER0 = integrate( G.emit(TestGraph::simplify(B.takeSuperNodes(), &Rec), GetExternalState)); EXPECT_EQ(collapseDefs(ER0.Ready), Defs0); EXPECT_EQ(ER0.Failed.size(), 0U); // Emit a node depending on an external dep -- stays Pending. ContainerElementsMap Defs1({{0, {1}}}); ContainerElementsMap Deps1({{1, {0}}}); B.add(Defs1, Deps1); auto ER1 = integrate( G.emit(TestGraph::simplify(B.takeSuperNodes(), &Rec), GetExternalState)); EXPECT_EQ(ER1.Ready.size(), 0U); EXPECT_EQ(ER1.Failed.size(), 0U); // Fail the external dep -- causes the pending node to fail. ContainerElementsMap FailElems({{1, {0}}}); auto FailedSNs = G.fail(FailElems, &Rec); EXPECT_EQ(FailedSNs.size(), 1U); Rec.recordEnd(); // Now replay on a fresh graph. TestGraph G2; typename WaitingOnGraphOpReplay::Replayer R(G2); Error Err = Error::success(); for (auto &Op : readWaitingOnGraphOpsFromBuffer(RecordBuf, Err)) R.replay(std::move(Op)); EXPECT_THAT_ERROR(std::move(Err), Succeeded()); }