vporpo e4580d2195
[SandboxVec][DAG] Fix unscheduled succs when nodes are scheduled (#184946)
When we update use-def edges the DAG gets notified to update the
UnscheduledSuccs counters. However, if either edge node is already
scheduled we should not update UnscheduledSuccs because the
UnscheduledSuccs counter value should be treated as "undefined" after a
node has been scheduled, i.e., it's value has a meaning only before the
node gets scheduled.
2026-03-09 08:58:15 -07:00

1270 lines
43 KiB
C++

//===- 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);
}
// Make sure we maintain the unscheduled succs when the use-def edges cross the
// DAG boundaries, i.e., when have an external user.
TEST_F(DependencyGraphTest, MaintainUnscheduledSuccsExtUser) {
parseIR(C, R"IR(
define void @foo(i8 %v0, i8 %v1) {
%add0 = add i8 %v0, %v1
%add1 = add i8 %add0, %v1
%extUser = add i8 %v0, 0 ; This is not in the DAG
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++);
auto *ExtUser = cast<sandboxir::BinaryOperator>(&*It++);
// DAG does not contain extUser
sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
DAG.extend({Add0, Add1});
auto *Add0N = DAG.getNode(Add0);
EXPECT_EQ(DAG.getNode(ExtUser), nullptr);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
// Adding the use Add0->ExtUser shouldn't change Add's unscheduled succs.
ExtUser->setOperand(0, Add0);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
ExtUser->setOperand(0, Arg0);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
// Same with RUOW.
ExtUser->replaceUsesOfWith(Arg0, Add0);
EXPECT_EQ(ExtUser->getOperand(0), Add0);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
ExtUser->setOperand(0, Arg0);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
}
// Don't udpate the unscheduled succs if nodes have already been scheduled.
TEST_F(DependencyGraphTest, MaintainUnscheduledSuccsWhenScheduled) {
parseIR(C, R"IR(
define void @foo(i8 %v0) {
%add0 = add i8 %v0, 0
%add1 = add i8 %v0, 1
%modify = add i8 %add0, 1
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 *Add1 = cast<sandboxir::BinaryOperator>(&*It++);
auto *Modify = cast<sandboxir::BinaryOperator>(&*It++);
// DAG contains all Add0, Add1 and Modify
sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
DAG.extend({Add0, Add1, Modify});
auto *Add0N = DAG.getNode(Add0);
auto *Add1N = DAG.getNode(Add1);
// Mark Add0N and Add1N as scheduled.
Add0N->setScheduled(true);
Add1N->setScheduled(true);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
EXPECT_EQ(Add1N->getNumUnscheduledSuccs(), 0u);
// Change Modify's operand and make sure we won't update Add0N's or Add1N's
// unscheduled succs.
Modify->setOperand(0, Add1);
EXPECT_EQ(Add0N->getNumUnscheduledSuccs(), 1u);
EXPECT_EQ(Add1N->getNumUnscheduledSuccs(), 0u);
}