[llvm] [SLP] Fix extract-cost scale using NCD of all external-user sites (PR #199962)
Ryan Buchner via llvm-commits
llvm-commits at lists.llvm.org
Fri May 29 09:48:33 PDT 2026
================
@@ -19551,6 +19551,55 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
}
}
AllUsersGEPSWithStoresLoads &= UsedLanes.all();
+
+ // Pre-pass: for each externally-used scalar, find the basic block at which
+ // the extractelement will be placed by codegen. This mirrors what
+ // vectorizeTree does: the extract is placed at the nearest common dominator
+ // of all effective use sites. For a non-PHI user the effective site is the
+ // user's own block; for a PHI user it is the incoming block for the scalar
+ // operand (the predecessor of the PHI on the edge that carries the scalar).
+ // Using the NCD of all effective sites rather than the first-encountered
+ // user's block makes the extract-cost scale order-independent and correct
+ // even when users live in different loop nests.
+ SmallDenseMap<Value *, BasicBlock *> ScalarToExtractBlock;
+ for (const ExternalUser &EU : ExternalUses) {
+ if (!EU.User || isa<InsertElementInst>(EU.User))
+ continue;
+ if (EphValues.count(EU.User))
+ continue;
+ BasicBlock *UserParent = cast<Instruction>(EU.User)->getParent();
+ if (!DT->isReachableFromEntry(UserParent) || UserParent->isEHPad() ||
+ isa_and_present<UnreachableInst>(UserParent->getTerminator()))
+ continue;
+ BasicBlock *UseBlock = nullptr;
+ if (auto *PHI = dyn_cast<PHINode>(EU.User)) {
+ // When the PHI itself is inside a loop, the extractelement is placed
+ // in the incoming block for the scalar operand (the predecessor edge),
+ // not in the PHI's own block. This applies to LCSSA phis at an inner-
+ // loop exit that are still inside an outer loop: the incoming block is
+ // in the inner loop while the PHI block is in the outer loop.
+ // When the PHI is outside all loops (a true loop-exit phi), codegen
+ // uses a vector phi at the exit block and the extract stays there
+ // (scale = 1), so we keep the PHI's own block as the effective site.
+ if (LI->getLoopFor(PHI->getParent())) {
+ for (unsigned Idx : seq<unsigned>(PHI->getNumIncomingValues())) {
+ if (PHI->getIncomingValue(Idx) != EU.Scalar)
+ continue;
+ BasicBlock *InBB = PHI->getIncomingBlock(Idx);
+ UseBlock =
+ UseBlock ? DT->findNearestCommonDominator(UseBlock, InBB) : InBB;
+ }
+ }
+ if (!UseBlock)
+ UseBlock = cast<Instruction>(EU.User)->getParent();
+ } else {
+ UseBlock = cast<Instruction>(EU.User)->getParent();
+ }
+ auto [It, Inserted] = ScalarToExtractBlock.try_emplace(EU.Scalar, UseBlock);
+ if (!Inserted && It->second && UseBlock)
+ It->second = DT->findNearestCommonDominator(It->second, UseBlock);
----------------
bababuck wrote:
I'm guessing it would be a separate optimization, but I imagine there are cases where it would be more profitable to emit multiple extract elements rather than a single when the NCD is much hotter than either individual basic block.
https://github.com/llvm/llvm-project/pull/199962
More information about the llvm-commits
mailing list