[polly] [Polly] Detect preconditions of cached user assumption translations (PR #226204)
Timur Baidusenov via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 24 11:01:28 PDT 2026
https://github.com/bai-tim updated https://github.com/llvm/llvm-project/pull/226204
>From 60adc356728d66f6c622dcd8ddad27aab3d06c72 Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Tue, 22 Sep 2026 19:16:10 +0300
Subject: [PATCH 1/2] [Polly] Detect preconditions of cached user assumption
translations
addUserAssumptions decided whether a user assumption can be added to the context by checking whether building its condition recorded new assumptions, but SCEVAffinator caches translated expressions, so an assumption that shares an expression with an earlier one, such as the same truncation, does not record the precondition again; it was then added to the context, which gists the invalid context and removes the runtime check that the precondition relies on. For example, with __builtin_assume((signed char)n >= 0) and __builtin_assume((signed char)n < 10), the context became 'n <= 9' and the 'n >= 128' runtime check was dropped, even though n = 261 satisfies both assumptions, and conditions such as 'if (n < 10)' in the SCoP were removed from the generated code. Instead, collect the invalid domain of each assumption separately, which is part of the cached translation and therefore also available for expressions that were translated before, and if it is non-empty, add the assumption to the defined behavior context as introduced in #189350.
Assisted-by: Claude (Anthropic)
---
polly/lib/Analysis/ScopBuilder.cpp | 22 +++++--
.../user-assumption-cached-precondition.ll | 63 +++++++++++++++++++
2 files changed, 81 insertions(+), 4 deletions(-)
create mode 100644 polly/test/ScopInfo/user-assumption-cached-precondition.ll
diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index 5f8c6dc8ccd38..c6460e5a0b962 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -1441,17 +1441,31 @@ void ScopBuilder::addUserAssumptions(
NewParams.insert(Param);
}
- size_t NumAssumptions = RecordedAssumptions.size();
SmallVector<isl_set *, 2> ConditionSets;
auto *TI = InScop ? CI->getParent()->getTerminator() : nullptr;
BasicBlock *BB = InScop ? CI->getParent() : R.getEntry();
auto *Dom = InScop ? isl_set_copy(scop->getDomainConditions(BB).get())
: isl_set_copy(scop->getContext().get());
assert(Dom && "Cannot propagate a nullptr.");
- bool Valid = buildConditionSets(BB, Val, TI, L, Dom, InvalidDomainMap,
- ConditionSets);
+
+ // Collect the invalid domain of this assumption on its own to determine
+ // whether its translation depends on preconditions (such as a truncation
+ // not overflowing). Checking for newly recorded assumptions is not
+ // sufficient: SCEVAffinator caches translated expressions, so a
+ // precondition shared with an earlier assumption is only recorded once.
+ isl::set &BBInvalidDomain = InvalidDomainMap[BB];
+ assert(!BBInvalidDomain.is_null() && "Cannot propagate a nullptr.");
+ DenseMap<BasicBlock *, isl::set> AssumptionInvalidDomainMap;
+ AssumptionInvalidDomainMap[BB] =
+ isl::set::empty(BBInvalidDomain.get_space());
+ bool Valid = buildConditionSets(BB, Val, TI, L, Dom,
+ AssumptionInvalidDomainMap, ConditionSets);
isl_set_free(Dom);
+ isl::set AssumptionInvalidDomain = AssumptionInvalidDomainMap[BB];
+ bool HasPreconditions = !AssumptionInvalidDomain.is_empty();
+ BBInvalidDomain = BBInvalidDomain.unite(AssumptionInvalidDomain);
+
if (!Valid)
continue;
@@ -1487,7 +1501,7 @@ void ScopBuilder::addUserAssumptions(
// correctness of AssumptionCtx. Using DefinedBehaviorContext which does not
// gist the other contexts.
// TODO: Use recordAssumption() for adding context/assumptions
- if (NumAssumptions == RecordedAssumptions.size()) {
+ if (!HasPreconditions) {
isl::set newContext =
scop->getContext().intersect(isl::manage(AssumptionCtx));
scop->setContext(newContext);
diff --git a/polly/test/ScopInfo/user-assumption-cached-precondition.ll b/polly/test/ScopInfo/user-assumption-cached-precondition.ll
new file mode 100644
index 0000000000000..43c1e444c641b
--- /dev/null
+++ b/polly/test/ScopInfo/user-assumption-cached-precondition.ll
@@ -0,0 +1,63 @@
+; 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
+;
+; Both user assumptions are derived from (trunc i64 %n to i8), which is only
+; modeled correctly if the truncation does not overflow. SCEVAffinator caches
+; the translation of the truncation, so the precondition is recorded only for
+; the first assumption. The second assumption must nevertheless not be added
+; to the context: this would remove the 'n >= 128' runtime check from the
+; invalid context and the 'n <= 9' condition from the domain of Stmt_then.
+; For n = 261, both assumptions hold but Stmt_then must not be executed.
+;
+; void f(int *A, long n) {
+; __builtin_assume((signed char)n >= 0);
+; __builtin_assume((signed char)n < 10);
+; for (long i = 0; i < 4; i++)
+; if (n < 10)
+; A[i] = 1;
+; }
+
+; CHECK: Context:
+; CHECK-NEXT: [n] -> { : -9223372036854775808 <= n <= 9223372036854775807 }
+; CHECK: Invalid Context:
+; CHECK-NEXT: [n] -> { : n <= -129 or n >= 128 }
+; CHECK: Defined Behavior Context:
+; CHECK-NEXT: [n] -> { : 0 <= n <= 9 }
+; CHECK: Domain :=
+; CHECK-NEXT: [n] -> { Stmt_then[i0] : n <= 9 and 0 <= i0 <= 3 };
+
+; AST: if (1 && 0 == (n <= -129 || n >= 128))
+; AST-EMPTY:
+; AST-NEXT: if (n <= 9)
+; AST-NEXT: for (int c0 = 0; c0 <= 3; c0 += 1)
+; AST-NEXT: Stmt_then(c0);
+
+define void @f(ptr %A, i64 %n) {
+entry:
+ %t = trunc i64 %n to i8
+ %c1 = icmp sge i8 %t, 0
+ call void @llvm.assume(i1 %c1)
+ %c2 = icmp slt i8 %t, 10
+ call void @llvm.assume(i1 %c2)
+ br label %for
+
+for:
+ %i = phi i64 [ 0, %entry ], [ %i.next, %latch ]
+ %small = icmp slt i64 %n, 10
+ br i1 %small, label %then, label %latch
+
+then:
+ %gep = getelementptr inbounds i32, ptr %A, i64 %i
+ store i32 1, ptr %gep
+ br label %latch
+
+latch:
+ %i.next = add nuw nsw i64 %i, 1
+ %cmp = icmp slt i64 %i.next, 4
+ br i1 %cmp, label %for, label %exit
+
+exit:
+ ret void
+}
+
+declare void @llvm.assume(i1)
>From fb224655f8a1237c774b6d79d83acc554873f16f Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Thu, 24 Sep 2026 19:06:22 +0300
Subject: [PATCH 2/2] Do not keep a reference into InvalidDomainMap across
buildConditionSets
Copy the invalid domain of the block before building the conditions of the assumption and store the union afterwards, instead of holding a reference to the map entry.
Assisted-by: Claude (Anthropic)
---
polly/lib/Analysis/ScopBuilder.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index c6460e5a0b962..7a52ee3481817 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -1453,7 +1453,7 @@ void ScopBuilder::addUserAssumptions(
// not overflowing). Checking for newly recorded assumptions is not
// sufficient: SCEVAffinator caches translated expressions, so a
// precondition shared with an earlier assumption is only recorded once.
- isl::set &BBInvalidDomain = InvalidDomainMap[BB];
+ isl::set BBInvalidDomain = InvalidDomainMap[BB];
assert(!BBInvalidDomain.is_null() && "Cannot propagate a nullptr.");
DenseMap<BasicBlock *, isl::set> AssumptionInvalidDomainMap;
AssumptionInvalidDomainMap[BB] =
@@ -1464,7 +1464,7 @@ void ScopBuilder::addUserAssumptions(
isl::set AssumptionInvalidDomain = AssumptionInvalidDomainMap[BB];
bool HasPreconditions = !AssumptionInvalidDomain.is_empty();
- BBInvalidDomain = BBInvalidDomain.unite(AssumptionInvalidDomain);
+ InvalidDomainMap[BB] = BBInvalidDomain.unite(AssumptionInvalidDomain);
if (!Valid)
continue;
More information about the llvm-commits
mailing list