[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