[llvm] [SLP] Add store-to-load forwarding conflict cost for widened store chains (PR #199606)

Alexey Bataev via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 05:49:55 PDT 2026


================
@@ -28885,6 +28930,210 @@ bool SLPVectorizerPass::runImpl(Function &F, ScalarEvolution *SE_,
   return Changed;
 }
 
+/// Returns the constant loop-carried byte stride of \p Ptr in \p L, i.e. the
+/// step of its affine SCEV recurrence, or std::nullopt when \p Ptr is not a
+/// simple affine recurrence in \p L with a constant step. This is the same
+/// notion as LoopAccessAnalysis's CommonStride; it is computed here directly
+/// from ScalarEvolution rather than via getPtrStride, whose no-wrap versioning
+/// and PredicatedScalarEvolution machinery is meant for legality, not costing.
+static std::optional<int64_t>
+getConstantLoopStrideInBytes(Value *Ptr, ScalarEvolution &SE, const Loop *L) {
+  const auto *AR = dyn_cast<SCEVAddRecExpr>(SE.getSCEV(Ptr));
+  if (!AR || AR->getLoop() != L)
+    return std::nullopt;
+  const auto *Step = dyn_cast<SCEVConstant>(AR->getStepRecurrence(SE));
+  if (!Step)
+    return std::nullopt;
+  const APInt &StepVal = Step->getAPInt();
+  if (StepVal.getSignificantBits() > 64)
+    return std::nullopt;
+  return StepVal.getSExtValue();
+}
+
+bool BoUpSLP::findStoreLoadForwardingConflict(
+    StoreInst *BaseStore, unsigned VF, LoadInst *OnlyLoad,
+    std::optional<uint64_t> LoadSizeOverride) {
+  assert(BaseStore && "Expected a valid base store");
+
+  Type *ValueTy = BaseStore->getValueOperand()->getType();
+  TypeSize StoreSize = DL->getTypeStoreSize(ValueTy);
+  if (StoreSize.isScalable())
+    return false;
+  uint64_t ElementSize = StoreSize.getFixedValue();
+  if (ElementSize == 0)
+    return false;
+
+  // Store-to-load forwarding hazards are a loop-carried concern.
+  const Loop *StoreL = LI->getLoopFor(BaseStore->getParent());
+  if (!StoreL)
+    return false;
+
+  uint64_t VectorStoreBytes = VF * ElementSize;
+  LLVM_DEBUG(dbgs() << "SLP: STLF check: VF=" << VF
+                    << " ElementSize=" << ElementSize
+                    << " VectorStoreBytes=" << VectorStoreBytes << "\n");
+
+  // Loop-carried byte stride of the store. A conflict is only a real hazard if
+  // a future iteration's load re-reads the bytes this store wrote, which is a
+  // property of the stride (see the per-load check below).
+  std::optional<int64_t> StoreStride =
+      getConstantLoopStrideInBytes(BaseStore->getPointerOperand(), *SE, StoreL);
+
+  // A store-to-load forwarding hazard can involve any load in the loop that
+  // reads the widened store's base, not only loads that became SLP tree nodes:
+  // a conflicting load may feed a scalar store, sit below a gather/splat leaf,
+  // or be vectorized in a different tree. Enumerate every simple load in the
+  // store's loop that shares the store base. The widened width below is only
+  // visible for loads in the current tree; loads vectorized by other trees are
+  // modeled at scalar width.
+  Value *StoreBase = getUnderlyingObject(BaseStore->getPointerOperand());
+  const auto CandidateLoads = [&] {
+    SmallPtrSet<LoadInst *, 8> Loads;
+    if (OnlyLoad) {
+      if (OnlyLoad->isSimple() &&
+          getUnderlyingObject(OnlyLoad->getPointerOperand()) == StoreBase)
+        Loads.insert(OnlyLoad);
+      return Loads;
+    }
+    for (BasicBlock *BB : StoreL->blocks())
+      for (Instruction &I : *BB)
+        if (auto *LoadI = dyn_cast<LoadInst>(&I))
+          if (LoadI->isSimple() &&
+              getUnderlyingObject(LoadI->getPointerOperand()) == StoreBase)
+            Loads.insert(LoadI);
+    return Loads;
+  }();
+
+  if (CandidateLoads.empty())
+    return false;
+
+  // For each candidate load, the widened chain becomes one wide store at the
+  // base; check whether the load straddles two such wide stores.
+  for (LoadInst *LoadI : CandidateLoads) {
+    // Only loads in the store's loop share its loop-carried dependence.
+    if (LI->getLoopFor(LoadI->getParent()) != StoreL)
+      continue;
+    const TreeEntry *WidenedLoadEntry = nullptr;
+    for (const TreeEntry *LTE : getTreeEntries(LoadI)) {
+      if (LTE->isGather() || DeletedNodes.contains(LTE) ||
+          TransformedToGatherNodes.contains(LTE))
+        continue;
+      if (LTE->hasState() && LTE->State == TreeEntry::Vectorize &&
+          LTE->getOpcode() == Instruction::Load) {
+        WidenedLoadEntry = LTE;
+        break;
+      }
+    }
+    std::optional<int64_t> Diff =
+        getPointersDiff(ValueTy, BaseStore->getPointerOperand(),
+                        LoadI->getType(), LoadI->getPointerOperand(), *DL, *SE,
+                        /*StrictCheck=*/true, /*CheckType=*/false);
+    if (!Diff || *Diff >= 0)
+      continue;
+    // Negating the minimum signed value is undefined.
+    if (*Diff == std::numeric_limits<int64_t>::min())
+      continue;
+
+    uint64_t Distance = static_cast<uint64_t>(-*Diff) * ElementSize;
+    LLVM_DEBUG(dbgs() << "SLP: STLF: load=" << *LoadI << " distance="
+                      << Distance << " bytes from chain base\n");
+
+    // A widened (regularly vectorized) load accesses the whole vector at once,
+    // so its effective width is the number of emitted lanes * element size, not
+    // one element. Such a wide load can straddle two wide stores even when
+    // perfectly aligned, which the misalignment-only test would miss. Use the
+    // count of distinct scalars actually loaded from memory (not the reuse-
+    // inflated vector factor) for the emitted load width.
+    TypeSize LoadTypeSize = DL->getTypeStoreSize(LoadI->getType());
+    uint64_t LoadElementSize =
+        LoadTypeSize.isScalable() ? 0 : LoadTypeSize.getFixedValue();
+    if (LoadSizeOverride)
+      LoadElementSize = *LoadSizeOverride;
+    else if (WidenedLoadEntry)
+      LoadElementSize *= WidenedLoadEntry->Scalars.size();
+    // A conflict is only a real hazard if a future iteration's load actually
+    // re-reads the bytes this store wrote. With a common positive loop-carried
+    // stride S, equal for the load and the store, the store's bytes are re-read
+    // iff there is an integer k >= 1 with
+    //   Distance - LoadElementSize < k * S < Distance + VectorStoreBytes.
+    // If no such k exists the accesses are strided-independent, so there is no
+    // forwarding hazard. When the stride is unknown, non-positive, or differs
+    // between load and store, fall back to the conservative check below.
+    std::optional<int64_t> LoadStride =
+        getConstantLoopStrideInBytes(LoadI->getPointerOperand(), *SE, StoreL);
+    if (StoreStride && LoadStride && *StoreStride == *LoadStride &&
----------------
alexey-bataev wrote:

```suggestion
    // The stride argument covers only re-reads of past iterations' stores;
    // a load wide enough to reach the current store must still go through
    // the conflict predicate.
    if (StoreStride && LoadStride && *StoreStride == *LoadStride &&
```

https://github.com/llvm/llvm-project/pull/199606


More information about the llvm-commits mailing list