[llvm] [SLP] Split blocking build-vector stores in scalar chains (PR #194970)
Yaxun Liu via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 6 21:54:07 PDT 2026
================
@@ -27346,6 +27473,137 @@ bool SLPVectorizerPass::vectorizeStores(
return A && (!B || A->getStride() < B->getStride());
});
+ // Mixed scalar/build-vector slice handling.
+ //
+ // Conceptually a "store reduction": SLP builds a tree on the per-lane
+ // values, treating the original IE chain and StoreInsts together as
+ // the reduction-operation chain via UserIgnoreList. After the tree's
+ // standard cost analysis we add the store-sink-specific cost (one vector
+ // store, minus the replaced scalar store costs); if profitable we let
+ // the standard tree emitter materialize the lane-value vector, then
+ // emit the new <VF x T> store and erase the originals here. The IE
+ // chains feeding any erased build-vector store become trivially dead
+ // and are cleaned up by the standard pass at the end.
+ auto VectorizeBuildVectorStoreSinkSlice =
+ [&](ArrayRef<StoreLane> Chain) -> std::optional<bool> {
+ SmallVector<Value *> LaneValues;
+ SmallVector<std::pair<StoreInst *, unsigned>> Owners;
+ StoreInst *LastOwner = nullptr;
+ for (const StoreLane &Lane : Chain) {
+ LaneValues.push_back(Lane.ScalarValue);
+ if (Lane.Store == LastOwner) {
+ Owners.back().second++;
+ } else {
+ Owners.emplace_back(Lane.Store, 1);
+ LastOwner = Lane.Store;
+ }
+ }
+ // Each build-vector owner must be fully covered by this slice;
+ // otherwise erasing it would lose data outside the slice.
+ for (auto &[SI, Count] : Owners) {
+ SmallVector<Value *, 16> Elts;
+ SmallVector<Instruction *, 16> Insts;
+ if (collectBuildVector(SI->getValueOperand(), Elts, Insts) &&
+ Count != Elts.size())
+ return false;
+ }
+ // UserIgnoreList: original stores plus any IE chain feeding a build-
+ // vector store. SLP treats this exactly like a reduction tree: the
+ // lane values' uses inside listed instructions are not counted as
+ // external uses, since those instructions will be erased after the
+ // new vector store is emitted.
+ SmallDenseSet<Value *> Ignored;
+ for (auto &[SI, _] : Owners) {
+ Ignored.insert(SI);
+ SmallVector<Value *, 16> Elts;
+ SmallVector<Instruction *, 16> Insts;
+ if (collectBuildVector(SI->getValueOperand(), Elts, Insts))
+ for (Instruction *I : Insts)
+ Ignored.insert(I);
+ }
+ // Conservatively require owner stores to appear in the same order in the
+ // block as they appear in the lane range, and reject any intervening
+ // instruction that may read or write memory. The standard scalar store
+ // path gets memory-dependence checks through store-root scheduling; this
+ // manual store sink must not move a wider store across unknown memory.
+ StoreInst *PrevStore = nullptr;
+ for (auto &[SI, _] : Owners) {
+ if (PrevStore && !PrevStore->comesBefore(SI))
+ return false;
+ PrevStore = SI;
+ }
+ StoreInst *FirstStore = Owners.front().first;
+ StoreInst *LastStore = Owners.back().first;
+ for (Instruction &I : make_range(std::next(FirstStore->getIterator()),
+ LastStore->getIterator())) {
+ if (Ignored.contains(&I))
+ continue;
+ if (I.mayReadOrWriteMemory())
+ return false;
+ }
+ R.buildTree(LaneValues, Ignored);
----------------
yxsamliu wrote:
Fixed, thanks for the report.
The issue was that this path uses `UserIgnoreList` for ignored stores/build-vector instructions, but the min-bitwidth code treated it like a reduction list and tried to query the size of a void-typed store. I added the test case and now skip that reduction-only analysis unless the ignored values are sized integers.
https://github.com/llvm/llvm-project/pull/194970
More information about the llvm-commits
mailing list