[llvm] [LAA][NFC] Refactor deref no-wrap check; expose broken reverse-loop bounds (PR #211960)
Aleksandr Popov via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 2 14:11:05 PDT 2026
https://github.com/aleks-tmb updated https://github.com/llvm/llvm-project/pull/211960
>From 172ef03d3d19e2d7ceca01079e59b3e6834d57e9 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Fri, 24 Jul 2026 22:37:09 +0000
Subject: [PATCH 1/6] [LAA][NFC] Restructure deref no-wrap check; flag
reverse-step bugs
Split evaluatePtrAddRecAtMaxBTCWillNotWrap into a two-stage form:
compute MaxOffset per direction, then apply the shared
MaxOffset <= DerefBytes check. Rename intermediates after what
they actually hold (LowestOffset, WalkBytes, SpanBytes, MaxOffset)
and document the safety invariants above the function.
The new shape makes two long-standing off-by-EltSize bugs on the
negative-step branch visible; both are flagged with FIXMEs and
pinned by a new LIT test, to be fixed in follow-ups:
* Lower bound is over-strict by EltSize, wrongly rejecting
reverse loops whose last iteration reaches the base pointer.
* Upper bound is under-counted by EltSize, accepting loops
whose top read spills past the deref region.
---
llvm/lib/Analysis/LoopAccessAnalysis.cpp | 70 ++++++++------
.../negative-step-deref-off-by-eltsize.ll | 94 +++++++++++++++++++
2 files changed, 136 insertions(+), 28 deletions(-)
create mode 100644 llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index 1f13106214ea9..f59dd7cee55c3 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -210,6 +210,14 @@ static const SCEV *mulSCEVNoOverflow(const SCEV *A, const SCEV *B,
/// Return true, if evaluating \p AR at \p MaxBTC cannot wrap, because \p AR at
/// \p MaxBTC is guaranteed inbounds of the accessed object.
+///
+/// The accessed byte range is [LowestOffset, LowestOffset + SpanBytes), where
+/// SpanBytes = MaxBTC * |Step| + EltSize,
+/// LowestOffset = smallest byte offset from StartPtr any iteration reaches.
+///
+/// Safety invariants (both directions):
+/// 1. LowestOffset >= 0 — no access below StartPtr.
+/// 2. LowestOffset + SpanBytes <= DerefBytes — no access past the region.
static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
const SCEVAddRecExpr *AR, const SCEV *MaxBTC, const SCEV *EltSize,
ScalarEvolution &SE, const DataLayout &DL, DominatorTree *DT,
@@ -268,54 +276,60 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
MaxBTC = SE.getNoopOrZeroExtend(MaxBTC, WiderTy);
// For the computations below, make sure they don't unsigned wrap.
- if (!SE.isKnownPredicate(CmpInst::ICMP_UGE, AR->getStart(), StartPtr))
+ // FIXME: for a negative step this holds the HIGHEST accessed address, not
+ // the lowest
+ const SCEV *LowestAddr = AR->getStart();
+ if (!SE.isKnownPredicate(CmpInst::ICMP_UGE, LowestAddr, StartPtr))
return false;
- const SCEV *StartOffset = SE.getNoopOrZeroExtend(
- SE.getMinusSCEV(AR->getStart(), StartPtr), WiderTy);
+ const SCEV *LowestOffset =
+ SE.getNoopOrZeroExtend(SE.getMinusSCEV(LowestAddr, StartPtr), WiderTy);
if (!LoopGuards)
LoopGuards.emplace(ScalarEvolution::LoopGuards::collect(AR->getLoop(), SE));
MaxBTC = SE.applyLoopGuards(MaxBTC, *LoopGuards);
- const SCEV *OffsetAtLastIter =
- mulSCEVNoOverflow(MaxBTC, SE.getAbsExpr(Step, /*IsNSW=*/false), SE);
- if (!OffsetAtLastIter) {
+ const SCEV *AbsStep = SE.getAbsExpr(Step, /*IsNSW=*/false);
+ // Total distance (in bytes) walked between the first and the last
+ // accessed pointer; MaxBTC * |Step|.
+ const SCEV *WalkBytes = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
+ if (!WalkBytes) {
// Re-try with constant max backedge-taken count if using the symbolic one
// failed.
MaxBTC = SE.getConstantMaxBackedgeTakenCount(AR->getLoop());
if (isa<SCEVCouldNotCompute>(MaxBTC))
return false;
- MaxBTC = SE.getNoopOrZeroExtend(
- MaxBTC, WiderTy);
- OffsetAtLastIter =
- mulSCEVNoOverflow(MaxBTC, SE.getAbsExpr(Step, /*IsNSW=*/false), SE);
- if (!OffsetAtLastIter)
+ MaxBTC = SE.getNoopOrZeroExtend(MaxBTC, WiderTy);
+ WalkBytes = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
+ if (!WalkBytes)
return false;
}
- const SCEV *OffsetEndBytes = addSCEVNoOverflow(
- OffsetAtLastIter, SE.getNoopOrZeroExtend(EltSize, WiderTy), SE);
- if (!OffsetEndBytes)
+ // Total length in bytes of the accessed range (from the first accessed
+ // byte through the end of the last access); WalkBytes + EltSize.
+ const SCEV *SpanBytes = addSCEVNoOverflow(
+ WalkBytes, SE.getNoopOrZeroExtend(EltSize, WiderTy), SE);
+ if (!SpanBytes)
return false;
+ // Compute MaxOffset per direction: exclusive upper offset of the
+ // accessed range.
+ const SCEV *MaxOffset;
if (IsKnownNonNegative) {
- // For positive steps, check if
- // (AR->getStart() - StartPtr) + (MaxBTC * Step) + EltSize <= DerefBytes,
- // while making sure none of the computations unsigned wrap themselves.
- const SCEV *EndBytes = addSCEVNoOverflow(StartOffset, OffsetEndBytes, SE);
- if (!EndBytes)
+ MaxOffset = addSCEVNoOverflow(LowestOffset, SpanBytes, SE);
+ if (!MaxOffset)
return false;
-
DerefBytesSCEV = SE.applyLoopGuards(DerefBytesSCEV, *LoopGuards);
- return SE.isKnownPredicate(CmpInst::ICMP_ULE, EndBytes, DerefBytesSCEV);
+ } else {
+ // FIXME: LowestOffset here is actually the HIGHEST offset (see FIXME
+ // above). Lower check is over-strict by EltSize, upper is under-counted
+ // by EltSize.
+ assert(SE.isKnownNegative(Step) && "must be known negative");
+ if (!SE.isKnownPredicate(CmpInst::ICMP_SGE, LowestOffset, SpanBytes))
+ return false;
+ MaxOffset = LowestOffset;
}
-
- // For negative steps check if
- // * StartOffset >= (MaxBTC * Step + EltSize)
- // * StartOffset <= DerefBytes.
- assert(SE.isKnownNegative(Step) && "must be known negative");
- return SE.isKnownPredicate(CmpInst::ICMP_SGE, StartOffset, OffsetEndBytes) &&
- SE.isKnownPredicate(CmpInst::ICMP_ULE, StartOffset, DerefBytesSCEV);
+ // MaxOffset must not exceed the deref-region end.
+ return SE.isKnownPredicate(CmpInst::ICMP_ULE, MaxOffset, DerefBytesSCEV);
}
std::pair<const SCEV *, const SCEV *> llvm::getStartAndEndForAccess(
diff --git a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
new file mode 100644
index 0000000000000..160b5847a728a
--- /dev/null
+++ b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
@@ -0,0 +1,94 @@
+; RUN: opt -passes='print<access-info>' -disable-output %s 2>&1 | FileCheck %s
+
+; Reverse i32 loop over 4 elements whose access range exactly fills the
+; dereferenceable region (deref(16), reads bytes [0, 16)).
+;
+; TODO: LAA should recognise that this AR fits within the deref
+; region and produce tight bounds (Low: %A, High: %A + 16).
+;
+; Pseudocode:
+; // A, B: at least 4 i32s dereferenceable each
+; for (i64 i = 3; i >= 0; --i) {
+; i32 l = A[i]; // A[3], A[2], A[1], A[0]
+; B[i] = 0;
+; if (l == 0) break;
+; }
+
+define void @reverse_reaches_base(ptr dereferenceable(16) %A, ptr dereferenceable(16) %B) {
+; CHECK-LABEL: 'reverse_reaches_base'
+; CHECK: Group GRP0:
+; CHECK-NEXT: (Low: (-4 + inttoptr (i64 -1 to ptr))<nsw> High: (16 + %B)<nuw>)
+; CHECK-NEXT: Member: {(12 + %B)<nuw>,+,-4}<nw><%loop>
+; CHECK: Group GRP1:
+; CHECK-NEXT: (Low: (-4 + inttoptr (i64 -1 to ptr))<nsw> High: (16 + %A)<nuw>)
+; CHECK-NEXT: Member: {(12 + %A)<nuw>,+,-4}<nw><%loop>
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 3, %entry ], [ %iv.dec, %latch ]
+ %gep.A = getelementptr inbounds i32, ptr %A, i64 %iv
+ %gep.B = getelementptr inbounds i32, ptr %B, i64 %iv
+ %l = load i32, ptr %gep.A, align 4
+ store i32 0, ptr %gep.B, align 4
+ %uncntable = icmp eq i32 %l, 0
+ br i1 %uncntable, label %exit.early, label %latch
+
+latch:
+ %iv.dec = add nsw i64 %iv, -1
+ %ec = icmp eq i64 %iv, 0
+ br i1 %ec, label %exit.done, label %loop
+
+exit.early:
+ ret void
+
+exit.done:
+ ret void
+}
+
+; Reverse i32 loop whose top read spills one byte past the deref end.
+; The IR is UB by construction: top i32 read at byte 13 covers [13, 17),
+; but deref(16) only guarantees [0, 16).
+;
+; TODO: LAA should reject this AR (top access exits the deref region)
+; and fall back to the wide low bound.
+;
+; Pseudocode:
+; for (i64 i = 13; i > 1; i -= 4) {
+; i32 l = *(i32*)((char*)A + i); // reads [i, i+4)
+; *(i32*)((char*)B + i) = 0;
+; if (l == 0) break;
+; }
+
+define void @reverse_top_spills(ptr dereferenceable(16) %A, ptr dereferenceable(16) %B) {
+; CHECK-LABEL: 'reverse_top_spills'
+; CHECK: Group GRP0:
+; CHECK-NEXT: (Low: (5 + %B)<nuw> High: (17 + %B))
+; CHECK-NEXT: Member: {(13 + %B)<nuw>,+,-4}<nw><%loop2>
+; CHECK: Group GRP1:
+; CHECK-NEXT: (Low: (5 + %A)<nuw> High: (17 + %A))
+; CHECK-NEXT: Member: {(13 + %A)<nuw>,+,-4}<nw><%loop2>
+entry:
+ br label %loop2
+
+loop2:
+ %iv = phi i64 [ 13, %entry ], [ %iv.dec, %latch ]
+ %gep.A = getelementptr inbounds i8, ptr %A, i64 %iv
+ %gep.B = getelementptr inbounds i8, ptr %B, i64 %iv
+ %l = load i32, ptr %gep.A, align 1
+ store i32 0, ptr %gep.B, align 1
+ %uncntable = icmp eq i32 %l, 0
+ br i1 %uncntable, label %exit.early, label %latch
+
+latch:
+ %iv.dec = add nsw i64 %iv, -4
+ %ec = icmp eq i64 %iv, 5
+ br i1 %ec, label %exit.done, label %loop2
+
+exit.early:
+ ret void
+
+exit.done:
+ ret void
+}
+
>From b00fd96be11396413b3d5989efec7aa1dba591b5 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Mon, 27 Jul 2026 10:33:32 +0000
Subject: [PATCH 2/6] Clarify FIXMEs and test comment
---
llvm/lib/Analysis/LoopAccessAnalysis.cpp | 13 ++++++++-----
.../negative-step-deref-off-by-eltsize.ll | 8 ++++----
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index f59dd7cee55c3..9ff3eac135c72 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -276,8 +276,8 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
MaxBTC = SE.getNoopOrZeroExtend(MaxBTC, WiderTy);
// For the computations below, make sure they don't unsigned wrap.
- // FIXME: for a negative step this holds the HIGHEST accessed address, not
- // the lowest
+ // FIXME: for a negative step LowestAddr should be evaluated at the last
+ // iteration as AR->evaluateAtIteration(MaxBTC, SE).
const SCEV *LowestAddr = AR->getStart();
if (!SE.isKnownPredicate(CmpInst::ICMP_UGE, LowestAddr, StartPtr))
return false;
@@ -320,9 +320,12 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
return false;
DerefBytesSCEV = SE.applyLoopGuards(DerefBytesSCEV, *LoopGuards);
} else {
- // FIXME: LowestOffset here is actually the HIGHEST offset (see FIXME
- // above). Lower check is over-strict by EltSize, upper is under-counted
- // by EltSize.
+ // FIXME: two independent off-by-EltSize bugs on this branch:
+ // 1. LowestOffset here is actually the HIGHEST offset, because
+ // LowestAddr is computed from AR->getStart() rather than
+ // AR->evaluateAtIteration(MaxBTC, SE) (see FIXME above).
+ // 2. The lower check is over-strict by EltSize and the upper is
+ // under-counted by EltSize.
assert(SE.isKnownNegative(Step) && "must be known negative");
if (!SE.isKnownPredicate(CmpInst::ICMP_SGE, LowestOffset, SpanBytes))
return false;
diff --git a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
index 160b5847a728a..b1d9a984592c8 100644
--- a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
+++ b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
@@ -47,11 +47,11 @@ exit.done:
}
; Reverse i32 loop whose top read spills one byte past the deref end.
-; The IR is UB by construction: top i32 read at byte 13 covers [13, 17),
-; but deref(16) only guarantees [0, 16).
+; The top i32 read at byte 13 covers [13, 17), but deref(16) only
+; guarantees [0, 16) — bytes at/after 16 may or may not be dereferenceable.
;
-; TODO: LAA should reject this AR (top access exits the deref region)
-; and fall back to the wide low bound.
+; TODO: LAA must not assume the AR fits in the deref region and should
+; fall back to the wide low bound.
;
; Pseudocode:
; for (i64 i = 13; i > 1; i -= 4) {
>From 054bd4549241e6a8d1187167393a2fe9ffdae0e7 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Wed, 29 Jul 2026 12:53:05 +0000
Subject: [PATCH 3/6] Revert StartOffset rename per review
---
llvm/lib/Analysis/LoopAccessAnalysis.cpp | 31 +++++++++---------------
1 file changed, 12 insertions(+), 19 deletions(-)
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index 9ff3eac135c72..d2f01c339fd29 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -210,14 +210,6 @@ static const SCEV *mulSCEVNoOverflow(const SCEV *A, const SCEV *B,
/// Return true, if evaluating \p AR at \p MaxBTC cannot wrap, because \p AR at
/// \p MaxBTC is guaranteed inbounds of the accessed object.
-///
-/// The accessed byte range is [LowestOffset, LowestOffset + SpanBytes), where
-/// SpanBytes = MaxBTC * |Step| + EltSize,
-/// LowestOffset = smallest byte offset from StartPtr any iteration reaches.
-///
-/// Safety invariants (both directions):
-/// 1. LowestOffset >= 0 — no access below StartPtr.
-/// 2. LowestOffset + SpanBytes <= DerefBytes — no access past the region.
static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
const SCEVAddRecExpr *AR, const SCEV *MaxBTC, const SCEV *EltSize,
ScalarEvolution &SE, const DataLayout &DL, DominatorTree *DT,
@@ -276,13 +268,14 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
MaxBTC = SE.getNoopOrZeroExtend(MaxBTC, WiderTy);
// For the computations below, make sure they don't unsigned wrap.
- // FIXME: for a negative step LowestAddr should be evaluated at the last
- // iteration as AR->evaluateAtIteration(MaxBTC, SE).
- const SCEV *LowestAddr = AR->getStart();
- if (!SE.isKnownPredicate(CmpInst::ICMP_UGE, LowestAddr, StartPtr))
+ // FIXME: for a negative step the lowest accessed address is not
+ // AR->getStart() but AR->evaluateAtIteration(MaxBTC, SE); the check below
+ // therefore compares StartPtr against the highest accessed address instead
+ // of the lowest.
+ if (!SE.isKnownPredicate(CmpInst::ICMP_UGE, AR->getStart(), StartPtr))
return false;
- const SCEV *LowestOffset =
- SE.getNoopOrZeroExtend(SE.getMinusSCEV(LowestAddr, StartPtr), WiderTy);
+ const SCEV *StartOffset = SE.getNoopOrZeroExtend(
+ SE.getMinusSCEV(AR->getStart(), StartPtr), WiderTy);
if (!LoopGuards)
LoopGuards.emplace(ScalarEvolution::LoopGuards::collect(AR->getLoop(), SE));
@@ -315,21 +308,21 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
// accessed range.
const SCEV *MaxOffset;
if (IsKnownNonNegative) {
- MaxOffset = addSCEVNoOverflow(LowestOffset, SpanBytes, SE);
+ MaxOffset = addSCEVNoOverflow(StartOffset, SpanBytes, SE);
if (!MaxOffset)
return false;
DerefBytesSCEV = SE.applyLoopGuards(DerefBytesSCEV, *LoopGuards);
} else {
// FIXME: two independent off-by-EltSize bugs on this branch:
- // 1. LowestOffset here is actually the HIGHEST offset, because
- // LowestAddr is computed from AR->getStart() rather than
+ // 1. StartOffset here is actually the HIGHEST offset, because it is
+ // computed from AR->getStart() rather than
// AR->evaluateAtIteration(MaxBTC, SE) (see FIXME above).
// 2. The lower check is over-strict by EltSize and the upper is
// under-counted by EltSize.
assert(SE.isKnownNegative(Step) && "must be known negative");
- if (!SE.isKnownPredicate(CmpInst::ICMP_SGE, LowestOffset, SpanBytes))
+ if (!SE.isKnownPredicate(CmpInst::ICMP_SGE, StartOffset, SpanBytes))
return false;
- MaxOffset = LowestOffset;
+ MaxOffset = StartOffset;
}
// MaxOffset must not exceed the deref-region end.
return SE.isKnownPredicate(CmpInst::ICMP_ULE, MaxOffset, DerefBytesSCEV);
>From 1cef1ac9099aa4eea1321069cc45e7752dff036d Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Fri, 31 Jul 2026 12:14:44 +0000
Subject: [PATCH 4/6] Regenerate check lines for
negative-step-deref-off-by-eltsize test
---
.../negative-step-deref-off-by-eltsize.ll | 64 ++++++++++++++-----
1 file changed, 48 insertions(+), 16 deletions(-)
diff --git a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
index b1d9a984592c8..1fc5fe3e92fb5 100644
--- a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
+++ b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
; RUN: opt -passes='print<access-info>' -disable-output %s 2>&1 | FileCheck %s
; Reverse i32 loop over 4 elements whose access range exactly fills the
@@ -16,12 +17,28 @@
define void @reverse_reaches_base(ptr dereferenceable(16) %A, ptr dereferenceable(16) %B) {
; CHECK-LABEL: 'reverse_reaches_base'
-; CHECK: Group GRP0:
-; CHECK-NEXT: (Low: (-4 + inttoptr (i64 -1 to ptr))<nsw> High: (16 + %B)<nuw>)
-; CHECK-NEXT: Member: {(12 + %B)<nuw>,+,-4}<nw><%loop>
-; CHECK: Group GRP1:
-; CHECK-NEXT: (Low: (-4 + inttoptr (i64 -1 to ptr))<nsw> High: (16 + %A)<nuw>)
-; CHECK-NEXT: Member: {(12 + %A)<nuw>,+,-4}<nw><%loop>
+; CHECK-NEXT: loop:
+; CHECK-NEXT: Memory dependences are safe with run-time checks
+; CHECK-NEXT: Dependences:
+; CHECK-NEXT: Run-time memory checks:
+; CHECK-NEXT: Check 0:
+; CHECK-NEXT: Comparing group GRP0:
+; CHECK-NEXT: %gep.B = getelementptr inbounds i32, ptr %B, i64 %iv
+; CHECK-NEXT: Against group GRP1:
+; CHECK-NEXT: %gep.A = getelementptr inbounds i32, ptr %A, i64 %iv
+; CHECK-NEXT: Grouped accesses:
+; CHECK-NEXT: Group GRP0:
+; CHECK-NEXT: (Low: (-4 + inttoptr (i64 -1 to ptr))<nsw> High: (16 + %B)<nuw>)
+; CHECK-NEXT: Member: {(12 + %B)<nuw>,+,-4}<nw><%loop>
+; CHECK-NEXT: Group GRP1:
+; CHECK-NEXT: (Low: (-4 + inttoptr (i64 -1 to ptr))<nsw> High: (16 + %A)<nuw>)
+; CHECK-NEXT: Member: {(12 + %A)<nuw>,+,-4}<nw><%loop>
+; CHECK-EMPTY:
+; CHECK-NEXT: Non vectorizable stores to invariant address were not found in loop.
+; CHECK-NEXT: SCEV assumptions:
+; CHECK-EMPTY:
+; CHECK-NEXT: Expressions re-written:
+;
entry:
br label %loop
@@ -62,16 +79,32 @@ exit.done:
define void @reverse_top_spills(ptr dereferenceable(16) %A, ptr dereferenceable(16) %B) {
; CHECK-LABEL: 'reverse_top_spills'
-; CHECK: Group GRP0:
-; CHECK-NEXT: (Low: (5 + %B)<nuw> High: (17 + %B))
-; CHECK-NEXT: Member: {(13 + %B)<nuw>,+,-4}<nw><%loop2>
-; CHECK: Group GRP1:
-; CHECK-NEXT: (Low: (5 + %A)<nuw> High: (17 + %A))
-; CHECK-NEXT: Member: {(13 + %A)<nuw>,+,-4}<nw><%loop2>
+; CHECK-NEXT: loop:
+; CHECK-NEXT: Memory dependences are safe with run-time checks
+; CHECK-NEXT: Dependences:
+; CHECK-NEXT: Run-time memory checks:
+; CHECK-NEXT: Check 0:
+; CHECK-NEXT: Comparing group GRP0:
+; CHECK-NEXT: %gep.B = getelementptr inbounds i8, ptr %B, i64 %iv
+; CHECK-NEXT: Against group GRP1:
+; CHECK-NEXT: %gep.A = getelementptr inbounds i8, ptr %A, i64 %iv
+; CHECK-NEXT: Grouped accesses:
+; CHECK-NEXT: Group GRP0:
+; CHECK-NEXT: (Low: (5 + %B)<nuw> High: (17 + %B))
+; CHECK-NEXT: Member: {(13 + %B)<nuw>,+,-4}<nw><%loop>
+; CHECK-NEXT: Group GRP1:
+; CHECK-NEXT: (Low: (5 + %A)<nuw> High: (17 + %A))
+; CHECK-NEXT: Member: {(13 + %A)<nuw>,+,-4}<nw><%loop>
+; CHECK-EMPTY:
+; CHECK-NEXT: Non vectorizable stores to invariant address were not found in loop.
+; CHECK-NEXT: SCEV assumptions:
+; CHECK-EMPTY:
+; CHECK-NEXT: Expressions re-written:
+;
entry:
- br label %loop2
+ br label %loop
-loop2:
+loop:
%iv = phi i64 [ 13, %entry ], [ %iv.dec, %latch ]
%gep.A = getelementptr inbounds i8, ptr %A, i64 %iv
%gep.B = getelementptr inbounds i8, ptr %B, i64 %iv
@@ -83,7 +116,7 @@ loop2:
latch:
%iv.dec = add nsw i64 %iv, -4
%ec = icmp eq i64 %iv, 5
- br i1 %ec, label %exit.done, label %loop2
+ br i1 %ec, label %exit.done, label %loop
exit.early:
ret void
@@ -91,4 +124,3 @@ exit.early:
exit.done:
ret void
}
-
>From a8ca0ef444ea1eff5898e69f3b90dc38f4a0837c Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <42888396+aleks-tmb at users.noreply.github.com>
Date: Sun, 2 Aug 2026 22:42:45 +0200
Subject: [PATCH 5/6] Apply suggestions from code review
Co-authored-by: Florian Hahn <flo at fhahn.com>
---
llvm/lib/Analysis/LoopAccessAnalysis.cpp | 4 ++--
.../LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index d2f01c339fd29..8214230b01ee8 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -282,8 +282,8 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
MaxBTC = SE.applyLoopGuards(MaxBTC, *LoopGuards);
const SCEV *AbsStep = SE.getAbsExpr(Step, /*IsNSW=*/false);
- // Total distance (in bytes) walked between the first and the last
- // accessed pointer; MaxBTC * |Step|.
+ // Total distance (in bytes) between the first and the last
+ // accessed pointer.
const SCEV *WalkBytes = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
if (!WalkBytes) {
// Re-try with constant max backedge-taken count if using the symbolic one
diff --git a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
index 1fc5fe3e92fb5..1a53fba949749 100644
--- a/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
+++ b/llvm/test/Analysis/LoopAccessAnalysis/negative-step-deref-off-by-eltsize.ll
@@ -1,7 +1,7 @@
; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
; RUN: opt -passes='print<access-info>' -disable-output %s 2>&1 | FileCheck %s
-; Reverse i32 loop over 4 elements whose access range exactly fills the
+; Reverse loop loading 4 i32 elements whose access range exactly fills the
; dereferenceable region (deref(16), reads bytes [0, 16)).
;
; TODO: LAA should recognise that this AR fits within the deref
>From ab6c28b831f3782f353e3e0721acb66dcbd84b75 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Sun, 2 Aug 2026 21:03:47 +0000
Subject: [PATCH 6/6] WalkBytes/SpanBytes to DistToLastIter/AccessedBytes
---
llvm/lib/Analysis/LoopAccessAnalysis.cpp | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index 8214230b01ee8..c99d43dd1ccc2 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -284,31 +284,31 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
const SCEV *AbsStep = SE.getAbsExpr(Step, /*IsNSW=*/false);
// Total distance (in bytes) between the first and the last
// accessed pointer.
- const SCEV *WalkBytes = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
- if (!WalkBytes) {
+ const SCEV *DistToLastIter = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
+ if (!DistToLastIter) {
// Re-try with constant max backedge-taken count if using the symbolic one
// failed.
MaxBTC = SE.getConstantMaxBackedgeTakenCount(AR->getLoop());
if (isa<SCEVCouldNotCompute>(MaxBTC))
return false;
MaxBTC = SE.getNoopOrZeroExtend(MaxBTC, WiderTy);
- WalkBytes = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
- if (!WalkBytes)
+ DistToLastIter = mulSCEVNoOverflow(MaxBTC, AbsStep, SE);
+ if (!DistToLastIter)
return false;
}
// Total length in bytes of the accessed range (from the first accessed
- // byte through the end of the last access); WalkBytes + EltSize.
- const SCEV *SpanBytes = addSCEVNoOverflow(
- WalkBytes, SE.getNoopOrZeroExtend(EltSize, WiderTy), SE);
- if (!SpanBytes)
+ // byte through the end of the last access).
+ const SCEV *AccessedBytes = addSCEVNoOverflow(
+ DistToLastIter, SE.getNoopOrZeroExtend(EltSize, WiderTy), SE);
+ if (!AccessedBytes)
return false;
// Compute MaxOffset per direction: exclusive upper offset of the
// accessed range.
const SCEV *MaxOffset;
if (IsKnownNonNegative) {
- MaxOffset = addSCEVNoOverflow(StartOffset, SpanBytes, SE);
+ MaxOffset = addSCEVNoOverflow(StartOffset, AccessedBytes, SE);
if (!MaxOffset)
return false;
DerefBytesSCEV = SE.applyLoopGuards(DerefBytesSCEV, *LoopGuards);
@@ -320,7 +320,7 @@ static bool evaluatePtrAddRecAtMaxBTCWillNotWrap(
// 2. The lower check is over-strict by EltSize and the upper is
// under-counted by EltSize.
assert(SE.isKnownNegative(Step) && "must be known negative");
- if (!SE.isKnownPredicate(CmpInst::ICMP_SGE, StartOffset, SpanBytes))
+ if (!SE.isKnownPredicate(CmpInst::ICMP_SGE, StartOffset, AccessedBytes))
return false;
MaxOffset = StartOffset;
}
More information about the llvm-commits
mailing list