[llvm] Reland "[LICM] Sink unused l-invariant loads in preheader #157559" (PR #170204)
Nikita Popov via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 16 01:37:57 PDT 2026
================
@@ -1560,6 +1572,181 @@ static void moveInstructionBefore(Instruction &I, BasicBlock::iterator Dest,
SE->forgetBlockAndLoopDispositions(&I);
}
+// If there's a single exit block, sink any loop-invariant values that were
+// defined in the preheader but not used inside the loop into the exit block
+// to reduce register pressure in the loop.
+static bool sinkUnusedInvariantsFromPreheaderToExit(
+ Loop *L, AAResults *AA, ICFLoopSafetyInfo *SafetyInfo,
+ MemorySSAUpdater &MSSAU, ScalarEvolution *SE, DominatorTree *DT,
+ SinkAndHoistLICMFlags &SinkFlags) {
+ BasicBlock *ExitBlock = L->getExitBlock();
+ if (!ExitBlock)
+ return false;
+
+ BasicBlock *Preheader = L->getLoopPreheader();
+ if (!Preheader)
+ return false;
+
+ // Collect instructions that are safe to sink and have no in-loop users.
+ SmallPtrSet<Instruction *, 16> LegalToSink;
+ for (Instruction &I : llvm::reverse(*Preheader)) {
+ if (I.isTerminator())
+ continue;
+
+ // New instructions were inserted at the end of the preheader.
+ if (isa<PHINode>(I))
+ break;
+
+ // Don't move instructions which might have side effects, since the side
+ // effects need to complete before instructions inside the loop. Note that
+ // it's okay if the instruction might have undefined behavior: LoopSimplify
+ // guarantees that the preheader dominates the exit block.
+ if (I.mayHaveSideEffects())
+ continue;
+
+ if (!canSinkOrHoistInst(I, AA, DT, L, MSSAU, true, SinkFlags, nullptr))
+ continue;
+
+ bool UsedInLoop = llvm::any_of(I.uses(), [L](Use &U) {
+ auto *UserI = cast<Instruction>(U.getUser());
+ BasicBlock *UseBB = UserI->getParent();
+ if (auto *PN = dyn_cast<PHINode>(UserI))
+ UseBB = PN->getIncomingBlock(U);
+ return L->contains(UseBB);
+ });
+ if (UsedInLoop)
+ continue;
+
+ LegalToSink.insert(&I);
+ }
+
+ if (LegalToSink.empty())
+ return false;
+
+ // Remove candidates that have a preheader user which is not itself a
+ // candidate, since that user keeps the definition live in the preheader.
+ // Iterate until stable to handle chains.
+ SmallPtrSet<Instruction *, 16> SinkCandidates(LegalToSink);
+ bool Shrunk = true;
+ while (Shrunk) {
+ Shrunk = false;
+ SmallVector<Instruction *, 8> ToRemove;
+ for (Instruction *I : SinkCandidates) {
+ bool HasUnsinkablePreheaderUser =
+ llvm::any_of(I->uses(), [Preheader, &SinkCandidates](Use &U) {
+ auto *UserI = cast<Instruction>(U.getUser());
+ BasicBlock *UseBB = UserI->getParent();
+ if (auto *PN = dyn_cast<PHINode>(UserI))
+ UseBB = PN->getIncomingBlock(U);
+ return UseBB == Preheader && !SinkCandidates.contains(UserI);
+ });
+ if (HasUnsinkablePreheaderUser)
+ ToRemove.push_back(I);
+ }
+ for (Instruction *I : ToRemove) {
+ SinkCandidates.erase(I);
+ Shrunk = true;
+ }
+ }
+
+ if (SinkCandidates.empty())
+ return false;
+
+ // Prune candidates that would increase register pressure. Sinking removes
+ // one live-across value but may make unsinkable preheader-defined operands
+ // newly live across the loop. Allow at most one such new live-across operand,
+ // since it may still enable sinking dependents for a net win.
+ SmallPtrSet<Instruction *, 4> Seen;
+ SmallDenseMap<Instruction *, bool, 16> AlreadyLiveAcross;
+ auto CountNewLiveAcrossOps =
+ [Preheader, &SinkCandidates, &Seen,
+ &AlreadyLiveAcross](Instruction *I) -> unsigned {
+ Seen.clear();
+ unsigned Count = 0;
+ for (Value *Op : I->operands()) {
+ auto *OpI = dyn_cast<Instruction>(Op);
+ if (!OpI || OpI->getParent() != Preheader)
+ continue;
+ if (!Seen.insert(OpI).second)
+ continue;
+ if (SinkCandidates.contains(OpI))
+ continue;
+ auto [It, Inserted] = AlreadyLiveAcross.try_emplace(OpI);
+ if (Inserted)
+ It->second = llvm::any_of(OpI->users(), [Preheader](User *U) {
+ auto *UI = dyn_cast<Instruction>(U);
+ // Check if the user is in a different block than the preheader.
+ // This is a heuristic to check if the user is live across the loop.
+ return UI && UI->getParent() != Preheader;
+ });
+ if (!It->second)
+ ++Count;
+ }
+ return Count;
+ };
+
+ bool Changed = true;
+ while (Changed) {
+ Changed = false;
+ SmallVector<Instruction *, 8> ToRemove;
+ for (Instruction *I : SinkCandidates)
+ if (CountNewLiveAcrossOps(I) > 1)
+ ToRemove.push_back(I);
+ for (Instruction *I : ToRemove) {
+ SinkCandidates.erase(I);
+ Changed = true;
+ }
+ }
+
+ if (SinkCandidates.empty())
+ return false;
+
+ // Sink surviving candidates in reverse program order so that defs in the
+ // exit block end up in their original relative order.
+ bool MadeAnyChanges = false;
+ SmallVector<Value *, 16> SunkInsts;
+ MemoryAccess *ExitDef = nullptr;
+ MemorySSA *MSSA = MSSAU.getMemorySSA();
+
+ for (Instruction &I : llvm::make_early_inc_range(llvm::reverse(*Preheader))) {
+ if (!SinkCandidates.contains(&I))
+ continue;
+
+ SafetyInfo->removeInstruction(&I);
+ SafetyInfo->insertInstructionTo(&I, ExitBlock);
+ I.moveBefore(*ExitBlock, ExitBlock->getFirstInsertionPt());
+ SunkInsts.push_back(&I);
+
+ // Update MemorySSA. Avoid the expensive getPreviousDefRecursive call by
+ // caching a defining access from the preheader on the first sunk MemoryUse.
+ if (auto *OldMA = MSSA->getMemoryAccess(&I)) {
----------------
nikic wrote:
I think this is assuming that the memory access must be a MemoryUse, otherwise this code would be entirely incorrect, right? Would be good to assert that.
https://github.com/llvm/llvm-project/pull/170204
More information about the llvm-commits
mailing list