[polly] [Polly] Do not assume loops to be bounded if latch conditions are invalid (PR #226202)

Timur Baidusenov via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 25 07:14:56 PDT 2026


https://github.com/bai-tim updated https://github.com/llvm/llvm-project/pull/226202

>From 0a56eee37fc3f75ac60351af597cf3c7efb48612 Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Tue, 22 Sep 2026 19:40:19 +0300
Subject: [PATCH 1/2] [Polly] Do not assume loops to be bounded if latch
 conditions are invalid

addLoopBoundsToHeaderDomain removes the parameter values for which a loop appears to be unbounded from its domain, and if the loop has an <nsw> induction variable it does so without a runtime check, since an unbounded loop would be undefined behavior. However, a loop may also appear to be unbounded because its latch condition is not modeled correctly for these parameter values: (zext i16 %n to i64) is modeled as n under the assumption that n is non-negative, so for n < 0 the modeled exit condition 'i + 1 == n' is never satisfied, although the loop actually executes 65536 + n iterations. Removing these parameter values from the domain also removed the invalid domain of the latch, so the non-negativity assumption was never checked at runtime and the loop was not executed at all. Collect the parameter values for which the latch conditions are invalid and exclude them by a runtime check instead of assuming them to not occur. Fixes #192616 and #192618.

Assisted-by: Claude (Anthropic)
---
 polly/lib/Analysis/ScopBuilder.cpp            | 34 ++++++++++--
 polly/test/ScopInfo/issue190128.ll            |  2 +-
 polly/test/ScopInfo/simple_loop_unsigned.ll   |  2 +-
 .../ScopInfo/zext-loop-bound-unbounded.ll     | 52 +++++++++++++++++++
 4 files changed, 85 insertions(+), 5 deletions(-)
 create mode 100644 polly/test/ScopInfo/zext-loop-bound-unbounded.ll

diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index 5f8c6dc8ccd382..da7d3d4787a441 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 a56fcfc926256e..d715687f2f9466 100644
--- a/polly/test/ScopInfo/issue190128.ll
+++ b/polly/test/ScopInfo/issue190128.ll
@@ -21,7 +21,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 d3834297e26680..3537306b529046 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 00000000000000..f2505d4bbfc1a2
--- /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
+}

>From 84118f54fde5c2b68744f4a57deb3200a29d3038 Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Fri, 25 Sep 2026 17:13:15 +0300
Subject: [PATCH 2/2] [Polly] Update the comment of issue190128.ll

The runtime check now also covers the values for which the latch condition is not modeled correctly, so the comment describes -56 <= shl <= 32710 instead of the ignorable trunc range.

Assisted-by: Claude (Anthropic)
---
 polly/test/ScopInfo/issue190128.ll | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/polly/test/ScopInfo/issue190128.ll b/polly/test/ScopInfo/issue190128.ll
index d715687f2f9466..8f67c801bf674d 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:



More information about the llvm-commits mailing list