CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
856 lines
28 KiB
C++
856 lines
28 KiB
C++
//===--------- 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<uintptr_t, uintptr_t>::ElementSet;
|
|
|
|
bool merge(TestElementSet &S, const TestElementSet &Other) {
|
|
return S.merge(Other);
|
|
}
|
|
|
|
bool remove(TestElementSet &S, const TestElementSet &Other) {
|
|
return S.remove(Other);
|
|
}
|
|
|
|
template <typename Pred> bool remove_if(TestElementSet &S, Pred &&P) {
|
|
return S.remove_if(std::forward<Pred>(P));
|
|
}
|
|
};
|
|
|
|
class ContainerElementsMapTest : public testing::Test {
|
|
public:
|
|
using TestContainerElementsMap =
|
|
WaitingOnGraph<uintptr_t, uintptr_t>::ContainerElementsMap;
|
|
|
|
bool merge(TestContainerElementsMap &M,
|
|
const TestContainerElementsMap &Other) {
|
|
return M.merge(Other);
|
|
}
|
|
|
|
bool remove(TestContainerElementsMap &M,
|
|
const TestContainerElementsMap &Other) {
|
|
return M.remove(Other);
|
|
}
|
|
|
|
template <typename Visitor>
|
|
bool visit(TestContainerElementsMap &M, Visitor &&V) {
|
|
return M.visit(std::forward<Visitor>(V));
|
|
}
|
|
};
|
|
|
|
class WaitingOnGraphTest : public testing::Test {
|
|
public:
|
|
using TestGraph = WaitingOnGraph<uintptr_t, uintptr_t>;
|
|
|
|
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<std::unique_ptr<SuperNode>> &getSNs(SimplifyResult &SR) {
|
|
return SR.SNs;
|
|
}
|
|
|
|
static ElemToSuperNodeMap &getElemToSN(SimplifyResult &SR) {
|
|
return SR.ElemToSN;
|
|
}
|
|
|
|
static std::vector<std::unique_ptr<SuperNode>> &getPendingSNs(TestGraph &G) {
|
|
return G.PendingSNs;
|
|
}
|
|
|
|
static ContainerElementsMap merge(ContainerElementsMap M1,
|
|
const ContainerElementsMap &M2) {
|
|
M1.merge(M2);
|
|
return M1;
|
|
}
|
|
|
|
ContainerElementsMap
|
|
collapseDefs(std::vector<std::unique_ptr<SuperNode>> &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<uintptr_t, uintptr_t> 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<uintptr_t, uintptr_t>::Replayer R(G2);
|
|
Error Err = Error::success();
|
|
for (auto &Op :
|
|
readWaitingOnGraphOpsFromBuffer<uintptr_t, uintptr_t>(RecordBuf, Err))
|
|
R.replay(std::move(Op));
|
|
EXPECT_THAT_ERROR(std::move(Err), Succeeded());
|
|
}
|