//===- DependencyGraphTest.cpp --------------------------------------------===// // // 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/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h" #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/AssumptionCache.h" #include "llvm/Analysis/BasicAliasAnalysis.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/Dominators.h" #include "llvm/SandboxIR/Context.h" #include "llvm/SandboxIR/Function.h" #include "llvm/SandboxIR/Instruction.h" #include "llvm/Support/SourceMgr.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" using namespace llvm; struct DependencyGraphTest : public testing::Test { LLVMContext C; std::unique_ptr M; std::unique_ptr AC; std::unique_ptr DT; std::unique_ptr BAA; std::unique_ptr AA; std::unique_ptr TLII; std::unique_ptr TLI; void parseIR(LLVMContext &C, const char *IR) { SMDiagnostic Err; M = parseAssemblyString(IR, Err, C); if (!M) { Err.print("DependencyGraphTest", errs()); return; } TLII = std::make_unique(M->getTargetTriple()); TLI = std::make_unique(*TLII); } AAResults &getAA(llvm::Function &LLVMF) { AA = std::make_unique(*TLI); AC = std::make_unique(LLVMF); DT = std::make_unique(LLVMF); BAA = std::make_unique(M->getDataLayout(), LLVMF, *TLI, *AC, DT.get()); AA->addAAResult(*BAA); return *AA; } /// \Returns true if there is a dependency: SrcN->DstN. bool memDependency(sandboxir::DGNode *SrcN, sandboxir::DGNode *DstN) { if (auto *MemDstN = dyn_cast(DstN)) return MemDstN->hasMemPred(SrcN); return false; } }; TEST_F(DependencyGraphTest, isStackSaveOrRestoreIntrinsic) { parseIR(C, R"IR( declare void @llvm.sideeffect() define void @foo(i8 %v1, ptr %ptr) { %add = add i8 %v1, %v1 %stacksave = call ptr @llvm.stacksave() call void @llvm.stackrestore(ptr %stacksave) call void @llvm.sideeffect() ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); sandboxir::Function *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add = cast(&*It++); auto *StackSave = cast(&*It++); auto *StackRestore = cast(&*It++); auto *Other = cast(&*It++); auto *Ret = cast(&*It++); using DGNode = sandboxir::DGNode; EXPECT_FALSE(DGNode::isStackSaveOrRestoreIntrinsic(Add)); EXPECT_TRUE(DGNode::isStackSaveOrRestoreIntrinsic(StackSave)); EXPECT_TRUE(DGNode::isStackSaveOrRestoreIntrinsic(StackRestore)); EXPECT_FALSE(DGNode::isStackSaveOrRestoreIntrinsic(Other)); EXPECT_FALSE(DGNode::isStackSaveOrRestoreIntrinsic(Ret)); } TEST_F(DependencyGraphTest, Instruction_isMemDepCandidate) { parseIR(C, R"IR( declare void @llvm.fake.use(...) declare void @llvm.sideeffect() declare void @llvm.pseudoprobe(i64, i64, i32, i64) declare void @bar() define void @foo(i8 %v1, ptr %ptr) { %add0 = add i8 %v1, %v1 %ld0 = load i8, ptr %ptr store i8 %v1, ptr %ptr call void @llvm.sideeffect() call void @llvm.pseudoprobe(i64 42, i64 1, i32 0, i64 -1) call void @llvm.fake.use(ptr %ptr) call void @bar() ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); sandboxir::Function *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add0 = cast(&*It++); auto *Ld0 = cast(&*It++); auto *St0 = cast(&*It++); auto *SideEffect0 = cast(&*It++); auto *PseudoProbe0 = cast(&*It++); auto *OtherIntrinsic0 = cast(&*It++); auto *CallBar = cast(&*It++); auto *Ret = cast(&*It++); using DGNode = sandboxir::DGNode; EXPECT_FALSE(DGNode::isMemDepCandidate(Add0)); EXPECT_TRUE(DGNode::isMemDepCandidate(Ld0)); EXPECT_TRUE(DGNode::isMemDepCandidate(St0)); EXPECT_FALSE(DGNode::isMemDepCandidate(SideEffect0)); EXPECT_FALSE(DGNode::isMemDepCandidate(PseudoProbe0)); EXPECT_TRUE(DGNode::isMemDepCandidate(OtherIntrinsic0)); EXPECT_TRUE(DGNode::isMemDepCandidate(CallBar)); EXPECT_FALSE(DGNode::isMemDepCandidate(Ret)); } TEST_F(DependencyGraphTest, Instruction_isMemIntrinsic) { parseIR(C, R"IR( declare void @llvm.sideeffect() declare void @llvm.pseudoprobe(i64) declare void @llvm.assume(i1) define void @foo(ptr %ptr, i1 %cond) { call void @llvm.sideeffect() call void @llvm.pseudoprobe(i64 42) call void @llvm.assume(i1 %cond) ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); sandboxir::Function *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *SideEffect = cast(&*It++); auto *PseudoProbe = cast(&*It++); auto *OtherIntrinsic = cast(&*It++); using DGNode = sandboxir::DGNode; EXPECT_FALSE(DGNode::isMemIntrinsic(SideEffect)); EXPECT_FALSE(DGNode::isMemIntrinsic(PseudoProbe)); EXPECT_TRUE(DGNode::isMemIntrinsic(OtherIntrinsic)); } TEST_F(DependencyGraphTest, MemDGNode) { parseIR(C, R"IR( declare void @llvm.sideeffect() declare void @llvm.pseudoprobe(i64, i64, i32, i64) declare void @llvm.fake.use(...) declare void @bar() define void @foo(i8 %v1, ptr %ptr) { store i8 %v1, ptr %ptr %ld0 = load i8, ptr %ptr %add = add i8 %v1, %v1 %stacksave = call ptr @llvm.stacksave() call void @llvm.stackrestore(ptr %stacksave) call void @llvm.sideeffect() call void @llvm.pseudoprobe(i64 42, i64 1, i32 0, i64 -1) call void @llvm.fake.use(ptr %ptr) call void @bar() ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Store = cast(&*It++); auto *Load = cast(&*It++); auto *Add = cast(&*It++); auto *StackSave = cast(&*It++); auto *StackRestore = cast(&*It++); auto *SideEffect = cast(&*It++); auto *PseudoProbe = cast(&*It++); auto *FakeUse = cast(&*It++); auto *Call = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); EXPECT_TRUE(isa(DAG.getNode(Store))); EXPECT_TRUE(isa(DAG.getNode(Load))); EXPECT_FALSE(isa(DAG.getNode(Add))); EXPECT_TRUE(isa(DAG.getNode(StackSave))); EXPECT_TRUE(isa(DAG.getNode(StackRestore))); EXPECT_FALSE(isa(DAG.getNode(SideEffect))); EXPECT_FALSE(isa(DAG.getNode(PseudoProbe))); EXPECT_TRUE(isa(DAG.getNode(FakeUse))); EXPECT_TRUE(isa(DAG.getNode(Call))); EXPECT_FALSE(isa(DAG.getNode(Ret))); } TEST_F(DependencyGraphTest, Basic) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v0, i8 %v1) { store i8 %v0, ptr %ptr store i8 %v1, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S0 = cast(&*It++); auto *S1 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); auto Span = DAG.extend({&*BB->begin(), BB->getTerminator()}); // Check extend(). EXPECT_EQ(Span.top(), &*BB->begin()); EXPECT_EQ(Span.bottom(), BB->getTerminator()); auto *N0 = cast(DAG.getNode(S0)); auto *N1 = cast(DAG.getNode(S1)); auto *N2 = DAG.getNode(Ret); // Check getInstruction(). EXPECT_EQ(N0->getInstruction(), S0); EXPECT_EQ(N1->getInstruction(), S1); // Check hasMemPred() EXPECT_TRUE(N1->hasMemPred(N0)); EXPECT_FALSE(N0->hasMemPred(N1)); // Check preds(). EXPECT_TRUE(N0->preds(DAG).empty()); EXPECT_THAT(N1->preds(DAG), testing::ElementsAre(N0)); // Check memPreds(). EXPECT_TRUE(N0->memPreds().empty()); EXPECT_THAT(N1->memPreds(), testing::ElementsAre(N0)); EXPECT_TRUE(N2->preds(DAG).empty()); // Check memSuccs(). EXPECT_THAT(N0->memSuccs(), testing::ElementsAre(N1)); // Check UnscheduledSuccs. EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); // N1 EXPECT_EQ(N1->getNumUnscheduledSuccs(), 0u); EXPECT_EQ(N2->getNumUnscheduledSuccs(), 0u); // Check decrUnscheduledSuccs. N0->decrUnscheduledSuccs(); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u); #ifndef NDEBUG EXPECT_DEATH(N0->decrUnscheduledSuccs(), ".*Counting.*"); #endif // NDEBUG // Check scheduled(), setScheduled(). EXPECT_FALSE(N0->scheduled()); N0->setScheduled(true); EXPECT_TRUE(N0->scheduled()); } TEST_F(DependencyGraphTest, AddRemoveMemPred) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v0, i8 %v1) { store i8 %v0, ptr %ptr store i8 %v1, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S0 = cast(&*It++); auto *S1 = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto *N0 = cast(DAG.getNode(S0)); auto *N1 = cast(DAG.getNode(S1)); // Check removeMemPred(). EXPECT_FALSE(N0->memSuccs().empty()); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); N1->removeMemPred(N0); EXPECT_TRUE(N1->memPreds().empty()); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u); // Check addMemPred(). N1->addMemPred(N0); EXPECT_THAT(N1->memPreds(), testing::UnorderedElementsAre(N0)); EXPECT_THAT(N0->memSuccs(), testing::UnorderedElementsAre(N1)); EXPECT_THAT(N0->getNumUnscheduledSuccs(), 1u); } TEST_F(DependencyGraphTest, Preds) { parseIR(C, R"IR( declare ptr @bar(i8) define i8 @foo(i8 %v0, i8 %v1) { %add0 = add i8 %v0, %v0 %add1 = add i8 %v1, %v1 %add2 = add i8 %add0, %add1 %ptr = call ptr @bar(i8 %add1) store i8 %add2, ptr %ptr ret i8 %add2 } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto *AddN0 = DAG.getNode(cast(&*It++)); auto *AddN1 = DAG.getNode(cast(&*It++)); auto *AddN2 = DAG.getNode(cast(&*It++)); auto *CallN = DAG.getNode(cast(&*It++)); auto *StN = DAG.getNode(cast(&*It++)); auto *RetN = DAG.getNode(cast(&*It++)); // Check preds(). EXPECT_THAT(AddN0->preds(DAG), testing::ElementsAre()); EXPECT_THAT(AddN1->preds(DAG), testing::ElementsAre()); EXPECT_THAT(AddN2->preds(DAG), testing::ElementsAre(AddN0, AddN1)); EXPECT_THAT(CallN->preds(DAG), testing::ElementsAre(AddN1)); EXPECT_THAT(StN->preds(DAG), testing::UnorderedElementsAre(CallN, CallN, AddN2)); EXPECT_THAT(RetN->preds(DAG), testing::ElementsAre(AddN2)); // Check UnscheduledSuccs. EXPECT_EQ(AddN0->getNumUnscheduledSuccs(), 1u); // AddN2 EXPECT_EQ(AddN1->getNumUnscheduledSuccs(), 2u); // AddN2, CallN EXPECT_EQ(AddN2->getNumUnscheduledSuccs(), 2u); // StN, RetN EXPECT_EQ(CallN->getNumUnscheduledSuccs(), 2u); // StN, StN EXPECT_EQ(StN->getNumUnscheduledSuccs(), 0u); EXPECT_EQ(RetN->getNumUnscheduledSuccs(), 0u); } // Make sure we don't get null predecessors even if they are outside the DAG. TEST_F(DependencyGraphTest, NonNullPreds) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %val) { %gep = getelementptr i8, ptr %ptr, i32 0 store i8 %val, ptr %gep ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); [[maybe_unused]] auto *GEP = cast(&*It++); auto *S0 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); // The DAG doesn't include GEP. DAG.extend({S0, Ret}); auto *S0N = DAG.getNode(S0); // S0 has one operand (the GEP) that is outside the DAG and no memory // predecessors. So pred_begin() should be == pred_end(). auto PredIt = S0N->preds_begin(DAG); auto PredItE = S0N->preds_end(DAG); EXPECT_EQ(PredIt, PredItE); // Check preds(). for (auto *PredN : S0N->preds(DAG)) EXPECT_NE(PredN, nullptr); } TEST_F(DependencyGraphTest, MemDGNode_getPrevNode_getNextNode) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v0, i8 %v1) { store i8 %v0, ptr %ptr add i8 %v0, %v0 store i8 %v1, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S0 = cast(&*It++); [[maybe_unused]] auto *Add = cast(&*It++); auto *S1 = cast(&*It++); [[maybe_unused]] auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto *S0N = cast(DAG.getNode(S0)); auto *S1N = cast(DAG.getNode(S1)); EXPECT_EQ(S0N->getPrevNode(), nullptr); EXPECT_EQ(S0N->getNextNode(), S1N); EXPECT_EQ(S1N->getPrevNode(), S0N); EXPECT_EQ(S1N->getNextNode(), nullptr); } TEST_F(DependencyGraphTest, DGNodeRange) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v0, i8 %v1) { add i8 %v0, %v0 store i8 %v0, ptr %ptr add i8 %v0, %v0 store i8 %v1, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add0 = cast(&*It++); auto *S0 = cast(&*It++); auto *Add1 = cast(&*It++); auto *S1 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto *S0N = cast(DAG.getNode(S0)); auto *S1N = cast(DAG.getNode(S1)); // Check getTopMemDGNode(). using B = sandboxir::MemDGNodeIntervalBuilder; using InstrInterval = sandboxir::Interval; EXPECT_EQ(B::getTopMemDGNode(InstrInterval(S0, S0), DAG), S0N); EXPECT_EQ(B::getTopMemDGNode(InstrInterval(S0, Ret), DAG), S0N); EXPECT_EQ(B::getTopMemDGNode(InstrInterval(Add0, Add1), DAG), S0N); EXPECT_EQ(B::getTopMemDGNode(InstrInterval(Add0, Add0), DAG), nullptr); // Check getBotMemDGNode(). EXPECT_EQ(B::getBotMemDGNode(InstrInterval(S1, S1), DAG), S1N); EXPECT_EQ(B::getBotMemDGNode(InstrInterval(Add0, S1), DAG), S1N); EXPECT_EQ(B::getBotMemDGNode(InstrInterval(Add0, Ret), DAG), S1N); EXPECT_EQ(B::getBotMemDGNode(InstrInterval(Ret, Ret), DAG), nullptr); // Check empty range. EXPECT_THAT(sandboxir::MemDGNodeIntervalBuilder::makeEmpty(), testing::ElementsAre()); // Returns the pointers in Range. auto getPtrVec = [](const auto &Range) { SmallVector Vec; for (const sandboxir::DGNode &N : Range) Vec.push_back(&N); return Vec; }; // Both TopN and BotN are memory. EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({S0, S1}, DAG)), testing::ElementsAre(S0N, S1N)); // Only TopN is memory. EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({S0, Ret}, DAG)), testing::ElementsAre(S0N, S1N)); EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({S0, Add1}, DAG)), testing::ElementsAre(S0N)); // Only BotN is memory. EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({Add0, S1}, DAG)), testing::ElementsAre(S0N, S1N)); EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({Add0, S0}, DAG)), testing::ElementsAre(S0N)); // Neither TopN or BotN is memory. EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({Add0, Ret}, DAG)), testing::ElementsAre(S0N, S1N)); EXPECT_THAT( getPtrVec(sandboxir::MemDGNodeIntervalBuilder::make({Add0, Add0}, DAG)), testing::ElementsAre()); } TEST_F(DependencyGraphTest, AliasingStores) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v0, i8 %v1) { store i8 %v0, ptr %ptr store i8 %v1, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto It = BB->begin(); auto *Store0N = cast( DAG.getNode(cast(&*It++))); auto *Store1N = cast( DAG.getNode(cast(&*It++))); auto *RetN = DAG.getNode(cast(&*It++)); EXPECT_TRUE(Store0N->memPreds().empty()); EXPECT_THAT(Store1N->memPreds(), testing::ElementsAre(Store0N)); EXPECT_TRUE(RetN->preds(DAG).empty()); } TEST_F(DependencyGraphTest, NonAliasingStores) { parseIR(C, R"IR( define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v0, i8 %v1) { store i8 %v0, ptr %ptr0 store i8 %v1, ptr %ptr1 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto It = BB->begin(); auto *Store0N = cast( DAG.getNode(cast(&*It++))); auto *Store1N = cast( DAG.getNode(cast(&*It++))); auto *RetN = DAG.getNode(cast(&*It++)); // We expect no dependencies because the stores don't alias. EXPECT_TRUE(Store0N->memPreds().empty()); EXPECT_TRUE(Store1N->memPreds().empty()); EXPECT_TRUE(RetN->preds(DAG).empty()); } TEST_F(DependencyGraphTest, VolatileLoads) { parseIR(C, R"IR( define void @foo(ptr noalias %ptr0, ptr noalias %ptr1) { %ld0 = load volatile i8, ptr %ptr0 %ld1 = load volatile i8, ptr %ptr1 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto It = BB->begin(); auto *Ld0N = cast( DAG.getNode(cast(&*It++))); auto *Ld1N = cast( DAG.getNode(cast(&*It++))); auto *RetN = DAG.getNode(cast(&*It++)); EXPECT_TRUE(Ld0N->memPreds().empty()); EXPECT_THAT(Ld1N->memPreds(), testing::ElementsAre(Ld0N)); EXPECT_TRUE(RetN->preds(DAG).empty()); } TEST_F(DependencyGraphTest, VolatileStores) { parseIR(C, R"IR( define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v) { store volatile i8 %v, ptr %ptr0 store volatile i8 %v, ptr %ptr1 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()}); auto It = BB->begin(); auto *Store0N = cast( DAG.getNode(cast(&*It++))); auto *Store1N = cast( DAG.getNode(cast(&*It++))); auto *RetN = DAG.getNode(cast(&*It++)); EXPECT_TRUE(Store0N->memPreds().empty()); EXPECT_THAT(Store1N->memPreds(), testing::ElementsAre(Store0N)); EXPECT_TRUE(RetN->preds(DAG).empty()); } TEST_F(DependencyGraphTest, Call) { parseIR(C, R"IR( declare void @bar1() declare void @bar2() define void @foo(float %v1, float %v2) { call void @bar1() %add = fadd float %v1, %v2 call void @bar2() ret void } )IR"); Function *LLVMF = M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()}); auto It = BB->begin(); auto *Call1N = cast(DAG.getNode(&*It++)); auto *AddN = DAG.getNode(&*It++); auto *Call2N = cast(DAG.getNode(&*It++)); EXPECT_THAT(Call1N->memPreds(), testing::ElementsAre()); EXPECT_THAT(AddN->preds(DAG), testing::ElementsAre()); EXPECT_THAT(Call2N->memPreds(), testing::ElementsAre(Call1N)); } // Check that there is a dependency: stacksave -> alloca -> stackrestore. TEST_F(DependencyGraphTest, StackSaveRestoreInAlloca) { parseIR(C, R"IR( declare ptr @llvm.stacksave() declare void @llvm.stackrestore(ptr %ptr) define void @foo() { %stack0 = call ptr @llvm.stacksave() ; Should depend on store %alloca0 = alloca inalloca i8 ; Should depend on stacksave call void @llvm.stackrestore(ptr %stack0) ; Should depend transiently on %alloca0 ret void } )IR"); Function *LLVMF = M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()}); auto It = BB->begin(); auto *StackSaveN = DAG.getNode(&*It++); auto *AllocaN = DAG.getNode(&*It++); auto *StackRestoreN = DAG.getNode(&*It++); EXPECT_TRUE(memDependency(AllocaN, StackRestoreN)); EXPECT_TRUE(memDependency(StackSaveN, AllocaN)); } // Checks that stacksave and stackrestore depend on other mem instrs. TEST_F(DependencyGraphTest, StackSaveRestoreDependOnOtherMem) { parseIR(C, R"IR( declare ptr @llvm.stacksave() declare void @llvm.stackrestore(ptr %ptr) define void @foo(i8 %v0, i8 %v1, ptr %ptr) { store volatile i8 %v0, ptr %ptr, align 4 %stack0 = call ptr @llvm.stacksave() ; Should depend on store call void @llvm.stackrestore(ptr %stack0) ; Should depend on stacksave store volatile i8 %v1, ptr %ptr, align 4 ; Should depend on stackrestore ret void } )IR"); Function *LLVMF = M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()}); auto It = BB->begin(); auto *Store0N = DAG.getNode(&*It++); auto *StackSaveN = DAG.getNode(&*It++); auto *StackRestoreN = DAG.getNode(&*It++); auto *Store1N = DAG.getNode(&*It++); EXPECT_TRUE(memDependency(Store0N, StackSaveN)); EXPECT_TRUE(memDependency(StackSaveN, StackRestoreN)); EXPECT_TRUE(memDependency(StackRestoreN, Store1N)); } // Make sure there is a dependency between a stackrestore and an alloca. TEST_F(DependencyGraphTest, StackRestoreAndInAlloca) { parseIR(C, R"IR( declare void @llvm.stackrestore(ptr %ptr) define void @foo(ptr %ptr) { call void @llvm.stackrestore(ptr %ptr) %alloca0 = alloca inalloca i8 ; Should depend on stackrestore ret void } )IR"); Function *LLVMF = M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()}); auto It = BB->begin(); auto *StackRestoreN = DAG.getNode(&*It++); auto *AllocaN = DAG.getNode(&*It++); EXPECT_TRUE(memDependency(StackRestoreN, AllocaN)); } // Make sure there is a dependency between the alloca and stacksave TEST_F(DependencyGraphTest, StackSaveAndInAlloca) { parseIR(C, R"IR( declare ptr @llvm.stacksave() define void @foo(ptr %ptr) { %alloca0 = alloca inalloca i8 ; Should depend on stackrestore %stack0 = call ptr @llvm.stacksave() ; Should depend on alloca0 ret void } )IR"); Function *LLVMF = M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()}); auto It = BB->begin(); auto *AllocaN = DAG.getNode(&*It++); auto *StackSaveN = DAG.getNode(&*It++); EXPECT_TRUE(memDependency(AllocaN, StackSaveN)); } // A non-InAlloca in a stacksave-stackrestore region does not need extra // dependencies. TEST_F(DependencyGraphTest, StackSaveRestoreNoInAlloca) { parseIR(C, R"IR( declare ptr @llvm.stacksave() declare void @llvm.stackrestore(ptr %ptr) declare void @use(ptr %ptr) define void @foo() { %stack = call ptr @llvm.stacksave() %alloca1 = alloca i8 ; No dependency call void @llvm.stackrestore(ptr %stack) ret void } )IR"); Function *LLVMF = M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()}); auto It = BB->begin(); auto *StackSaveN = DAG.getNode(&*It++); auto *AllocaN = DAG.getNode(&*It++); auto *StackRestoreN = DAG.getNode(&*It++); EXPECT_FALSE(memDependency(StackSaveN, AllocaN)); EXPECT_FALSE(memDependency(AllocaN, StackRestoreN)); } TEST_F(DependencyGraphTest, Extend) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %v3, i8 %v4, i8 %v5) { store i8 %v1, ptr %ptr store i8 %v2, ptr %ptr store i8 %v3, ptr %ptr store i8 %v4, ptr %ptr store i8 %v5, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S1 = cast(&*It++); auto *S2 = cast(&*It++); auto *S3 = cast(&*It++); auto *S4 = cast(&*It++); auto *S5 = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); { // Scenario 1: Build new DAG auto NewIntvl = DAG.extend({S3, S3}); EXPECT_EQ(NewIntvl, sandboxir::Interval(S3, S3)); EXPECT_EQ(DAG.getInterval().top(), S3); EXPECT_EQ(DAG.getInterval().bottom(), S3); [[maybe_unused]] auto *S3N = cast(DAG.getNode(S3)); // Check UnscheduledSuccs. EXPECT_EQ(S3N->getNumUnscheduledSuccs(), 0u); } { // Scenario 2: Extend below auto NewIntvl = DAG.extend({S5, S5}); EXPECT_EQ(NewIntvl, sandboxir::Interval(S4, S5)); auto *S3N = cast(DAG.getNode(S3)); auto *S4N = cast(DAG.getNode(S4)); auto *S5N = cast(DAG.getNode(S5)); EXPECT_TRUE(S4N->hasMemPred(S3N)); EXPECT_TRUE(S5N->hasMemPred(S4N)); EXPECT_TRUE(S5N->hasMemPred(S3N)); // Check UnscheduledSuccs. EXPECT_EQ(S3N->getNumUnscheduledSuccs(), 2u); // S4N, S5N EXPECT_EQ(S4N->getNumUnscheduledSuccs(), 1u); // S5N EXPECT_EQ(S5N->getNumUnscheduledSuccs(), 0u); } { // Scenario 3: Extend above auto NewIntvl = DAG.extend({S1, S2}); EXPECT_EQ(NewIntvl, sandboxir::Interval(S1, S2)); auto *S1N = cast(DAG.getNode(S1)); auto *S2N = cast(DAG.getNode(S2)); auto *S3N = cast(DAG.getNode(S3)); auto *S4N = cast(DAG.getNode(S4)); auto *S5N = cast(DAG.getNode(S5)); EXPECT_TRUE(S2N->hasMemPred(S1N)); EXPECT_TRUE(S3N->hasMemPred(S2N)); EXPECT_TRUE(S3N->hasMemPred(S1N)); EXPECT_TRUE(S4N->hasMemPred(S3N)); EXPECT_TRUE(S4N->hasMemPred(S2N)); EXPECT_TRUE(S4N->hasMemPred(S1N)); EXPECT_TRUE(S5N->hasMemPred(S4N)); EXPECT_TRUE(S5N->hasMemPred(S3N)); EXPECT_TRUE(S5N->hasMemPred(S2N)); EXPECT_TRUE(S5N->hasMemPred(S1N)); // Check UnscheduledSuccs. EXPECT_EQ(S1N->getNumUnscheduledSuccs(), 4u); // S2N, S3N, S4N, S5N EXPECT_EQ(S2N->getNumUnscheduledSuccs(), 3u); // S3N, S4N, S5N EXPECT_EQ(S3N->getNumUnscheduledSuccs(), 2u); // S4N, S5N EXPECT_EQ(S4N->getNumUnscheduledSuccs(), 1u); // S5N EXPECT_EQ(S5N->getNumUnscheduledSuccs(), 0u); } { // Check UnscheduledSuccs when a node is scheduled sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({S2, S2}); auto *S2N = cast(DAG.getNode(S2)); S2N->setScheduled(true); DAG.extend({S1, S1}); auto *S1N = cast(DAG.getNode(S1)); EXPECT_EQ(S1N->getNumUnscheduledSuccs(), 0u); // S1 is scheduled } } // Check that the DAG gets updated when we create a new instruction. TEST_F(DependencyGraphTest, CreateInstrCallback) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %v3, i8 %new1, i8 %new2) { store i8 %v1, ptr %ptr store i8 %v2, ptr %ptr store i8 %v3, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S1 = cast(&*It++); auto *S2 = cast(&*It++); auto *S3 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); // Create a DAG spanning S1 to S3. DAG.extend({S1, S3}); auto *ArgNew1 = F->getArg(4); auto *ArgNew2 = F->getArg(5); auto *Ptr = S1->getPointerOperand(); auto *S1MemN = cast(DAG.getNode(S1)); auto *S2MemN = cast(DAG.getNode(S2)); auto *S3MemN = cast(DAG.getNode(S3)); sandboxir::MemDGNode *New1MemN = nullptr; sandboxir::MemDGNode *New2MemN = nullptr; { // Create a new store before S3 (within the span of the DAG). sandboxir::StoreInst *NewS = sandboxir::StoreInst::create(ArgNew1, Ptr, Align(8), S3->getIterator(), /*IsVolatile=*/true, Ctx); // Check the MemDGNode chain. New1MemN = cast(DAG.getNode(NewS)); EXPECT_EQ(S2MemN->getNextNode(), New1MemN); EXPECT_EQ(New1MemN->getPrevNode(), S2MemN); EXPECT_EQ(New1MemN->getNextNode(), S3MemN); EXPECT_EQ(S3MemN->getPrevNode(), New1MemN); // Check dependencies. EXPECT_TRUE(memDependency(S1MemN, New1MemN)); EXPECT_TRUE(memDependency(S2MemN, New1MemN)); EXPECT_TRUE(memDependency(New1MemN, S3MemN)); } { // Create a new store before Ret (outside the current DAG). sandboxir::StoreInst *NewS = sandboxir::StoreInst::create(ArgNew2, Ptr, Align(8), Ret->getIterator(), /*IsVolatile=*/true, Ctx); // Check the MemDGNode chain. New2MemN = cast(DAG.getNode(NewS)); EXPECT_EQ(S3MemN->getNextNode(), New2MemN); EXPECT_EQ(New2MemN->getPrevNode(), S3MemN); EXPECT_EQ(New2MemN->getNextNode(), nullptr); // Check dependencies. EXPECT_TRUE(memDependency(S1MemN, New2MemN)); EXPECT_TRUE(memDependency(S2MemN, New2MemN)); EXPECT_TRUE(memDependency(New1MemN, New2MemN)); EXPECT_TRUE(memDependency(S3MemN, New2MemN)); } } TEST_F(DependencyGraphTest, EraseInstrCallback) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %v3, i8 %v4, i8 %arg) { store i8 %v1, ptr %ptr store i8 %v2, ptr %ptr store i8 %v3, ptr %ptr store i8 %v4, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S1 = cast(&*It++); auto *S2 = cast(&*It++); auto *S3 = cast(&*It++); auto *S4NotInDAG = cast(&*It++); // Check erase instruction callback. sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({S1, S3}); auto *S1MemN = cast(DAG.getNode(S1)); auto *S2MemN = cast(DAG.getNode(S2)); auto *S3MemN = cast(DAG.getNode(S3)); EXPECT_EQ(S1MemN->getNumUnscheduledSuccs(), 2u); EXPECT_EQ(S2MemN->getNumUnscheduledSuccs(), 1u); EXPECT_EQ(S3MemN->getNumUnscheduledSuccs(), 0u); S2->eraseFromParent(); // Check that the DAG Node for S2 no longer exists. auto *DeletedN = DAG.getNode(S2); EXPECT_TRUE(DeletedN == nullptr); // Check that dependencies are maintained. EXPECT_THAT(S3MemN->preds(DAG), testing::UnorderedElementsAre(S1MemN)); // Also check that UnscheduledSuccs was updated for S1. EXPECT_EQ(S1MemN->getNumUnscheduledSuccs(), 1u); // Check the MemDGNode chain. EXPECT_EQ(S1MemN->getNextNode(), S3MemN); EXPECT_EQ(S3MemN->getPrevNode(), S1MemN); // Check the chain when we erase the top node. S1->eraseFromParent(); EXPECT_EQ(S3MemN->getPrevNode(), nullptr); // Check that we don't crash if we erase a node not in the DAG. S4NotInDAG->eraseFromParent(); } // Same but check that we don't update UnscheduledSuccs when Node is scheduled. TEST_F(DependencyGraphTest, EraseInstrCallbackScheduled) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %v3, i8 %v4, i8 %arg) { store i8 %v1, ptr %ptr store i8 %v2, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S1 = cast(&*It++); auto *S2 = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({S1, S2}); auto *S1MemN = cast(DAG.getNode(S1)); auto *S2MemN = cast(DAG.getNode(S2)); EXPECT_EQ(S1MemN->getNumUnscheduledSuccs(), 1u); EXPECT_EQ(S2MemN->getNumUnscheduledSuccs(), 0u); // Mark S2 as scheduled and erase it. S2MemN->setScheduled(true); S2->eraseFromParent(); EXPECT_EQ(DAG.getNode(S2), nullptr); // Check that we did not update S1's UnscheduledSuccs EXPECT_EQ(S1MemN->getNumUnscheduledSuccs(), 1u); } TEST_F(DependencyGraphTest, MoveInstrCallback) { parseIR(C, R"IR( define void @foo(ptr %ptr, ptr %ptr2, i8 %v1, i8 %v2, i8 %v3, i8 %arg) { %ld0 = load i8, ptr %ptr2 store i8 %v1, ptr %ptr store i8 %v2, ptr %ptr store i8 %v3, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Ld = cast(&*It++); auto *S1 = cast(&*It++); auto *S2 = cast(&*It++); auto *S3 = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({Ld, S3}); auto *LdN = cast(DAG.getNode(Ld)); auto *S1N = cast(DAG.getNode(S1)); auto *S2N = cast(DAG.getNode(S2)); EXPECT_EQ(S1N->getPrevNode(), LdN); S1->moveBefore(Ld); EXPECT_EQ(S1N->getPrevNode(), nullptr); EXPECT_EQ(S1N->getNextNode(), LdN); EXPECT_EQ(LdN->getPrevNode(), S1N); EXPECT_EQ(LdN->getNextNode(), S2N); } // Check that the mem chain is maintained correctly when the move destination is // not a mem node. TEST_F(DependencyGraphTest, MoveInstrCallbackWithNonMemInstrs) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %arg) { %ld = load i8, ptr %ptr %zext1 = zext i8 %arg to i32 %zext2 = zext i8 %arg to i32 store i8 %v1, ptr %ptr store i8 %v2, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Ld = cast(&*It++); [[maybe_unused]] auto *Zext1 = cast(&*It++); auto *Zext2 = cast(&*It++); auto *S1 = cast(&*It++); auto *S2 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({Ld, S2}); auto *LdN = cast(DAG.getNode(Ld)); auto *S1N = cast(DAG.getNode(S1)); auto *S2N = cast(DAG.getNode(S2)); EXPECT_EQ(LdN->getNextNode(), S1N); EXPECT_EQ(S1N->getNextNode(), S2N); S1->moveBefore(Zext2); EXPECT_EQ(LdN->getNextNode(), S1N); EXPECT_EQ(S1N->getNextNode(), S2N); // Try move right after the end of the DAGInterval. S1->moveBefore(Ret); EXPECT_EQ(S2N->getNextNode(), S1N); EXPECT_EQ(S1N->getNextNode(), nullptr); } // Extending an "Old" interval with no mem instructions. TEST_F(DependencyGraphTest, ExtendDAGWithNoMem) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %v, i8 %v0, i8 %v1, i8 %v2, i8 %v3) { store i8 %v0, ptr %ptr store i8 %v1, ptr %ptr %zext1 = zext i8 %v to i32 %zext2 = zext i8 %v to i32 store i8 %v2, ptr %ptr store i8 %v3, ptr %ptr ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *S0 = cast(&*It++); auto *S1 = cast(&*It++); auto *Z1 = cast(&*It++); auto *Z2 = cast(&*It++); auto *S2 = cast(&*It++); auto *S3 = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); // Create a non-empty DAG that contains no memory instructions. DAG.extend({Z1, Z2}); // Now extend it downwards. DAG.extend({S2, S3}); EXPECT_TRUE(memDependency(DAG.getNode(S2), DAG.getNode(S3))); // Same but upwards. DAG.clear(); DAG.extend({Z1, Z2}); DAG.extend({S0, S1}); EXPECT_TRUE(memDependency(DAG.getNode(S0), DAG.getNode(S1))); } // Setting a Use with a setOperand(), RUW, RAUW etc. can add/remove use-def // edges. This needs to maintain the UnscheduledSuccs counter. TEST_F(DependencyGraphTest, MaintainUnscheduledSuccsOnUseSet) { parseIR(C, R"IR( define void @foo(i8 %v0, i8 %v1) { %add0 = add i8 %v0, %v1 %add1 = add i8 %add0, %v1 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *Arg0 = F->getArg(0); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add0 = cast(&*It++); auto *Add1 = cast(&*It++); sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); DAG.extend({Add0, Add1}); auto *N0 = DAG.getNode(Add0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); // Now change %add1 operand to not use %add0. Add1->setOperand(0, Arg0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u); // Restore it: %add0 is now used by %add1. Add1->setOperand(0, Add0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); // RAUW Add0->replaceAllUsesWith(Arg0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u); // Restore it: %add0 is now used by %add1. Add1->setOperand(0, Add0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); // RUWIf Add0->replaceUsesWithIf(Arg0, [](const auto &U) { return true; }); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u); // Restore it: %add0 is now used by %add1. Add1->setOperand(0, Add0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); // RUOW Add1->replaceUsesOfWith(Add0, Arg0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 0u); // Restore it: %add0 is now used by %add1. Add1->setOperand(0, Add0); EXPECT_EQ(N0->getNumUnscheduledSuccs(), 1u); }