[polly] [Polly] Consider all conditions in loop for its unbounded parameter values (PR #226549)
Timur Baidusenov via llvm-commits
llvm-commits at lists.llvm.org
Sat Oct 3 14:42:53 PDT 2026
https://github.com/bai-tim updated https://github.com/llvm/llvm-project/pull/226549
>From 46d727ce786dae9c25abfa5f1f7499cf795ca47e Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Fri, 25 Sep 2026 18:19:52 +0300
Subject: [PATCH 1/2] [Polly] Consider all conditions in a loop for its
unbounded parameter values
addLoopBoundsToHeaderDomain only excluded the parameter values for which a latch's own branch condition is not modeled correctly from being assumed to make the loop unbounded. The domain of a latch is also derived from the other branch conditions in the loop: the exit condition may be in another block, e.g. the header with an unconditional latch, and any other condition may decide whether the exit condition is evaluated at all. A zero-extended value that is assumed to be non-negative in such a condition still made the loop appear to be unbounded for negative values, which were then removed from the domain without a runtime check. Use the invalid domains of all blocks in the loop, taken before the header's domain is restricted to the loop bounds. This also adds the missing runtime checks to four existing tests with such loops. Fixes #226515.
Assisted-by: Claude (Anthropic)
---
polly/lib/Analysis/ScopBuilder.cpp | 43 ++++---
polly/test/DeLICM/pr41656.ll | 2 +-
polly/test/DeLICM/pr48783.ll | 2 +-
...ser_provided_non_dominating_assumptions.ll | 1 +
polly/test/ScopInfo/zero_ext_of_truncate_2.ll | 2 +-
.../ScopInfo/zext-loop-bound-unbounded.ll | 110 ++++++++++++++++++
6 files changed, 139 insertions(+), 21 deletions(-)
diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index 9d48ba2f89248..f222cfe32d628 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -746,9 +746,6 @@ 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);
@@ -767,18 +764,10 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
else if (auto *BI = dyn_cast<CondBrInst>(TI)) {
SmallVector<isl_set *, 8> ConditionSets;
int idx = BI->getSuccessor(0) != HeaderBB;
- 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)
+ if (!buildConditionSets(LatchBB, TI, L, LatchBBDom.get(),
+ InvalidDomainMap, ConditionSets,
+ /*IsInsideDomain=*/false))
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]);
@@ -794,6 +783,24 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
UnionBackedgeCondition = UnionBackedgeCondition.unite(BackedgeCondition);
}
+ // Parameter values for which a branch condition inside the loop is not
+ // modeled correctly. The domains of the latches are derived from all these
+ // conditions, not only from the conditions of the latches themselves: the
+ // exit condition may be in another block, e.g. the header with an
+ // unconditional latch, and any other condition may decide whether the exit
+ // condition is evaluated at all. Collect them before the header's domain is
+ // restricted to the loop bounds, since the domains of the other blocks are
+ // later derived from it.
+ isl::set InvalidLoopCtx = isl::set::empty(HeaderBBDom.get_space().params());
+ for (BasicBlock *BB : L->blocks()) {
+ isl::set InvalidDomain = InvalidDomainMap.lookup(BB);
+ if (InvalidDomain.is_null() || InvalidDomain.is_empty() ||
+ !scop->isDomainDefined(BB))
+ continue;
+ InvalidLoopCtx = InvalidLoopCtx.unite(
+ InvalidDomain.intersect(scop->getDomainConditions(BB)).params());
+ }
+
isl::map ForwardMap = ForwardMap.lex_le(HeaderBBDom.get_space());
for (int i = 0; i < LoopDepth; i++)
ForwardMap = ForwardMap.equate(isl::dim::in, i, isl::dim::out, i);
@@ -818,14 +825,14 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
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
+ // An unbounded loop only implies undefined behavior if the conditions in the
+ // loop 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);
+ isl::set InvalidUnboundedCtx = UnboundedCtx.intersect(InvalidLoopCtx);
if (!InvalidUnboundedCtx.is_empty()) {
recordAssumption(&RecordedAssumptions, INFINITELOOP, InvalidUnboundedCtx,
HeaderBB->getTerminator()->getDebugLoc(), AS_RESTRICTION,
diff --git a/polly/test/DeLICM/pr41656.ll b/polly/test/DeLICM/pr41656.ll
index 82799e4fd1ab8..f2b3aaed347b5 100644
--- a/polly/test/DeLICM/pr41656.ll
+++ b/polly/test/DeLICM/pr41656.ll
@@ -81,7 +81,7 @@ attributes #2 = { nounwind }
; CHECK: Invalid Context:
-; CHECK-NEXT: [call24] -> { : false }
+; CHECK-NEXT: [call24] -> { : call24 < 0 }
; CHECK: Defined Behavior Context:
; CHECK-NEXT: [call24] -> { : 3 <= call24 <= 2147483647 }
diff --git a/polly/test/DeLICM/pr48783.ll b/polly/test/DeLICM/pr48783.ll
index 10f8b64c3dd2f..f9a13a317a78c 100644
--- a/polly/test/DeLICM/pr48783.ll
+++ b/polly/test/DeLICM/pr48783.ll
@@ -82,7 +82,7 @@ attributes #2 = { nounwind }
; CHECK: Invalid Context:
-; CHECK-NEXT: [call24] -> { : false }
+; CHECK-NEXT: [call24] -> { : call24 < 0 }
; CHECK: Defined Behavior Context:
; CHECK-NEXT: [call24] -> { : 3 <= call24 <= 2147483647 }
diff --git a/polly/test/ScopInfo/user_provided_non_dominating_assumptions.ll b/polly/test/ScopInfo/user_provided_non_dominating_assumptions.ll
index a6eed5df2063e..be9079536bd64 100644
--- a/polly/test/ScopInfo/user_provided_non_dominating_assumptions.ll
+++ b/polly/test/ScopInfo/user_provided_non_dominating_assumptions.ll
@@ -3,6 +3,7 @@
; CHECK: remark: <unknown>:0:0: SCoP begins here.
; CHECK-NEXT: remark: <unknown>:0:0: Use user assumption: [i, N, M] -> { : N <= i or (N > i and N >= 0) }
; CHECK-NEXT: remark: <unknown>:0:0: Inbounds assumption: [i, N, M] -> { : N <= i or (N > i and M <= 100) }
+; CHECK-NEXT: remark: <unknown>:0:0: Finite loop restriction: [i, N, M] -> { : N > i and M < 0 }
; CHECK-NEXT: remark: <unknown>:0:0: SCoP ends here.
;
; void f(int *restrict A, int *restrict B, int i, int N, int M, int C[100][100]) {
diff --git a/polly/test/ScopInfo/zero_ext_of_truncate_2.ll b/polly/test/ScopInfo/zero_ext_of_truncate_2.ll
index ee68054b06096..ee3642f7ae1e8 100644
--- a/polly/test/ScopInfo/zero_ext_of_truncate_2.ll
+++ b/polly/test/ScopInfo/zero_ext_of_truncate_2.ll
@@ -13,7 +13,7 @@
; CHECK: Assumed Context:
; CHECK-NEXT: [N, tmp] -> { : }
; CHECK: Invalid Context:
-; CHECK-NEXT: [N, tmp] -> { : tmp <= -2147483649 or tmp >= 2147483648 or (N > 0 and tmp < 0) }
+; CHECK-NEXT: [N, tmp] -> { : tmp <= -2147483649 or tmp >= 2147483648 or N < 0 or (N > 0 and tmp < 0) }
; CHECK: Defined Behavior Context:
; CHECK-NEXT: [N, tmp] -> { : -2147483648 <= tmp <= 2147483647 and ((0 < N <= 2147483647 and tmp >= 0) or N = 0) }
;
diff --git a/polly/test/ScopInfo/zext-loop-bound-unbounded.ll b/polly/test/ScopInfo/zext-loop-bound-unbounded.ll
index f2505d4bbfc1a..6d10073367802 100644
--- a/polly/test/ScopInfo/zext-loop-bound-unbounded.ll
+++ b/polly/test/ScopInfo/zext-loop-bound-unbounded.ll
@@ -3,6 +3,7 @@
;
; https://github.com/llvm/llvm-project/issues/192616
; https://github.com/llvm/llvm-project/issues/192618
+; https://github.com/llvm/llvm-project/issues/226515
;
; 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
@@ -12,6 +13,13 @@
; executes 65536 + n iterations, i.e. the non-negativity assumption must be
; checked at runtime.
;
+; The same applies if the exit condition is not in the latch, but in another
+; block of the loop, and the latch is unconditional (split_latch and
+; exiting_body): the latch's domain is derived from that condition. It also
+; applies if the invalid condition does not exit the loop, but decides whether
+; the exit condition is evaluated (guarded_exit): for n < 0, 'i <u n' is never
+; satisfied in the model, but in reality the loop exits after 100 iterations.
+;
; void f(char *A, unsigned short *N) {
; unsigned short n = *N;
; if (n == 0)
@@ -22,11 +30,41 @@
; while (++i != n);
; }
+; CHECK-LABEL: Function: f
+; CHECK: Invalid Context:
+; CHECK-NEXT: [n] -> { : n < 0 }
+; CHECK: Domain :=
+; CHECK-NEXT: [n] -> { Stmt_for[i0] : 0 <= i0 < n };
+
+; CHECK-LABEL: Function: split_latch
; CHECK: Invalid Context:
; CHECK-NEXT: [n] -> { : n < 0 }
; CHECK: Domain :=
; CHECK-NEXT: [n] -> { Stmt_for[i0] : 0 <= i0 < n };
+; CHECK-LABEL: Function: exiting_body
+; CHECK: Invalid Context:
+; CHECK-NEXT: [n] -> { : n < 0 }
+; CHECK: Domain :=
+; CHECK-NEXT: [n] -> { Stmt_for[i0] : 0 <= i0 < n };
+
+; CHECK-LABEL: Function: guarded_exit
+; CHECK: Invalid Context:
+; CHECK-NEXT: [n] -> { : n < 0 }
+
+; AST-LABEL: :: isl ast :: f ::
+; AST: if (1 && 0 == n <= -1)
+; AST-EMPTY:
+; AST-NEXT: for (int c0 = 0; c0 < n; c0 += 1)
+; AST-NEXT: Stmt_for(c0);
+
+; AST-LABEL: :: isl ast :: split_latch ::
+; AST: if (1 && 0 == n <= -1)
+; AST-EMPTY:
+; AST-NEXT: for (int c0 = 0; c0 < n; c0 += 1)
+; AST-NEXT: Stmt_for(c0);
+
+; AST-LABEL: :: isl ast :: exiting_body ::
; AST: if (1 && 0 == n <= -1)
; AST-EMPTY:
; AST-NEXT: for (int c0 = 0; c0 < n; c0 += 1)
@@ -50,3 +88,75 @@ for:
exit:
ret void
}
+
+define void @split_latch(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, %latch ]
+ %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 %latch
+
+latch:
+ br label %for
+
+exit:
+ ret void
+}
+
+define void @exiting_body(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, %latch ]
+ %gep = getelementptr inbounds i8, ptr %A, i64 %i
+ store i8 1, ptr %gep
+ br label %body
+
+body:
+ %i.next = add nuw nsw i64 %i, 1
+ %done = icmp eq i64 %i.next, %nz
+ br i1 %done, label %exit, label %latch
+
+latch:
+ br label %for
+
+exit:
+ ret void
+}
+
+define void @guarded_exit(ptr %A, ptr %N) {
+entry:
+ %n = load i16, ptr %N
+ %nz = zext i16 %n to i64
+ br label %for
+
+for:
+ %i = phi i64 [ 0, %entry ], [ %i.next, %latch ]
+ %gep = getelementptr inbounds i8, ptr %A, i64 %i
+ store i8 1, ptr %gep
+ %i.next = add nuw nsw i64 %i, 1
+ %inrange = icmp ult i64 %i, %nz
+ br i1 %inrange, label %check, label %latch
+
+check:
+ %done = icmp eq i64 %i.next, 100
+ br i1 %done, label %exit, label %latch
+
+latch:
+ br label %for
+
+exit:
+ ret void
+}
>From 97c0cb6e1137a6ba9bad89f4dd8eaf29fa774954 Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Sun, 4 Oct 2026 00:05:37 +0300
Subject: [PATCH 2/2] Describe code in comment on InvalidLoopCtx
---
polly/lib/Analysis/ScopBuilder.cpp | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index f222cfe32d628..7a7b190149f0e 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -783,14 +783,11 @@ bool ScopBuilder::addLoopBoundsToHeaderDomain(
UnionBackedgeCondition = UnionBackedgeCondition.unite(BackedgeCondition);
}
- // Parameter values for which a branch condition inside the loop is not
- // modeled correctly. The domains of the latches are derived from all these
- // conditions, not only from the conditions of the latches themselves: the
- // exit condition may be in another block, e.g. the header with an
- // unconditional latch, and any other condition may decide whether the exit
- // condition is evaluated at all. Collect them before the header's domain is
- // restricted to the loop bounds, since the domains of the other blocks are
- // later derived from it.
+ // Parameter values for which a branch condition in the loop is not modeled
+ // correctly. Each of these conditions can affect the domains of the latches
+ // that the loop bounds are derived from, e.g. an exit condition in the
+ // header. The header's domain is used before it is restricted below to the
+ // part in which the loop is bounded.
isl::set InvalidLoopCtx = isl::set::empty(HeaderBBDom.get_space().params());
for (BasicBlock *BB : L->blocks()) {
isl::set InvalidDomain = InvalidDomainMap.lookup(BB);
More information about the llvm-commits
mailing list