Skip to content

[SCCP] Improve worklist management #146321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 30, 2025
Merged

[SCCP] Improve worklist management #146321

merged 2 commits into from
Jun 30, 2025

Conversation

nikic
Copy link
Contributor

@nikic nikic commented Jun 30, 2025

SCCP currently stores instructions whose lattice value has changed in a worklist, and then updates their users in the main loop. This may result in instructions unnecessarily being visited multiple times (as an instruction will often use multiple other instructions). Additionally, we'd often redundantly visit instructions that were already visited when the containing block first became executable.

Instead, change the worklist to directly store the instructions that need to be revisited. Additionally, do not add instructions to the worklist that will already be covered by the main basic block walk.

This change is conceptually NFC, but is expected to produce minor differences in practice, because the visitation order interacts with the range widening limit.

Compile-time improvement: https://llvm-compile-time-tracker.com/compare.php?from=7354123c34e658e990559a36b1ac7eb0b671e317&to=8f1fac228d7ebbddd8a2aa00e47ee3f5712db125&stat=instructions:u

SCCP currently stores instructions whose lattice value has changed
in a worklist, and then updates their users in the main loop. This
may result in instructions unnecessarily being visited multiple
times (as an instruction will often use multiple other instructions).
Additionally, we'd often redundantly visit instructions that were
already visited when the containing block first became executable.

Instead, change the worklist to directly store the instructions that
need to be revisited. Additionally, do not add instructions to the
worklist that will already be covered by the main basic block walk.

This change is conceptually NFC, but is expected to produce minor
difference in practice, because the visitation order interacts
with the range widening limit
@llvmbot
Copy link
Member

llvmbot commented Jun 30, 2025

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-function-specialization

Author: Nikita Popov (nikic)

Changes

SCCP currently stores instructions whose lattice value has changed in a worklist, and then updates their users in the main loop. This may result in instructions unnecessarily being visited multiple times (as an instruction will often use multiple other instructions). Additionally, we'd often redundantly visit instructions that were already visited when the containing block first became executable.

Instead, change the worklist to directly store the instructions that need to be revisited. Additionally, do not add instructions to the worklist that will already be covered by the main basic block walk.

This change is conceptually NFC, but is expected to produce minor difference in practice, because the visitation order interacts with the range widening limit.

Compile-time improvement: https://llvm-compile-time-tracker.com/compare.php?from=7354123c34e658e990559a36b1ac7eb0b671e317&to=8f1fac228d7ebbddd8a2aa00e47ee3f5712db125&stat=instructions:u


Full diff: https://github.com/llvm/llvm-project/pull/146321.diff

1 Files Affected:

  • (modified) llvm/lib/Transforms/Utils/SCCPSolver.cpp (+66-101)
diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index cb6aafa279b5f..d72e9e9ea5c56 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -426,7 +426,10 @@ void SCCPSolver::inferArgAttributes() const {
 class SCCPInstVisitor : public InstVisitor<SCCPInstVisitor> {
   const DataLayout &DL;
   std::function<const TargetLibraryInfo &(Function &)> GetTLI;
-  SmallPtrSet<BasicBlock *, 8> BBExecutable; // The BBs that are executable.
+  /// Basic blocks that are executable (but may not have been visited yet).
+  SmallPtrSet<BasicBlock *, 8> BBExecutable;
+  /// Basic blocks that are executable and have been visited at least once.
+  SmallPtrSet<BasicBlock *, 8> BBVisited;
   DenseMap<Value *, ValueLatticeElement>
       ValueState; // The state each value is in.
 
@@ -466,15 +469,14 @@ class SCCPInstVisitor : public InstVisitor<SCCPInstVisitor> {
   /// constants.
   SmallPtrSet<Function *, 16> TrackingIncomingArguments;
 
-  /// The reason for two worklists is that overdefined is the lowest state
-  /// on the lattice, and moving things to overdefined as fast as possible
-  /// makes SCCP converge much faster.
-  ///
-  /// By having a separate worklist, we accomplish this because everything
-  /// possibly overdefined will become overdefined at the soonest possible
-  /// point.
-  SmallVector<Value *, 64> OverdefinedInstWorkList;
-  SmallVector<Value *, 64> InstWorkList;
+  /// Worklist of instructions to re-visit. This only includes instructions
+  /// in blocks that have already been visited at least once.
+  SmallSetVector<Instruction *, 16> InstWorkList;
+
+  /// Current instruction while visiting a block for the first time, used to
+  /// avoid unnecessary instruction worklist insertions. Null if an instruction
+  /// is visited outside a whole-block visitation.
+  Instruction *CurI = nullptr;
 
   // The BasicBlock work list
   SmallVector<BasicBlock *, 64> BBWorkList;
@@ -497,12 +499,15 @@ class SCCPInstVisitor : public InstVisitor<SCCPInstVisitor> {
     return dyn_cast_or_null<ConstantInt>(getConstant(IV, Ty));
   }
 
-  // pushToWorkList - Helper for markConstant/markOverdefined
-  void pushToWorkList(ValueLatticeElement &IV, Value *V);
+  /// Push instruction \p I to the worklist.
+  void pushToWorkList(Instruction *I);
+
+  /// Push users of value \p V to the worklist.
+  void pushUsersToWorkList(Value *V);
 
-  // Helper to push \p V to the worklist, after updating it to \p IV. Also
-  // prints a debug message with the updated value.
-  void pushToWorkListMsg(ValueLatticeElement &IV, Value *V);
+  /// Like pushUsersToWorkList(), but also prints a debug message with the
+  /// updated value.
+  void pushUsersToWorkListMsg(ValueLatticeElement &IV, Value *V);
 
   // markConstant - Make a value be marked as "constant".  If the value
   // is not already a constant, add it to the instruction work list so that
@@ -661,46 +666,9 @@ class SCCPInstVisitor : public InstVisitor<SCCPInstVisitor> {
   // successors are reachable from a given terminator instruction.
   void getFeasibleSuccessors(Instruction &TI, SmallVectorImpl<bool> &Succs);
 
-  // OperandChangedState - This method is invoked on all of the users of an
-  // instruction that was just changed state somehow.  Based on this
-  // information, we need to update the specified user of this instruction.
-  void operandChangedState(Instruction *I) {
-    if (BBExecutable.count(I->getParent())) // Inst is executable?
-      visit(*I);
-  }
-
   // Add U as additional user of V.
   void addAdditionalUser(Value *V, User *U) { AdditionalUsers[V].insert(U); }
 
-  // Mark I's users as changed, including AdditionalUsers.
-  void markUsersAsChanged(Value *I) {
-    // Functions include their arguments in the use-list. Changed function
-    // values mean that the result of the function changed. We only need to
-    // update the call sites with the new function result and do not have to
-    // propagate the call arguments.
-    if (isa<Function>(I)) {
-      for (User *U : I->users()) {
-        if (auto *CB = dyn_cast<CallBase>(U))
-          handleCallResult(*CB);
-      }
-    } else {
-      for (User *U : I->users())
-        if (auto *UI = dyn_cast<Instruction>(U))
-          operandChangedState(UI);
-    }
-
-    auto Iter = AdditionalUsers.find(I);
-    if (Iter != AdditionalUsers.end()) {
-      // Copy additional users before notifying them of changes, because new
-      // users may be added, potentially invalidating the iterator.
-      SmallVector<Instruction *, 2> ToNotify;
-      for (User *U : Iter->second)
-        if (auto *UI = dyn_cast<Instruction>(U))
-          ToNotify.push_back(UI);
-      for (Instruction *UI : ToNotify)
-        operandChangedState(UI);
-    }
-  }
   void handleCallOverdefined(CallBase &CB);
   void handleCallResult(CallBase &CB);
   void handleCallArguments(CallBase &CB);
@@ -985,19 +953,41 @@ bool SCCPInstVisitor::markBlockExecutable(BasicBlock *BB) {
   return true;
 }
 
-void SCCPInstVisitor::pushToWorkList(ValueLatticeElement &IV, Value *V) {
-  if (IV.isOverdefined()) {
-    if (OverdefinedInstWorkList.empty() || OverdefinedInstWorkList.back() != V)
-      OverdefinedInstWorkList.push_back(V);
+void SCCPInstVisitor::pushToWorkList(Instruction *I) {
+  // If we're currently visiting a block, do not push any instructions in the
+  // same blocks that are after the current one, as they will be visited
+  // anyway. We do have to push updates to earlier instructions (e.g. phi
+  // nodes or loads of tracked globals).
+  if (CurI && I->getParent() == CurI->getParent() && !I->comesBefore(CurI))
     return;
+  // Only push instructions in already visited blocks. Otherwise we'll handle
+  // it when we visit the block for the first time.
+  if (BBVisited.count(I->getParent()))
+    InstWorkList.insert(I);
+}
+
+void SCCPInstVisitor::pushUsersToWorkList(Value *V) {
+  for (User *U : V->users())
+    if (auto *UI = dyn_cast<Instruction>(U))
+      pushToWorkList(UI);
+
+  auto Iter = AdditionalUsers.find(V);
+  if (Iter != AdditionalUsers.end()) {
+    // Copy additional users before notifying them of changes, because new
+    // users may be added, potentially invalidating the iterator.
+    SmallVector<Instruction *, 2> ToNotify;
+    for (User *U : Iter->second)
+      if (auto *UI = dyn_cast<Instruction>(U))
+        ToNotify.push_back(UI);
+    for (Instruction *UI : ToNotify)
+      pushToWorkList(UI);
   }
-  if (InstWorkList.empty() || InstWorkList.back() != V)
-    InstWorkList.push_back(V);
 }
 
-void SCCPInstVisitor::pushToWorkListMsg(ValueLatticeElement &IV, Value *V) {
+void SCCPInstVisitor::pushUsersToWorkListMsg(ValueLatticeElement &IV,
+                                             Value *V) {
   LLVM_DEBUG(dbgs() << "updated " << IV << ": " << *V << '\n');
-  pushToWorkList(IV, V);
+  pushUsersToWorkList(V);
 }
 
 bool SCCPInstVisitor::markConstant(ValueLatticeElement &IV, Value *V,
@@ -1005,7 +995,7 @@ bool SCCPInstVisitor::markConstant(ValueLatticeElement &IV, Value *V,
   if (!IV.markConstant(C, MayIncludeUndef))
     return false;
   LLVM_DEBUG(dbgs() << "markConstant: " << *C << ": " << *V << '\n');
-  pushToWorkList(IV, V);
+  pushUsersToWorkList(V);
   return true;
 }
 
@@ -1014,7 +1004,7 @@ bool SCCPInstVisitor::markNotConstant(ValueLatticeElement &IV, Value *V,
   if (!IV.markNotConstant(C))
     return false;
   LLVM_DEBUG(dbgs() << "markNotConstant: " << *C << ": " << *V << '\n');
-  pushToWorkList(IV, V);
+  pushUsersToWorkList(V);
   return true;
 }
 
@@ -1023,7 +1013,7 @@ bool SCCPInstVisitor::markConstantRange(ValueLatticeElement &IV, Value *V,
   if (!IV.markConstantRange(CR))
     return false;
   LLVM_DEBUG(dbgs() << "markConstantRange: " << CR << ": " << *V << '\n');
-  pushToWorkList(IV, V);
+  pushUsersToWorkList(V);
   return true;
 }
 
@@ -1036,7 +1026,7 @@ bool SCCPInstVisitor::markOverdefined(ValueLatticeElement &IV, Value *V) {
              << "Function '" << F->getName() << "'\n";
              else dbgs() << *V << '\n');
   // Only instructions go on the work list
-  pushToWorkList(IV, V);
+  pushUsersToWorkList(V);
   return true;
 }
 
@@ -1144,7 +1134,7 @@ bool SCCPInstVisitor::mergeInValue(ValueLatticeElement &IV, Value *V,
                                    ValueLatticeElement MergeWithV,
                                    ValueLatticeElement::MergeOptions Opts) {
   if (IV.mergeIn(MergeWithV, Opts)) {
-    pushToWorkList(IV, V);
+    pushUsersToWorkList(V);
     LLVM_DEBUG(dbgs() << "Merged " << MergeWithV << " into " << *V << " : "
                       << IV << "\n");
     return true;
@@ -1164,7 +1154,7 @@ bool SCCPInstVisitor::markEdgeExecutable(BasicBlock *Source, BasicBlock *Dest) {
                       << " -> " << Dest->getName() << '\n');
 
     for (PHINode &PN : Dest->phis())
-      visitPHINode(PN);
+      pushToWorkList(&PN);
   }
   return true;
 }
@@ -1540,7 +1530,7 @@ void SCCPInstVisitor::visitSelectInst(SelectInst &I) {
   bool Changed = State.mergeIn(TVal);
   Changed |= State.mergeIn(FVal);
   if (Changed)
-    pushToWorkListMsg(State, &I);
+    pushUsersToWorkListMsg(State, &I);
 }
 
 // Handle Unary Operators.
@@ -2039,53 +2029,28 @@ void SCCPInstVisitor::handleCallResult(CallBase &CB) {
 
 void SCCPInstVisitor::solve() {
   // Process the work lists until they are empty!
-  while (!BBWorkList.empty() || !InstWorkList.empty() ||
-         !OverdefinedInstWorkList.empty()) {
-    // Process the overdefined instruction's work list first, which drives other
-    // things to overdefined more quickly.
-    while (!OverdefinedInstWorkList.empty()) {
-      Value *I = OverdefinedInstWorkList.pop_back_val();
-      Invalidated.erase(I);
-
-      LLVM_DEBUG(dbgs() << "\nPopped off OI-WL: " << *I << '\n');
-
-      // "I" got into the work list because it either made the transition from
-      // bottom to constant, or to overdefined.
-      //
-      // Anything on this worklist that is overdefined need not be visited
-      // since all of its users will have already been marked as overdefined
-      // Update all of the users of this instruction's value.
-      //
-      markUsersAsChanged(I);
-    }
-
+  while (!BBWorkList.empty() || !InstWorkList.empty()) {
     // Process the instruction work list.
     while (!InstWorkList.empty()) {
-      Value *I = InstWorkList.pop_back_val();
+      Instruction *I = InstWorkList.pop_back_val();
       Invalidated.erase(I);
 
       LLVM_DEBUG(dbgs() << "\nPopped off I-WL: " << *I << '\n');
 
-      // "I" got into the work list because it made the transition from undef to
-      // constant.
-      //
-      // Anything on this worklist that is overdefined need not be visited
-      // since all of its users will have already been marked as overdefined.
-      // Update all of the users of this instruction's value.
-      //
-      if (I->getType()->isStructTy() || !getValueState(I).isOverdefined())
-        markUsersAsChanged(I);
+      visit(I);
     }
 
     // Process the basic block work list.
     while (!BBWorkList.empty()) {
       BasicBlock *BB = BBWorkList.pop_back_val();
+      BBVisited.insert(BB);
 
       LLVM_DEBUG(dbgs() << "\nPopped off BBWL: " << *BB << '\n');
-
-      // Notify all instructions in this basic block that they are newly
-      // executable.
-      visit(BB);
+      for (Instruction &I : *BB) {
+        CurI = &I;
+        visit(I);
+      }
+      CurI = nullptr;
     }
   }
 }

void SCCPInstVisitor::pushUsersToWorkList(Value *V) {
for (User *U : V->users())
if (auto *UI = dyn_cast<Instruction>(U))
pushToWorkList(UI);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still valuable to have a separate worklist for insts that contain an operand just marked as Overdefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikic
Copy link
Contributor Author

nikic commented Jun 30, 2025

Clang test failure is unrelated. llvm-opt-benchmark looks better on average at a glance.

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@nikic nikic merged commit 545cdca into llvm:main Jun 30, 2025
6 of 7 checks passed
@nikic nikic deleted the sccp-order branch June 30, 2025 15:17
Copy link
Contributor

@fhahn fhahn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice, thanks!

rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
SCCP currently stores instructions whose lattice value has changed in a
worklist, and then updates their users in the main loop. This may result
in instructions unnecessarily being visited multiple times (as an
instruction will often use multiple other instructions). Additionally,
we'd often redundantly visit instructions that were already visited when
the containing block first became executable.

Instead, change the worklist to directly store the instructions that
need to be revisited. Additionally, do not add instructions to the
worklist that will already be covered by the main basic block walk.

This change is conceptually NFC, but is expected to produce minor
differences in practice, because the visitation order interacts with the
range widening limit.
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
SCCP currently stores instructions whose lattice value has changed in a
worklist, and then updates their users in the main loop. This may result
in instructions unnecessarily being visited multiple times (as an
instruction will often use multiple other instructions). Additionally,
we'd often redundantly visit instructions that were already visited when
the containing block first became executable.

Instead, change the worklist to directly store the instructions that
need to be revisited. Additionally, do not add instructions to the
worklist that will already be covered by the main basic block walk.

This change is conceptually NFC, but is expected to produce minor
differences in practice, because the visitation order interacts with the
range widening limit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants