[llvm] [LoopPeel] Peel last iteration to enable load widening (PR #173420)
Guy David via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 4 10:48:07 PDT 2026
================
@@ -749,13 +753,194 @@ static bool violatesLegacyMultiExitLoopCheck(Loop *L) {
});
}
+namespace {
+// Represents a group of loads in a loop that can be combined into a wider one.
+struct LoadGroup {
+ // Base object being read.
+ Value *BasePtr;
+ // First load instruction in the program order.
+ LoadInst *FirstLoad;
+ // Pairs of (load instruction, offset from base) sorted by offset.
+ SmallVector<std::pair<LoadInst *, APInt>, 4> Loads;
+ // An applicable wider integer type to load as.
+ Type *WideType;
+};
+
+// Helper to compute load group span and validate for widening.
+static std::optional<LoadGroup> tryFormLoadGroupForWidening(
+ Value *Base, SmallVectorImpl<std::pair<LoadInst *, APInt>> &Loads, Loop &L,
+ ScalarEvolution &SE, const DataLayout &DL, const TargetTransformInfo &TTI) {
+ // Verify all loads use the same address space.
+ unsigned AddrSpace = Loads[0].first->getPointerAddressSpace();
+ for (const auto &[Load, Offset] : Loads) {
+ if (Load->getPointerAddressSpace() != AddrSpace)
+ return std::nullopt;
+ }
+
+ // Find the span of the loaded data.
+ int64_t Left = std::numeric_limits<int64_t>::max();
+ int64_t Right = std::numeric_limits<int64_t>::min();
+ for (const auto &[Load, Offset] : Loads) {
+ int64_t OffsetVal = Offset.getSExtValue();
+ int64_t StoreSize =
+ static_cast<int64_t>(DL.getTypeStoreSize(Load->getType()));
+ if (OffsetVal > std::numeric_limits<int64_t>::max() - StoreSize)
+ return std::nullopt;
+ Left = std::min(Left, OffsetVal);
+ Right = std::max(Right, OffsetVal + StoreSize);
+ }
+ assert((Left < Right) && "Invalid load group span");
+ uint64_t TotalBytes = Right - Left;
+ uint64_t TotalBits = TotalBytes * 8;
+ Type *WideType =
+ DL.getSmallestLegalIntType(L.getHeader()->getContext(), TotalBits);
+ if (!WideType)
+ return std::nullopt;
+ unsigned WideBits = WideType->getIntegerBitWidth();
+ // Total size is already natural for the target, no benefit from widening.
+ if (WideBits == TotalBits)
+ return std::nullopt;
+ // Peeling doubles dereferenceable bytes, ensure wide type fits.
+ if (WideBits > TotalBits * 2)
+ return std::nullopt;
+ // Check alignment is unconstrained and without penalty.
+ unsigned Fast = 0;
+ if (!TTI.allowsMisalignedMemoryAccesses(L.getHeader()->getContext(), WideBits,
+ AddrSpace, Align(1), &Fast) ||
+ !Fast)
+ return std::nullopt;
+ // Validate pointer stride across iterations.
+ const SCEVConstant *ConstStep = nullptr;
+ if (!match(SE.getSCEV(Base),
+ m_scev_AffineAddRec(m_SCEV(), m_SCEVConstant(ConstStep),
+ m_SpecificLoop(&L))))
+ return std::nullopt;
+ int64_t StepVal = ConstStep->getValue()->getSExtValue();
+ if (StepVal != static_cast<int64_t>(TotalBytes))
+ return std::nullopt;
+
+ LoadInst *FirstLoad = Loads[0].first;
+ // Cost model: compare cost of individual loads vs wide load + extraction ops.
+ // Sort loads by offset first since we need this for cost calculation.
+ llvm::sort(Loads, [](const auto &A, const auto &B) {
+ return A.second.slt(B.second);
+ });
+ int64_t FirstOffset = Loads[0].second.getSExtValue();
+
+ TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput;
+
+ // Cost of original individual loads.
+ InstructionCost OriginalCost = 0;
+ for (const auto &[Load, Offset] : Loads) {
+ OriginalCost += TTI.getMemoryOpCost(Instruction::Load, Load->getType(),
+ Load->getAlign(), AddrSpace, CostKind);
+ }
+
+ // Cost of wide load + extraction operations (shift + trunc for each load).
----------------
guy-david wrote:
I thought about adding a helper in DataLayout but it couldn't find a clean refactor, mainly because `scalarizeExtExtract` uses a vector type while here there's none.
https://github.com/llvm/llvm-project/pull/173420
More information about the llvm-commits
mailing list