[polly] 6ad85f1 - [Polly] Do not assume loops to be bounded if latch conditions are invalid (#226202)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 25 07:51:17 PDT 2026
Author: Timur Baidusenov
Date: 2026-09-25T16:51:10+02:00
New Revision: 6ad85f11c5151f18a3686322e37f8e64a48be327
URL: https://github.com/llvm/llvm-project/commit/6ad85f11c5151f18a3686322e37f8e64a48be327
DIFF: https://github.com/llvm/llvm-project/commit/6ad85f11c5151f18a3686322e37f8e64a48be327.diff
LOG: [Polly] Do not assume loops to be bounded if latch conditions are invalid (#226202)
If a loop with an `<nsw>` induction variable appears to be unbounded for
some parameter values, `addLoopBoundsToHeaderDomain` removes them from
its domain without a runtime check, but the loop may only appear
unbounded because its latch condition is modeled incorrectly for these
values, e.g. `(zext i16 %n to i64)` is modeled as `n` under the
assumption `n >= 0`. Removing them also dropped that assumption, so for
`n < 0` the loop was not executed at all instead of running `65536 + n`
iterations. Exclude the parameter values for which the latch conditions
are invalid by a runtime check instead.
Fixes #192616 and #192618.
Assisted-by: Claude (Anthropic)
Added:
polly/test/ScopInfo/zext-loop-bound-unbounded.ll
Modified:
polly/lib/Analysis/ScopBuilder.cpp
polly/test/ScopInfo/issue190128.ll
polly/test/ScopInfo/simple_loop_unsigned.ll
Removed:
################################################################################
diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index 7a52ee3481817..9d48ba2f89248 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -746,6 +746,9 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
isl::set UnionBackedgeCondition = HeaderBBDom.empty(HeaderBBDom.get_space());
+ // Parameter values for which a latch condition is not modeled correctly.
+ isl::set InvalidLatchCtx = isl::set::empty(HeaderBBDom.get_space().params());
+
SmallVector<BasicBlock *, 4> LatchBlocks;
L->getLoopLatches(LatchBlocks);
@@ -764,10 +767,18 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
else if (auto *BI = dyn_cast<CondBrInst>(TI)) {
SmallVector<isl_set *, 8> ConditionSets;
int idx = BI->getSuccessor(0) != HeaderBB;
- if (!buildConditionSets(LatchBB, TI, L, LatchBBDom.get(),
- InvalidDomainMap, ConditionSets,
- /*IsInsideDomain=*/false))
+ DenseMap<BasicBlock *, isl::set> LatchInvalidDomainMap;
+ LatchInvalidDomainMap[LatchBB] = LatchBBDom.empty(LatchBBDom.get_space());
+ bool Valid = buildConditionSets(LatchBB, TI, L, LatchBBDom.get(),
+ LatchInvalidDomainMap, ConditionSets,
+ /*IsInsideDomain=*/false);
+ isl::set LatchInvalidDomain = LatchInvalidDomainMap[LatchBB];
+ InvalidDomainMap[LatchBB] =
+ InvalidDomainMap[LatchBB].unite(LatchInvalidDomain);
+ if (!Valid)
return false;
+ InvalidLatchCtx = InvalidLatchCtx.unite(
+ LatchInvalidDomain.intersect(LatchBBDom).params());
// Free the non back edge condition set as we do not need it.
isl_set_free(ConditionSets[1 - idx]);
@@ -806,6 +817,23 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
bool RequiresRTC = !scop->hasNSWAddRecForLoop(L);
isl::set UnboundedCtx = Parts.first.params();
+
+ // An unbounded loop only implies undefined behavior if its latch conditions
+ // are modeled correctly. Otherwise, e.g. if a zero-extended loop bound is
+ // assumed to be non-negative, the loop may just appear to be unbounded and
+ // the parameter values must be excluded by a runtime check. Assuming them
+ // to not occur would make the loop's domain empty, which also drops the
+ // restrictions that would have caught the invalid model.
+ if (!RequiresRTC) {
+ isl::set InvalidUnboundedCtx = UnboundedCtx.intersect(InvalidLatchCtx);
+ if (!InvalidUnboundedCtx.is_empty()) {
+ recordAssumption(&RecordedAssumptions, INFINITELOOP, InvalidUnboundedCtx,
+ HeaderBB->getTerminator()->getDebugLoc(), AS_RESTRICTION,
+ nullptr, /*RequiresRTC=*/true);
+ UnboundedCtx = UnboundedCtx.subtract(InvalidUnboundedCtx);
+ }
+ }
+
recordAssumption(&RecordedAssumptions, INFINITELOOP, UnboundedCtx,
HeaderBB->getTerminator()->getDebugLoc(), AS_RESTRICTION,
nullptr, RequiresRTC);
diff --git a/polly/test/ScopInfo/issue190128.ll b/polly/test/ScopInfo/issue190128.ll
index a56fcfc926256..8f67c801bf674 100644
--- a/polly/test/ScopInfo/issue190128.ll
+++ b/polly/test/ScopInfo/issue190128.ll
@@ -13,7 +13,9 @@
; }
; }
;
-; The constraint -58 <= shl < 32768 (ignorable trunc range) must be checked in an RTC (here: InvalidConstant).
+; The latch condition is only modeled correctly if the start value 0 <= trunc1 + 56 <= 32766
+; (unsigned comparison, nsw increment), i.e. -56 <= shl <= 32710. This must be checked in an RTC
+; (here: Invalid Context) instead of assuming the loop to be bounded.
; Alternatively, %conv6 could be used as a parameter, instead of %shl.
; CHECK: Context:
@@ -21,7 +23,7 @@
; CHECK: Assumed Context:
; CHECK-NEXT: [shl] -> { : }
; CHECK: Invalid Context:
-; CHECK-NEXT: [shl] -> { : shl <= -57 or shl >= 32768 }
+; CHECK-NEXT: [shl] -> { : shl >= 32711 or shl <= -57 }
; CHECK: Defined Behavior Context:
; CHECK-NEXT: [shl] -> { : -56 <= shl <= 32710 }
diff --git a/polly/test/ScopInfo/simple_loop_unsigned.ll b/polly/test/ScopInfo/simple_loop_unsigned.ll
index d3834297e2668..3537306b52904 100644
--- a/polly/test/ScopInfo/simple_loop_unsigned.ll
+++ b/polly/test/ScopInfo/simple_loop_unsigned.ll
@@ -10,7 +10,7 @@
; CHECK: Assumed Context:
; CHECK-NEXT: [N] -> { : }
; CHECK-NEXT: Invalid Context:
-; CHECK-NEXT: [N] -> { : false }
+; CHECK-NEXT: [N] -> { : N < 0 }
;
; CHECK: Domain :=
; CHECK-NEXT: [N] -> { Stmt_bb[i0] : 0 <= i0 < N; Stmt_bb[0] : N = 0 };
diff --git a/polly/test/ScopInfo/zext-loop-bound-unbounded.ll b/polly/test/ScopInfo/zext-loop-bound-unbounded.ll
new file mode 100644
index 0000000000000..f2505d4bbfc1a
--- /dev/null
+++ b/polly/test/ScopInfo/zext-loop-bound-unbounded.ll
@@ -0,0 +1,52 @@
+; RUN: opt %loadNPMPolly '-passes=polly-custom<scops>' -polly-print-scops -disable-output < %s 2>&1 | FileCheck %s
+; RUN: opt %loadNPMPolly '-passes=polly-custom<ast>' -polly-print-ast -disable-output < %s 2>&1 | FileCheck %s --check-prefix=AST
+;
+; https://github.com/llvm/llvm-project/issues/192616
+; https://github.com/llvm/llvm-project/issues/192618
+;
+; The loop bound (zext i16 %n to i64) is modeled as 'n' under the assumption
+; that n is non-negative. For n < 0, the model's exit condition is never
+; satisfied and the loop appears to be unbounded. Since the induction variable
+; is <nsw>, an unbounded loop would be undefined behavior, which removes
+; n < 0 from the loop's domain without a runtime check. In reality, the loop
+; executes 65536 + n iterations, i.e. the non-negativity assumption must be
+; checked at runtime.
+;
+; void f(char *A, unsigned short *N) {
+; unsigned short n = *N;
+; if (n == 0)
+; return;
+; long i = 0;
+; do
+; A[i] = 1;
+; while (++i != n);
+; }
+
+; CHECK: Invalid Context:
+; CHECK-NEXT: [n] -> { : n < 0 }
+; CHECK: Domain :=
+; CHECK-NEXT: [n] -> { Stmt_for[i0] : 0 <= i0 < n };
+
+; AST: if (1 && 0 == n <= -1)
+; AST-EMPTY:
+; AST-NEXT: for (int c0 = 0; c0 < n; c0 += 1)
+; AST-NEXT: Stmt_for(c0);
+
+define void @f(ptr %A, ptr %N) {
+entry:
+ %n = load i16, ptr %N
+ %nz = zext i16 %n to i64
+ %nonzero = icmp ne i16 %n, 0
+ br i1 %nonzero, label %for, label %exit
+
+for:
+ %i = phi i64 [ 0, %entry ], [ %i.next, %for ]
+ %gep = getelementptr inbounds i8, ptr %A, i64 %i
+ store i8 1, ptr %gep
+ %i.next = add nuw nsw i64 %i, 1
+ %done = icmp eq i64 %i.next, %nz
+ br i1 %done, label %exit, label %for
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list