[llvm] [SandboxVec][DAG] Implement missing API for successors (PR #195301)
via llvm-commits
llvm-commits at lists.llvm.org
Fri May 1 10:21:35 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-vectorizers
@llvm/pr-subscribers-llvm-transforms
Author: vporpo (vporpo)
<details>
<summary>Changes</summary>
This patch implements the missing API for accessing the DAG successors. This includes the successor iterators and DAG Node member functions like succs(). These are mirroring the existing predecessor API.
---
Full diff: https://github.com/llvm/llvm-project/pull/195301.diff
3 Files Affected:
- (modified) llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h (+77)
- (modified) llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp (+57)
- (modified) llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp (+58)
``````````diff
diff --git a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h
index 95accc92979e3..4a8605a262d6d 100644
--- a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h
+++ b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h
@@ -90,6 +90,49 @@ class PredIterator {
bool operator!=(const PredIterator &Other) const { return !(*this == Other); }
};
+/// Iterate over both def-use and mem dependencies.
+class SuccIterator {
+ User::user_iterator UserIt;
+ User::user_iterator UserItE;
+ DenseSet<MemDGNode *>::iterator MemIt;
+ DGNode *N = nullptr;
+ DependencyGraph *DAG = nullptr;
+
+ SuccIterator(const Value::user_iterator &UserIt,
+ const Value::user_iterator &UserItE,
+ const DenseSet<MemDGNode *>::iterator &MemIt, DGNode *N,
+ DependencyGraph &DAG)
+ : UserIt(UserIt), UserItE(UserItE), MemIt(MemIt), N(N), DAG(&DAG) {}
+ SuccIterator(const User::user_iterator &UserIt,
+ const User::user_iterator &UserItE, DGNode *N,
+ DependencyGraph &DAG)
+ : UserIt(UserIt), UserItE(UserItE), N(N), DAG(&DAG) {}
+ friend class DGNode; // For constructor
+ friend class MemDGNode; // For constructor
+
+ /// Skip iterators that don't point instructions or are outside \p DAG,
+ /// starting from \p OpIt and ending before \p OpItE.n
+ LLVM_ABI static User::user_iterator skipBadIt(User::user_iterator UserIt,
+ User::user_iterator UserItE,
+ const DependencyGraph &DAG);
+
+public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = DGNode *;
+ using pointer = value_type *;
+ using reference = value_type &;
+ using iterator_category = std::input_iterator_tag;
+ LLVM_ABI value_type operator*();
+ LLVM_ABI SuccIterator &operator++();
+ SuccIterator operator++(int) {
+ auto Copy = *this;
+ ++(*this);
+ return Copy;
+ }
+ LLVM_ABI bool operator==(const SuccIterator &Other) const;
+ bool operator!=(const SuccIterator &Other) const { return !(*this == Other); }
+};
+
/// A DependencyGraph Node that points to an Instruction and contains memory
/// dependency edges.
class LLVM_ABI DGNode {
@@ -175,6 +218,29 @@ class LLVM_ABI DGNode {
return make_range(preds_begin(DAG), preds_end(DAG));
}
+ using succ_iterator = SuccIterator;
+ virtual succ_iterator succs_begin(DependencyGraph &DAG) {
+ return SuccIterator(
+ SuccIterator::skipBadIt(I->user_begin(), I->user_end(), DAG),
+ I->user_end(), this, DAG);
+ }
+ virtual succ_iterator succs_end(DependencyGraph &DAG) {
+ return SuccIterator(I->user_end(), I->user_end(), this, DAG);
+ }
+ succ_iterator succs_begin(DependencyGraph &DAG) const {
+ return const_cast<DGNode *>(this)->succs_begin(DAG);
+ }
+ succ_iterator succs_end(DependencyGraph &DAG) const {
+ return const_cast<DGNode *>(this)->succs_end(DAG);
+ }
+ /// \Returns a range of DAG successor nodes. If this is a MemDGNode then
+ /// this will also include the memory dependency successors.
+ /// Please note that this can include the same node more than once, if for
+ /// example it's both a use-def predecessor and a mem dep successor.
+ iterator_range<succ_iterator> succs(DependencyGraph &DAG) const {
+ return make_range(succs_begin(DAG), succs_end(DAG));
+ }
+
static bool isStackSaveOrRestoreIntrinsic(Instruction *I) {
if (auto *II = dyn_cast<IntrinsicInst>(I)) {
auto IID = II->getIntrinsicID();
@@ -238,6 +304,7 @@ class MemDGNode final : public DGNode {
/// Memory successors.
DenseSet<MemDGNode *> MemSuccs;
friend class PredIterator; // For MemPreds.
+ friend class SuccIterator; // For MemSuccs.
/// Creates both edges: this<->N.
void setNextNode(MemDGNode *N) {
assert(N != this && "About to point to self!");
@@ -277,6 +344,16 @@ class MemDGNode final : public DGNode {
iterator preds_end(DependencyGraph &DAG) override {
return PredIterator(I->op_end(), I->op_end(), MemPreds.end(), this, DAG);
}
+ succ_iterator succs_begin(DependencyGraph &DAG) override {
+ auto UserEndIt = I->user_end();
+ return SuccIterator(
+ SuccIterator::skipBadIt(I->user_begin(), UserEndIt, DAG), UserEndIt,
+ MemSuccs.begin(), this, DAG);
+ }
+ succ_iterator succs_end(DependencyGraph &DAG) override {
+ return SuccIterator(I->user_end(), I->user_end(), MemSuccs.end(), this,
+ DAG);
+ }
/// \Returns the previous Mem DGNode in instruction order.
MemDGNode *getPrevNode() const { return PrevMemN; }
/// \Returns the next Mem DGNode in instruction order.
diff --git a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp
index 234a78fceac56..360c409bdb54f 100644
--- a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp
+++ b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp
@@ -71,6 +71,63 @@ bool PredIterator::operator==(const PredIterator &Other) const {
return OpIt == Other.OpIt && MemIt == Other.MemIt;
}
+User::user_iterator SuccIterator::skipBadIt(User::user_iterator UserIt,
+ User::user_iterator UserItE,
+ const DependencyGraph &DAG) {
+ auto Skip = [&DAG](User::user_iterator UserIt) {
+ auto *I = dyn_cast<Instruction>(*UserIt);
+ return I == nullptr || DAG.getNode(I) == nullptr;
+ };
+ while (UserIt != UserItE && Skip(UserIt))
+ ++UserIt;
+ return UserIt;
+}
+
+SuccIterator::value_type SuccIterator::operator*() {
+ // If it's a DGNode then we dereference the user iterator.
+ if (!isa<MemDGNode>(N)) {
+ assert(UserIt != UserItE && "Can't dereference end iterator!");
+ return DAG->getNode(cast<Instruction>((Value *)*UserIt));
+ }
+ // It's a MemDGNode, so we check if we return either the def-use operand,
+ // or a mem predecessor.
+ if (UserIt != UserItE)
+ return DAG->getNode(cast<Instruction>((Value *)*UserIt));
+ // It's a MemDGNode with UserIt == end, so we need to use MemIt.
+ assert(MemIt != cast<MemDGNode>(N)->MemSuccs.end() &&
+ "Cant' dereference end iterator!");
+ return *MemIt;
+}
+
+SuccIterator &SuccIterator::operator++() {
+ // If it's a DGNode then we increment the use-def iterator.
+ if (!isa<MemDGNode>(N)) {
+ assert(UserIt != UserItE && "Already at end!");
+ ++UserIt;
+ // Skip users that are not instructions or are outside the DAG.
+ UserIt = SuccIterator::skipBadIt(UserIt, UserItE, *DAG);
+ return *this;
+ }
+ // It's a MemDGNode, so if we are not at the end of the def-use iterator we
+ // need to first increment that.
+ if (UserIt != UserItE) {
+ ++UserIt;
+ // Skip operands that are not instructions or are outside the DAG.
+ UserIt = SuccIterator::skipBadIt(UserIt, UserItE, *DAG);
+ return *this;
+ }
+ // It's a MemDGNode with UserIt == end, so we need to increment MemIt.
+ assert(MemIt != cast<MemDGNode>(N)->MemSuccs.end() && "Already at end!");
+ ++MemIt;
+ return *this;
+}
+
+bool SuccIterator::operator==(const SuccIterator &Other) const {
+ assert(DAG == Other.DAG && "Iterators of different DAGs!");
+ assert(N == Other.N && "Iterators of different nodes!");
+ return UserIt == Other.UserIt && MemIt == Other.MemIt;
+}
+
void DGNode::setSchedBundle(SchedBundle &SB) {
if (this->SB != nullptr)
this->SB->eraseFromBundle(this);
diff --git a/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp b/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp
index 2658461655e91..dd28337e0b1a9 100644
--- a/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp
@@ -250,6 +250,10 @@ define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
EXPECT_TRUE(N0->preds(DAG).empty());
EXPECT_THAT(N1->preds(DAG), testing::ElementsAre(N0));
+ // Check succs().
+ EXPECT_THAT(N0->succs(DAG), testing::ElementsAre(N1));
+ EXPECT_TRUE(N1->succs(DAG).empty());
+
// Check memPreds().
EXPECT_TRUE(N0->memPreds().empty());
EXPECT_THAT(N1->memPreds(), testing::ElementsAre(N0));
@@ -347,6 +351,13 @@ define i8 @foo(i8 %v0, i8 %v1) {
testing::UnorderedElementsAre(CallN, CallN, AddN2));
EXPECT_THAT(RetN->preds(DAG), testing::ElementsAre(AddN2));
+ // Check succs().
+ EXPECT_THAT(AddN0->succs(DAG), testing::ElementsAre(AddN2));
+ EXPECT_THAT(AddN1->succs(DAG), testing::UnorderedElementsAre(AddN2, CallN));
+ EXPECT_THAT(AddN2->succs(DAG), testing::UnorderedElementsAre(StN, RetN));
+ EXPECT_THAT(CallN->succs(DAG), testing::ElementsAre(StN, StN));
+ EXPECT_THAT(RetN->succs(DAG), testing::ElementsAre());
+
// Check UnscheduledSuccs.
EXPECT_EQ(AddN0->getNumUnscheduledSuccs(), 1u); // AddN2
EXPECT_EQ(AddN1->getNumUnscheduledSuccs(), 2u); // AddN2, CallN
@@ -389,6 +400,39 @@ define void @foo(ptr %ptr, i8 %val) {
EXPECT_NE(PredN, nullptr);
}
+// Make sure we don't get null successors even if they are outside the DAG.
+TEST_F(DependencyGraphTest, NonNullSuccs) {
+ 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();
+ auto *GEP = cast<sandboxir::GetElementPtrInst>(&*It++);
+ auto *S0 = cast<sandboxir::StoreInst>(&*It++);
+ [[maybe_unused]] auto *Ret = cast<sandboxir::ReturnInst>(&*It++);
+
+ sandboxir::DependencyGraph DAG(getAA(*LLVMF), Ctx);
+ // The DAG doesn't include S0.
+ DAG.extend({GEP, GEP});
+
+ auto *GEPN = DAG.getNode(GEP);
+ // GEPN has one user (S0) that is outside the DAG and no memory
+ // successors. So succs_begin() should be == succs_end().
+ auto SuccIt = GEPN->succs_begin(DAG);
+ auto SuccItE = GEPN->succs_end(DAG);
+ EXPECT_EQ(SuccIt, SuccItE);
+ // Check succs().
+ for (auto *SuccN : GEPN->succs(DAG))
+ EXPECT_NE(SuccN, nullptr);
+}
+
TEST_F(DependencyGraphTest, MemDGNode_getPrevNode_getNextNode) {
parseIR(C, R"IR(
define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
@@ -523,6 +567,9 @@ define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
EXPECT_TRUE(Store0N->memPreds().empty());
EXPECT_THAT(Store1N->memPreds(), testing::ElementsAre(Store0N));
EXPECT_TRUE(RetN->preds(DAG).empty());
+ EXPECT_THAT(Store0N->memSuccs(), testing::ElementsAre(Store1N));
+ EXPECT_TRUE(Store1N->memSuccs().empty());
+ EXPECT_THAT(Store0N->succs(DAG), testing::ElementsAre(Store1N));
}
TEST_F(DependencyGraphTest, NonAliasingStores) {
@@ -549,6 +596,8 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v0, i8 %v1) {
EXPECT_TRUE(Store0N->memPreds().empty());
EXPECT_TRUE(Store1N->memPreds().empty());
EXPECT_TRUE(RetN->preds(DAG).empty());
+ EXPECT_TRUE(Store1N->memSuccs().empty());
+ EXPECT_TRUE(Store0N->succs(DAG).empty());
}
TEST_F(DependencyGraphTest, VolatileLoads) {
@@ -573,7 +622,9 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1) {
auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*It++));
EXPECT_TRUE(Ld0N->memPreds().empty());
EXPECT_THAT(Ld1N->memPreds(), testing::ElementsAre(Ld0N));
+ EXPECT_THAT(Ld0N->memSuccs(), testing::ElementsAre(Ld1N));
EXPECT_TRUE(RetN->preds(DAG).empty());
+ EXPECT_THAT(Ld0N->succs(DAG), testing::ElementsAre(Ld1N));
}
TEST_F(DependencyGraphTest, VolatileStores) {
@@ -599,6 +650,8 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v) {
EXPECT_TRUE(Store0N->memPreds().empty());
EXPECT_THAT(Store1N->memPreds(), testing::ElementsAre(Store0N));
EXPECT_TRUE(RetN->preds(DAG).empty());
+ EXPECT_THAT(Store0N->memSuccs(), testing::ElementsAre(Store1N));
+ EXPECT_THAT(Store0N->succs(DAG), testing::ElementsAre(Store1N));
}
TEST_F(DependencyGraphTest, Call) {
@@ -629,6 +682,10 @@ define void @foo(float %v1, float %v2) {
EXPECT_THAT(Call1N->memPreds(), testing::ElementsAre());
EXPECT_THAT(AddN->preds(DAG), testing::ElementsAre());
EXPECT_THAT(Call2N->memPreds(), testing::ElementsAre(Call1N));
+
+ EXPECT_THAT(Call1N->memSuccs(), testing::ElementsAre(Call2N));
+ EXPECT_THAT(Call1N->succs(DAG), testing::ElementsAre(Call2N));
+ EXPECT_THAT(Call2N->succs(DAG), testing::ElementsAre());
}
// Check that there is a dependency: stacksave -> alloca -> stackrestore.
@@ -978,6 +1035,7 @@ define void @foo(ptr %ptr, i8 %v1, i8 %v2, i8 %v3, i8 %v4, i8 %arg) {
EXPECT_TRUE(DeletedN == nullptr);
// Check that dependencies are maintained.
EXPECT_THAT(S3MemN->preds(DAG), testing::UnorderedElementsAre(S1MemN));
+ EXPECT_THAT(S1MemN->succs(DAG), testing::UnorderedElementsAre(S3MemN));
// Also check that UnscheduledSuccs was updated for S1.
EXPECT_EQ(S1MemN->getNumUnscheduledSuccs(), 1u);
``````````
</details>
https://github.com/llvm/llvm-project/pull/195301
More information about the llvm-commits
mailing list