Compare commits

...

22 commits

Author SHA1 Message Date
Andrei Elovikov
c260047698 Drop now unnecessary include 2026-07-31 13:59:05 -07:00
Andrei Elovikov
bdfc8ae0a3 Clamp later 2026-07-31 13:55:16 -07:00
Andrei Elovikov
7ee607f9e7 Add a comment per code review 2026-07-31 13:49:55 -07:00
Andrei Elovikov
412a61741f Revert adding widenUnitStridedLoadStore helper
We now rely on the actual consequtive memops widening in a single place (both
original and speculated/mv'ed).
2026-07-30 11:11:43 -07:00
Andrei Elovikov
f71ec68b9c Move to before consecutive memops widening, and only propagate MV'ed constant without widening 2026-07-30 11:06:10 -07:00
Andrei Elovikov
7e87100f10 Replace outside truncs same as had been already done for [sz]exts 2026-07-30 10:55:20 -07:00
Andrei Elovikov
337082065a Add test for narrow trunc modifying constant 2026-07-30 10:47:55 -07:00
Andrei Elovikov
9c8bba85fc Revert remaining change in VPRecipeBuilder.h 2026-07-29 16:48:21 -07:00
Andrei Elovikov
de737fcc50 Drop isConsecutiveWithoutVPlanBasedStrideSpeculation
Not necessary after all unit-strided memops are widened on VPlan prior
to this pass.
2026-07-28 15:25:32 -07:00
Andrei Elovikov
8042782f89 Merge remote-tracking branch 'origin/main' into vplan-based-stride-mv-rt-guard 2026-07-28 15:20:08 -07:00
Andrei Elovikov
894392dec2 Outline widenUnitStridedLoadStore helper
Reuses it for both normal and speculative unit-strided memaccesses widening.

Apparently, I misread `createVectorPointer` interface (or maybe it changed
throughout the life of this PR), so this also fixes a bug in the new code.
2026-07-27 11:57:49 -07:00
Andrei Elovikov
b2f91291a0 Merge remote-tracking branch 'origin/main' into vplan-based-stride-mv-rt-guard 2026-07-27 11:16:30 -07:00
Andrei Elovikov
f3424c771c Inline RewriteVPExpandSCEV lambda
Calling it on `Plan.getTripCount()` explicitly doesn't seem to be
necessary anymore, so can inline into the single remaining use.
2026-07-22 13:36:21 -07:00
Andrei Elovikov
f2a41d5c72 Address code review comments 2026-07-21 11:47:42 -07:00
Andrei Elovikov
4c8cde5fdd Use erase_if/drop Skip 2026-07-21 10:24:44 -07:00
Andrei Elovikov
770d4b7212 Expand directly to VPlan, drop VPExpandStridePredicatesRecipe 2026-07-20 15:28:29 -07:00
Andrei Elovikov
6e1e9a0794 Address code-review comments 2026-07-20 15:28:29 -07:00
Andrei Elovikov
8258e553e4 Expand to VPlan recipes instead of to LLVM IR 2026-07-17 09:42:56 -07:00
Andrei Elovikov
9e7830d8a5 Address review comments 2026-07-17 09:39:50 -07:00
Andrei Elovikov
193fc77621 [VPlan] Implement VPlan-based stride speculation 2026-07-17 09:39:48 -07:00
Andrei Elovikov
662b2c6a87 Add @independent_strides test 2026-07-17 09:36:54 -07:00
Andrei Elovikov
bb7eed2fe0 [NFC][VPlan] Add initial tests for future VPlan-based stride MV
I tried to include both the features that current
LoopAccessAnalysis-based transformation supports (e.g., trunc/sext of
stride) but also cases where the current implementation behaves poorly,
e.g., https://godbolt.org/z/h31c3zKxK; as well as some other potentially
interesting scenarios I could imagine.

The are two test files with the same content. One is for VPlan dump change of
the future transformation alone (I'll update `-vplan-print-after` in the next
PR), another is for the full vectorizer pipeline. The latter have two `RUN:`
lines:
 * No multiversioning, so the next PR diff can show the transformation itself
 * Stride multiversionin performed in LAA, so that we can compare future
   VPlan-based transformation vs old behavior.
2026-07-17 09:36:18 -07:00
6 changed files with 1690 additions and 1151 deletions

View file

@ -43,6 +43,11 @@ using namespace llvm;
using namespace VPlanPatternMatch;
using namespace SCEVPatternMatch;
static cl::opt<bool> EnableVPlanBasedStrideMV(
"enable-vplan-based-stride-mv", cl::init(false), cl::Hidden,
cl::desc("Perform stride multiversioning directly on VPlan instead of in "
"LoopAccessAnalysis."));
/// If the pointer operand \p Addr of a memory access is an affine AddRec
/// w.r.t. \p L with a constant stride, return the stride in units of
/// \p AccessTy. Otherwise return std::nullopt.
@ -5454,6 +5459,11 @@ void VPlanTransforms::makeMemOpWideningDecisions(VPlan &Plan, VFRange &Range,
});
}
if (EnableVPlanBasedStrideMV &&
!CostCtx.L->getHeader()->getParent()->hasOptSize())
RUN_VPLAN_PASS(VPlanTransforms::multiversionForUnitStridedMemOps, Plan,
CostCtx, RecipeBuilder, Range, MemOps);
// Widen unit-stride consecutive accesses, matching the legacy CM. Both
// forward (stride +1) and reverse (stride -1) accesses are handled.
VPlanTransforms::runPass(
@ -5521,6 +5531,191 @@ void VPlanTransforms::makeMemOpWideningDecisions(VPlan &Plan, VFRange &Range,
});
}
void VPlanTransforms::multiversionForUnitStridedMemOps(
VPlan &Plan, VPCostContext &CostCtx, VPRecipeBuilder &RecipeBuilder,
VFRange &Range, SmallVectorImpl<VPInstruction *> &MemOps) {
ScalarEvolution *SE = CostCtx.PSE.getSE();
PredicatedScalarEvolution StrideMVPSE(*SE, const_cast<Loop &>(*CostCtx.L));
SCEVUnionPredicate StridePredicates({}, *SE);
for (VPInstruction *VPI : MemOps) {
auto *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();
const auto *TypeSize = cast<SCEVConstant>(SE->getSizeOfExpr(
Stride->getType(), SE->getDataLayout().getTypeAllocSize(ScalarTy)));
if (isa<SCEVConstant>(Stride))
continue;
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);
// Check if new predicate implies that backedge is never taken. If so, there
// is no reason to multiversion for it.
auto *PredicatedMaxBTC = SE->rewriteUsingPredicate(
SE->getSymbolicMaxBackedgeTakenCount(CostCtx.L), CostCtx.L,
StridePredicates.getUnionWith(NewPred, *SE)
.getUnionWith(&CostCtx.PSE.getPredicate(), *SE));
if (LoopVectorizationPlanner::getDecisionAndClampRange(
[&](ElementCount VF) {
return SE->isKnownPredicate(
ICmpInst::ICMP_ULT, PredicatedMaxBTC,
SE->getConstant(PredicatedMaxBTC->getType(),
VF.isScalable() ? 1
: VF.getFixedValue() - 1));
},
Range))
continue;
if (VPI->getMask()) {
Instruction *I = VPI->getUnderlyingInstr();
bool IsLoad = VPI->getOpcode() == Instruction::Load;
// No reason to speculate unitstrideness if that won't improve vector code
// as we'd pay the price of not taking vector loop if the runtime
// condition is false for no benefits.
//
// We perform this clamping here because we don't want to
// `getDecisionAndClampRange` too early - here is better because we have
// another clamping right above.
if (!LoopVectorizationPlanner::getDecisionAndClampRange(
[&](ElementCount VF) -> bool {
return CostCtx.Config.isLegalMaskedLoadOrStore(
IsLoad, ScalarTy, getLoadStoreAlignment(I),
getLoadStoreAddressSpace(I));
},
Range))
continue;
}
StridePredicates = StridePredicates.getUnionWith(NewPred, *SE);
auto ReplaceMVUses = [&](Value *V) {
VPValue *From = Plan.getLiveIn(V);
if (!From)
return;
VPValue *To = Plan.getConstantInt(
From->getScalarType(),
cast<SCEVConstant>(MVConst)->getAPInt().getLimitedValue());
// 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.getVectorLoopRegion()->getSinglePredecessor();
});
};
ReplaceMVUses(StrideVal);
for (auto *U : StrideVal->users())
if (isa<SExtInst, ZExtInst, TruncInst>(U))
ReplaceMVUses(U);
}
if (StridePredicates.isAlwaysTrue())
return;
VPBasicBlock *Entry = Plan.getEntry();
VPBuilder Builder(Entry);
DebugLoc DL = cast<VPIRBasicBlock>(Entry)
->getIRBasicBlock()
->getTerminator()
->getDebugLoc();
VPSCEVExpander Expander(Builder, *SE, DL);
VPValue *Pred = Expander.tryToExpandPredicate(&StridePredicates);
assert(Pred && "Must be expandable!");
auto *StridesCheckBB = Plan.createVPBasicBlock("strides.check");
VPBasicBlock *ScalarPH = Plan.getScalarPreheader();
VPBlockUtils::insertBlockBefore(StridesCheckBB, Plan.getVectorPreheader());
VPBlockUtils::connectBlocks(StridesCheckBB, ScalarPH);
// SCEVExpander/VPSCEVExpander::expandCodeForPredicate negate the condition,
// so scalar preheader should be the first successor.
std::swap(StridesCheckBB->getSuccessors()[0],
StridesCheckBB->getSuccessors()[1]);
Builder.setInsertPoint(StridesCheckBB);
Builder.createNaryOp(VPInstruction::BranchOnCond, Pred);
for (VPRecipeBase &R : ScalarPH->phis()) {
auto &Phi = cast<VPPhi>(R);
Phi.addIncoming(Phi.getIncomingValueForBlock(Entry));
}
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)
return;
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();
}
}
void VPlanTransforms::makeScalarizationDecisions(VPlan &Plan, VFRange &Range) {
if (LoopVectorizationPlanner::getDecisionAndClampRange(
[&](ElementCount VF) { return VF.isScalar(); }, Range))

View file

@ -587,6 +587,12 @@ struct VPlanTransforms {
VPRecipeBuilder &RecipeBuilder,
VPCostContext &CostCtx);
/// \p MemOps must be updated to contain ones that haven't been processed by
/// the pass.
static void multiversionForUnitStridedMemOps(
VPlan &Plan, VPCostContext &CostCtx, VPRecipeBuilder &RecipeBuilder,
VFRange &Range, SmallVectorImpl<VPInstruction *> &MemOps);
/// Make VPlan-based scalarization decision prior to delegating to the ones
/// made by the legacy CM. Only transforms "usesFirstLaneOnly` def-use chains
/// enabled by prior widening of consecutive memory operations for now.

View file

@ -1005,6 +1005,40 @@ VPValue *VPSCEVExpander::tryToExpand(const SCEV *S) {
}
}
VPValue *VPSCEVExpander::tryToExpandPredicate(const SCEVPredicate *Pred) {
if (auto *CmpPred = dyn_cast<SCEVComparePredicate>(Pred)) {
VPValue *LHS = tryToExpand(CmpPred->getLHS());
if (!LHS)
return nullptr;
VPValue *RHS = tryToExpand(CmpPred->getRHS());
if (!RHS)
return nullptr;
// Match SCEVExpander::expandComparePredicate behavior:
auto InvPred = CmpInst::getInversePredicate(CmpPred->getPredicate());
return Builder.createICmp(InvPred, LHS, RHS, DL);
}
if (auto *UnionPred = dyn_cast<SCEVUnionPredicate>(Pred)) {
SmallVector<VPValue *, 4> Checks;
for (const auto *Pred : UnionPred->getPredicates()) {
VPValue *Check = tryToExpandPredicate(Pred);
if (!Check)
return nullptr;
Checks.push_back(Check);
}
if (Checks.empty())
return Builder.getPlan().getFalse();
VPValue *Result = Checks[0];
for (VPValue *Check : drop_begin(Checks))
Result = Builder.createOr(Result, Check, DL);
return Result;
}
llvm_unreachable("Unhandled predicate");
}
bool vputils::isDeadRecipe(VPRecipeBase &R) {
// Do remove conditional assume instructions as their conditions may be
// flattened.

View file

@ -264,6 +264,11 @@ public:
/// Try to expand \p S into recipes and live-ins using the builder. Returns
/// nullptr if \p S cannot be expanded yet.
VPValue *tryToExpand(const SCEV *S);
/// Try to expand \p Pred into recipes and live-ins using the builder. Returns
/// nullptr if \p Pred cannot be expanded (not expected to happen for the
/// predicates we currently generate).
VPValue *tryToExpandPredicate(const SCEVPredicate *Pred);
};
//===----------------------------------------------------------------------===//
// Utilities for modifying predecessors and successors of VPlan blocks.

File diff suppressed because it is too large Load diff