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
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 66 additions & 101 deletions llvm/lib/Transforms/Utils/SCCPSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -985,27 +953,49 @@ 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.contains(I->getParent()))
InstWorkList.insert(I);
}

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.


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,
Constant *C, bool MayIncludeUndef) {
if (!IV.markConstant(C, MayIncludeUndef))
return false;
LLVM_DEBUG(dbgs() << "markConstant: " << *C << ": " << *V << '\n');
pushToWorkList(IV, V);
pushUsersToWorkList(V);
return true;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
}
}
Expand Down
Loading