| //===- 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<Module> M; |
| std::unique_ptr<AssumptionCache> AC; |
| std::unique_ptr<DominatorTree> DT; |
| std::unique_ptr<BasicAAResult> BAA; |
| std::unique_ptr<AAResults> AA; |
| std::unique_ptr<TargetLibraryInfoImpl> TLII; |
| std::unique_ptr<TargetLibraryInfo> 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<TargetLibraryInfoImpl>(M->getTargetTriple()); |
| TLI = std::make_unique<TargetLibraryInfo>(*TLII); |
| } |
| |
| AAResults &getAA(llvm::Function &LLVMF) { |
| AA = std::make_unique<AAResults>(*TLI); |
| AC = std::make_unique<AssumptionCache>(LLVMF); |
| DT = std::make_unique<DominatorTree>(LLVMF); |
| BAA = std::make_unique<BasicAAResult>(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<sandboxir::MemDGNode>(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<sandboxir::BinaryOperator>(&*It++); |
| auto *StackSave = cast<sandboxir::CallInst>(&*It++); |
| auto *StackRestore = cast<sandboxir::CallInst>(&*It++); |
| auto *Other = cast<sandboxir::CallInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*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<sandboxir::BinaryOperator>(&*It++); |
| auto *Ld0 = cast<sandboxir::LoadInst>(&*It++); |
| auto *St0 = cast<sandboxir::StoreInst>(&*It++); |
| auto *SideEffect0 = cast<sandboxir::CallInst>(&*It++); |
| auto *PseudoProbe0 = cast<sandboxir::CallInst>(&*It++); |
| auto *OtherIntrinsic0 = cast<sandboxir::CallInst>(&*It++); |
| auto *CallBar = cast<sandboxir::CallInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*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<sandboxir::IntrinsicInst>(&*It++); |
| auto *PseudoProbe = cast<sandboxir::IntrinsicInst>(&*It++); |
| auto *OtherIntrinsic = cast<sandboxir::IntrinsicInst>(&*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<sandboxir::StoreInst>(&*It++); |
| auto *Load = cast<sandboxir::LoadInst>(&*It++); |
| auto *Add = cast<sandboxir::BinaryOperator>(&*It++); |
| auto *StackSave = cast<sandboxir::CallInst>(&*It++); |
| auto *StackRestore = cast<sandboxir::CallInst>(&*It++); |
| auto *SideEffect = cast<sandboxir::CallInst>(&*It++); |
| auto *PseudoProbe = cast<sandboxir::CallInst>(&*It++); |
| auto *FakeUse = cast<sandboxir::CallInst>(&*It++); |
| auto *Call = cast<sandboxir::CallInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({&*BB->begin(), BB->getTerminator()}); |
| EXPECT_TRUE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(Store))); |
| EXPECT_TRUE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(Load))); |
| EXPECT_FALSE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(Add))); |
| EXPECT_TRUE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(StackSave))); |
| EXPECT_TRUE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(StackRestore))); |
| EXPECT_FALSE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(SideEffect))); |
| EXPECT_FALSE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(PseudoProbe))); |
| EXPECT_TRUE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(FakeUse))); |
| EXPECT_TRUE(isa<llvm::sandboxir::MemDGNode>(DAG.getNode(Call))); |
| EXPECT_FALSE(isa<llvm::sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*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<sandboxir::MemDGNode>(DAG.getNode(S0)); |
| auto *N1 = cast<sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({&*BB->begin(), BB->getTerminator()}); |
| auto *N0 = cast<sandboxir::MemDGNode>(DAG.getNode(S0)); |
| auto *N1 = cast<sandboxir::MemDGNode>(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<sandboxir::BinaryOperator>(&*It++)); |
| auto *AddN1 = DAG.getNode(cast<sandboxir::BinaryOperator>(&*It++)); |
| auto *AddN2 = DAG.getNode(cast<sandboxir::BinaryOperator>(&*It++)); |
| auto *CallN = DAG.getNode(cast<sandboxir::CallInst>(&*It++)); |
| auto *StN = DAG.getNode(cast<sandboxir::StoreInst>(&*It++)); |
| auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*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<sandboxir::GetElementPtrInst>(&*It++); |
| auto *S0 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*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<sandboxir::StoreInst>(&*It++); |
| [[maybe_unused]] auto *Add = cast<sandboxir::BinaryOperator>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| [[maybe_unused]] auto *Ret = cast<sandboxir::ReturnInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({&*BB->begin(), BB->getTerminator()}); |
| |
| auto *S0N = cast<sandboxir::MemDGNode>(DAG.getNode(S0)); |
| auto *S1N = cast<sandboxir::MemDGNode>(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<sandboxir::BinaryOperator>(&*It++); |
| auto *S0 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Add1 = cast<sandboxir::BinaryOperator>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({&*BB->begin(), BB->getTerminator()}); |
| |
| auto *S0N = cast<sandboxir::MemDGNode>(DAG.getNode(S0)); |
| auto *S1N = cast<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| |
| // Check getTopMemDGNode(). |
| using B = sandboxir::MemDGNodeIntervalBuilder; |
| using InstrInterval = sandboxir::Interval<sandboxir::Instruction>; |
| 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<const sandboxir::DGNode *> 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<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::StoreInst>(&*It++))); |
| auto *Store1N = cast<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::StoreInst>(&*It++))); |
| auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*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<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::StoreInst>(&*It++))); |
| auto *Store1N = cast<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::StoreInst>(&*It++))); |
| auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*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<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::LoadInst>(&*It++))); |
| auto *Ld1N = cast<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::LoadInst>(&*It++))); |
| auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*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<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::StoreInst>(&*It++))); |
| auto *Store1N = cast<sandboxir::MemDGNode>( |
| DAG.getNode(cast<sandboxir::StoreInst>(&*It++))); |
| auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*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<sandboxir::MemDGNode>(DAG.getNode(&*It++)); |
| auto *AddN = DAG.getNode(&*It++); |
| auto *Call2N = cast<sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S3 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S4 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S5 = cast<sandboxir::StoreInst>(&*It++); |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| { |
| // Scenario 1: Build new DAG |
| auto NewIntvl = DAG.extend({S3, S3}); |
| EXPECT_EQ(NewIntvl, sandboxir::Interval<sandboxir::Instruction>(S3, S3)); |
| EXPECT_EQ(DAG.getInterval().top(), S3); |
| EXPECT_EQ(DAG.getInterval().bottom(), S3); |
| [[maybe_unused]] auto *S3N = cast<sandboxir::MemDGNode>(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<sandboxir::Instruction>(S4, S5)); |
| auto *S3N = cast<sandboxir::MemDGNode>(DAG.getNode(S3)); |
| auto *S4N = cast<sandboxir::MemDGNode>(DAG.getNode(S4)); |
| auto *S5N = cast<sandboxir::MemDGNode>(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<sandboxir::Instruction>(S1, S2)); |
| auto *S1N = cast<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| auto *S2N = cast<sandboxir::MemDGNode>(DAG.getNode(S2)); |
| auto *S3N = cast<sandboxir::MemDGNode>(DAG.getNode(S3)); |
| auto *S4N = cast<sandboxir::MemDGNode>(DAG.getNode(S4)); |
| auto *S5N = cast<sandboxir::MemDGNode>(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<sandboxir::MemDGNode>(DAG.getNode(S2)); |
| S2N->setScheduled(true); |
| |
| DAG.extend({S1, S1}); |
| auto *S1N = cast<sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S3 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*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<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| auto *S2MemN = cast<sandboxir::MemDGNode>(DAG.getNode(S2)); |
| auto *S3MemN = cast<sandboxir::MemDGNode>(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<sandboxir::MemDGNode>(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<sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S3 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S4NotInDAG = cast<sandboxir::StoreInst>(&*It++); |
| |
| // Check erase instruction callback. |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({S1, S3}); |
| auto *S1MemN = cast<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| auto *S2MemN = cast<sandboxir::MemDGNode>(DAG.getNode(S2)); |
| auto *S3MemN = cast<sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({S1, S2}); |
| auto *S1MemN = cast<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| auto *S2MemN = cast<sandboxir::MemDGNode>(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<sandboxir::LoadInst>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S3 = cast<sandboxir::StoreInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({Ld, S3}); |
| auto *LdN = cast<sandboxir::MemDGNode>(DAG.getNode(Ld)); |
| auto *S1N = cast<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| auto *S2N = cast<sandboxir::MemDGNode>(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<sandboxir::LoadInst>(&*It++); |
| [[maybe_unused]] auto *Zext1 = cast<sandboxir::CastInst>(&*It++); |
| auto *Zext2 = cast<sandboxir::CastInst>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Ret = cast<sandboxir::ReturnInst>(&*It++); |
| |
| sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx); |
| DAG.extend({Ld, S2}); |
| auto *LdN = cast<sandboxir::MemDGNode>(DAG.getNode(Ld)); |
| auto *S1N = cast<sandboxir::MemDGNode>(DAG.getNode(S1)); |
| auto *S2N = cast<sandboxir::MemDGNode>(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<sandboxir::StoreInst>(&*It++); |
| auto *S1 = cast<sandboxir::StoreInst>(&*It++); |
| auto *Z1 = cast<sandboxir::CastInst>(&*It++); |
| auto *Z2 = cast<sandboxir::CastInst>(&*It++); |
| auto *S2 = cast<sandboxir::StoreInst>(&*It++); |
| auto *S3 = cast<sandboxir::StoreInst>(&*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<sandboxir::BinaryOperator>(&*It++); |
| auto *Add1 = cast<sandboxir::BinaryOperator>(&*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); |
| } |