[llvm] [VPlan] Implement VPlan-based unit-strideness speculation (PR #182595)

Luke Lau via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 20 19:34:23 PDT 2026


================
@@ -5520,6 +5529,183 @@ void VPlanTransforms::makeMemOpWideningDecisions(VPlan &Plan, VFRange &Range,
                            });
 }
 
+void VPlanTransforms::multiversionForUnitStridedMemOps(
+    VPlan &Plan, VPCostContext &CostCtx, VFRange &Range,
+    ArrayRef<VPInstruction *> MemOps) {
+  ScalarEvolution *SE = CostCtx.PSE.getSE();
+  SCEVUnionPredicate StridePredicates({}, *SE);
+
+  for (VPInstruction *VPI : MemOps) {
+    VPValue *PtrOp = VPI->getOpcode() == Instruction::Load ? VPI->getOperand(0)
+                                                           : VPI->getOperand(1);
+
+    const SCEV *PtrSCEV =
+        vputils::getSCEVExprForVPValue(PtrOp, CostCtx.PSE, CostCtx.L);
+    const SCEV *Start, *Stride;
+
+    if (!match(PtrSCEV, m_scev_AffineAddRec(m_SCEV(Start), m_SCEV(Stride),
+                                            m_SpecificLoop(CostCtx.L))))
+      continue;
+
+    Type *ScalarTy = VPI->getOpcode() == Instruction::Load
+                         ? VPI->getScalarType()
+                         : VPI->getOperand(0)->getScalarType();
+
+    if (VPI->getMask()) {
+      Instruction *I = VPI->getUnderlyingInstr();
+      bool IsLoad = VPI->getOpcode() == Instruction::Load;
+      // Don't speculate unit-strideness if it won't result in any unit-strided
+      // loads, as we'd pay the price of not taking vector loop if the runtime
+      // condition is false for no benefits.
+      if (!CostCtx.Config.isLegalMaskedLoadOrStore(IsLoad, ScalarTy,
+                                                   getLoadStoreAlignment(I),
+                                                   getLoadStoreAddressSpace(I)))
+        continue;
+    }
+
+    if (isa<SCEVConstant>(Stride))
+      continue;
+
+    const auto *TypeSize = cast<SCEVConstant>(SE->getSizeOfExpr(
+        Stride->getType(), SE->getDataLayout().getTypeAllocSize(ScalarTy)));
+
+    const SCEVConstant *StrideConstantMultiplier;
+    const SCEV *StrideNonConstantMultiplier;
+
+    const SCEV *ToMultiVersion = Stride;
+    const SCEV *MVConst = TypeSize;
+    if (match(Stride, m_scev_c_Mul(m_SCEVConstant(StrideConstantMultiplier),
+                                   m_SCEV(StrideNonConstantMultiplier)))) {
+      if (TypeSize != StrideConstantMultiplier) {
+        // TODO: Support `TypeSize = N * StrideConstantMultiplier`,
+        // including negative `N`. For now, only process when they're equal,
+        // which matches the useful part of the legacy behavior that
+        // multiversiones GEP index for stride one.
+        continue;
+      }
+      ToMultiVersion = StrideNonConstantMultiplier;
+      MVConst = SE->getOne(ToMultiVersion->getType());
+    } else if (!TypeSize->isOne()) {
+      // Likewise - try to match legacy behavior.
+      continue;
+    }
+
+    while (auto *C = dyn_cast<SCEVIntegralCastExpr>(ToMultiVersion)) {
+      ToMultiVersion = C->getOperand();
+      MVConst = SE->getTruncateOrSignExtend(MVConst, ToMultiVersion->getType());
+    }
+
+    if (match(ToMultiVersion, m_scev_UndefOrPoison()))
+      continue;
+
+    if (!isa<SCEVUnknown>(ToMultiVersion)) {
+      // Match legacy behavior.
+      // If/when changed, make sure that explicit poison/undef in the defining
+      // expression doesn't cause any issues.
+      continue;
+    }
+
+    Value *StrideVal = cast<SCEVUnknown>(ToMultiVersion)->getValue();
+
+    const SCEVPredicate *NewPred =
+        SE->getComparePredicate(CmpInst::ICMP_EQ, ToMultiVersion, MVConst);
+
+    auto *PredicatedMaxBTC = SE->rewriteUsingPredicate(
+        SE->getSymbolicMaxBackedgeTakenCount(CostCtx.L), CostCtx.L,
+        StridePredicates.getUnionWith(NewPred, *SE)
+            .getUnionWith(&CostCtx.PSE.getPredicate(), *SE));
+    Type *BTCTy = PredicatedMaxBTC->getType();
+
+    // If predicate implies scalar loop never takes the backedge, don't perform
+    // multiversioning.
+    if (SE->isKnownPredicate(ICmpInst::ICMP_ULT, PredicatedMaxBTC,
+                             SE->getOne(BTCTy)))
+      continue;
+
+    // If we don't fold the tail, we need enough scalar iterations to fill the
+    // full vector.
+    if (!Plan.hasTailFolded() &&
+        LoopVectorizationPlanner::getDecisionAndClampRange(
+            [&](ElementCount VF) {
+              return SE->isKnownPredicate(
+                  ICmpInst::ICMP_ULT, PredicatedMaxBTC,
+                  SE->getAddExpr(SE->getElementCount(BTCTy, VF),
+                                 SE->getMinusOne(BTCTy)));
+            },
+            Range))
+      continue;
+
+    StridePredicates = StridePredicates.getUnionWith(NewPred, *SE);
+
+    auto ReplaceUsesInVectorLoop = [&](Value *V, const SCEV *ToSCEV) {
+      VPValue *From = Plan.getLiveIn(V);
+      if (!From)
+        return;
+
+      assert(From->getScalarType() == ToSCEV->getType() &&
+             "Wrong type for ToSCEV!");
+      VPValue *To = Plan.getConstantInt(cast<SCEVConstant>(ToSCEV)->getAPInt());
+
+      // Original scalar loop can still use `From`, make sure to only rewrite
+      // uses inside the vector loop that we guard with the checks.
+      From->replaceUsesWithIf(To, [&](VPUser &U, unsigned) {
+        auto *R = cast<VPRecipeBase>(&U);
+        return R->getRegion() || R->getParent() == Plan.getVectorPreheader();
+      });
+    };
+
+    ReplaceUsesInVectorLoop(StrideVal, MVConst);
+    // If `StrideVal` has casts defined outside VPlan that are live-ins, replace
+    // them too.
+    for (auto *U : StrideVal->users())
+      if (isa<SExtInst>(U))
+        ReplaceUsesInVectorLoop(U,
+                                SE->getSignExtendExpr(MVConst, U->getType()));
+      else if (isa<ZExtInst, TruncInst>(U))
+        ReplaceUsesInVectorLoop(
+            U, SE->getTruncateOrZeroExtend(MVConst, U->getType()));
+  }
+
+  if (StridePredicates.isAlwaysTrue())
+    return;
+
+  VPBasicBlock *StridesCheckVPBB = Plan.createVPBasicBlock("strides.check");
+  // We will replace the condition once we expand the predicate.
+  attachVPCheckBlock(Plan, Plan.getTrue(), StridesCheckVPBB,
+                     /*AddBranchWeights=*/false);
+  VPBasicBlock *Entry = Plan.getEntry();
+  VPBuilder Builder(&StridesCheckVPBB->back());
+  DebugLoc DL = cast<VPIRBasicBlock>(Entry)
+                    ->getIRBasicBlock()
+                    ->getTerminator()
+                    ->getDebugLoc();
+  VPSCEVExpander Expander(Builder, *SE, DL);
+  VPValue *Pred = Expander.tryToExpandPredicate(&StridePredicates);
+  assert(Pred && "Must be expandable!");
+  StridesCheckVPBB->getTerminator()->setOperand(0, Pred);
+
+  for (auto &R : make_early_inc_range(*Entry)) {
+    auto *ExpandSCEV = dyn_cast<VPExpandSCEVRecipe>(&R);
+    if (!ExpandSCEV)
+      continue;
+
+    const SCEV *S = ExpandSCEV->getSCEV();
+    Builder.setInsertPoint(ExpandSCEV);
+    const SCEV *NewS =
+        SE->rewriteUsingPredicate(S, CostCtx.L, StridePredicates);
+    if (NewS == S)
+      continue;
+    auto *NewR = Builder.createExpandSCEV(NewS);
+    ExpandSCEV->replaceAllUsesWith(NewR);
+
+    // If this recipe is a trip count then we need to reset it explicitly.
+    if (ExpandSCEV == Plan.getTripCount())
+      Plan.resetTripCount(NewR);
+
+    ExpandSCEV->eraseFromParent();
+  }
+}
----------------
lukel97 wrote:

I believe this is correct to rewrite the SCEVs before the stride.checks block, because:

- if the stride is 1 at runtime, we the min.iters check will be accurate
- if the stride is not 1 at runtime, the min.iters check will be inaccurate
  - if the min.iters check fails -> scalar loop
  - if the min.iters check passes, we go to the stride.check block but that will fail -> scalar loop

And the VPExpandSCEVRecipes won't be used in the scalar loop. But worth a comment explaining this

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


More information about the llvm-commits mailing list