[polly] Isl ast gen compute out (PR #201859)
Shikhar Jain via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 13 00:06:12 PDT 2026
https://github.com/ShikharJ-Corp updated https://github.com/llvm/llvm-project/pull/201859
>From c2d5d58e7821bd6540e01e466013031fc552ac12 Mon Sep 17 00:00:00 2001
From: ShikharJain <shikharj at qti.qualcomm.com>
Date: Wed, 10 Jun 2026 21:31:10 -0700
Subject: [PATCH] [Polly] Bound ISL operations during AST generation to prevent
indefinite compile time
When -polly-process-unprofitable is used, this allows
SCoPs with complex iteration domains to reach ISL AST generation.
This patch applies IslMaxOperationsGuard around
isl_ast_build_node_from_schedule() in IslAst::init().
A new flag polly-astgen-computeout (default 3000000) controls
the limit.
Also this patch handles the null object leaks (which may emerge
due to polly functions being invoked via callbacks registered
in IslAst::init()), false-positives generated due to im-precise
handling of isl_bool types, few other cases such that we can
avoid the mis-compilation or semantically incorrect compilation.
Also added a unit testcase with description
Fixes #201801
---
polly/lib/Analysis/DependenceInfo.cpp | 22 +++-
polly/lib/CodeGen/IslAst.cpp | 39 ++++++-
polly/test/IstAstInfo/AstGenComputeOut.ll | 130 ++++++++++++++++++++++
3 files changed, 179 insertions(+), 12 deletions(-)
create mode 100644 polly/test/IstAstInfo/AstGenComputeOut.ll
diff --git a/polly/lib/Analysis/DependenceInfo.cpp b/polly/lib/Analysis/DependenceInfo.cpp
index 0f208ec74634b..fcbb9b2fca239 100644
--- a/polly/lib/Analysis/DependenceInfo.cpp
+++ b/polly/lib/Analysis/DependenceInfo.cpp
@@ -717,34 +717,44 @@ bool Dependences::isParallel(__isl_keep isl_union_map *Schedule,
__isl_give isl_pw_aff **MinDistancePtr) const {
isl_set *Deltas, *Distance;
isl_map *ScheduleDeps;
- unsigned Dimension;
+ long Dimension;
bool IsParallel;
Deps = isl_union_map_apply_range(Deps, isl_union_map_copy(Schedule));
Deps = isl_union_map_apply_domain(Deps, isl_union_map_copy(Schedule));
+ isl_bool UnionMapIsEmpty = isl_union_map_is_empty(Deps);
- if (isl_union_map_is_empty(Deps)) {
+ if (UnionMapIsEmpty != isl_bool_false) {
isl_union_map_free(Deps);
- return true;
+ return UnionMapIsEmpty == isl_bool_true;
}
ScheduleDeps = isl_map_from_union_map(Deps);
+ // In the event isl_map_dim() returns isl_size_error which is -1
+ // then Dimension being an unsigned variable would store a very large
+ // wrap-around value. This could result in compilation issues like indefinite
+ // hang. Thus changed Dimension into an signed integer storage
Dimension = isl_map_dim(ScheduleDeps, isl_dim_out) - 1;
- for (unsigned i = 0; i < Dimension; i++)
+ if (Dimension < 0) {
+ isl_map_free(ScheduleDeps);
+ return false;
+ }
+
+ for (unsigned i = 0; i < (unsigned)Dimension; i++)
ScheduleDeps = isl_map_equate(ScheduleDeps, isl_dim_out, i, isl_dim_in, i);
Deltas = isl_map_deltas(ScheduleDeps);
Distance = isl_set_universe(isl_set_get_space(Deltas));
// [0, ..., 0, +] - All zeros and last dimension larger than zero
- for (unsigned i = 0; i < Dimension; i++)
+ for (unsigned i = 0; i < (unsigned)Dimension; i++)
Distance = isl_set_fix_si(Distance, isl_dim_set, i, 0);
Distance = isl_set_lower_bound_si(Distance, isl_dim_set, Dimension, 1);
Distance = isl_set_intersect(Distance, Deltas);
- IsParallel = isl_set_is_empty(Distance);
+ IsParallel = (isl_set_is_empty(Distance) == isl_bool_true);
if (IsParallel || !MinDistancePtr) {
isl_set_free(Distance);
return IsParallel;
diff --git a/polly/lib/CodeGen/IslAst.cpp b/polly/lib/CodeGen/IslAst.cpp
index 0ea14ae2fc2e0..ce4c9875fd8a7 100644
--- a/polly/lib/CodeGen/IslAst.cpp
+++ b/polly/lib/CodeGen/IslAst.cpp
@@ -86,6 +86,11 @@ static cl::opt<bool>
cl::desc("Print the ISL abstract syntax tree"),
cl::cat(PollyCategory));
+static cl::opt<unsigned long>
+ AstGenComputeout("polly-astgen-computeout",
+ cl::desc("Bound the AST generation by a maximal number of "
+ "ISL operations [0 means un-bounded]"),
+ cl::Hidden, cl::init(3000000), cl::cat(PollyCategory));
STATISTIC(ScopsProcessed, "Number of SCoPs processed");
STATISTIC(ScopsBeneficial, "Number of beneficial SCoPs");
STATISTIC(BeneficialAffineLoops, "Number of beneficial affine loops");
@@ -204,18 +209,23 @@ static isl_printer *cbPrintFor(__isl_take isl_printer *Printer,
static bool astScheduleDimIsParallel(const isl::ast_build &Build,
const Dependences *D,
IslAstUserPayload *NodeInfo) {
- if (!D->hasValidDependences())
+ if (!D || !D->hasValidDependences())
return false;
isl::union_map Schedule = Build.get_schedule();
+ if (Schedule.is_null())
+ return false;
isl::union_map Dep = D->getDependences(
Dependences::TYPE_RAW | Dependences::TYPE_WAW | Dependences::TYPE_WAR);
-
+ if (Dep.is_null())
+ return false;
if (!D->isParallel(Schedule.get(), Dep.release())) {
isl::union_map DepsAll =
D->getDependences(Dependences::TYPE_RAW | Dependences::TYPE_WAW |
Dependences::TYPE_WAR | Dependences::TYPE_TC_RED);
// TODO: We will need to change isParallel to stop the unwrapping
+ if (DepsAll.is_null())
+ return false;
isl_pw_aff *MinimalDependenceDistanceIsl = nullptr;
D->isParallel(Schedule.get(), DepsAll.release(),
&MinimalDependenceDistanceIsl);
@@ -225,6 +235,8 @@ static bool astScheduleDimIsParallel(const isl::ast_build &Build,
}
isl::union_map RedDeps = D->getDependences(Dependences::TYPE_TC_RED);
+ if (RedDeps.is_null())
+ return false;
if (!D->isParallel(Schedule.get(), RedDeps.release()))
NodeInfo->IsReductionParallel = true;
@@ -315,6 +327,8 @@ astBuildAfterMark(__isl_take isl_ast_node *Node,
assert(isl_ast_node_get_type(Node) == isl_ast_node_mark);
AstBuildUserInfo *BuildInfo = (AstBuildUserInfo *)User;
auto *Id = isl_ast_node_mark_get_id(Node);
+ if (!Id)
+ return Node;
if (strcmp(isl_id_get_name(Id), "SIMD") == 0)
BuildInfo->InSIMD = false;
isl_id_free(Id);
@@ -545,10 +559,23 @@ void IslAst::init(const Dependences &D) {
}
RunCondition = buildRunCondition(S, isl::manage_copy(Build));
-
- Root = isl::manage(
- isl_ast_build_node_from_schedule(Build, S.getScheduleTree().release()));
- walkAstForStatistics(Root);
+ // Apply IslMaxOperationsGuard on the API that starts the process of AST
+ // generation from the schedule tree. This is to avoid a timeout when the
+ // schedule tree is too big and complex.
+
+ {
+ IslMaxOperationsGuard MaxOpGuard(Ctx.get(), AstGenComputeout);
+ Root = isl::manage(
+ isl_ast_build_node_from_schedule(Build, S.getScheduleTree().release()));
+ if (MaxOpGuard.hasQuotaExceeded()) {
+ POLLY_DEBUG(
+ dbgs() << "AST generation for SCoP in function '"
+ << S.getFunction().getName()
+ << "' exceeded operation limit (operations). Skipping.\n");
+ }
+ }
+ if (!Root.is_null())
+ walkAstForStatistics(Root);
isl_ast_build_free(Build);
}
diff --git a/polly/test/IstAstInfo/AstGenComputeOut.ll b/polly/test/IstAstInfo/AstGenComputeOut.ll
new file mode 100644
index 0000000000000..5c457db12649b
--- /dev/null
+++ b/polly/test/IstAstInfo/AstGenComputeOut.ll
@@ -0,0 +1,130 @@
+; This test checks that Polly's ISL AST generation aborts gracefully when the
+; ISL operation quota (set via -polly-astgen-computeout) is exhausted, instead
+; of running for an unbounded amount of time.
+;
+; The SCoP is constructed to be deliberately expensive for AST generation:
+; - a large iteration space (the outer loop runs with trip count 65536, see
+; the exit test 'icmp eq i64 %phi, 65536' in bb49), combined with
+; - a long chain of conditionals (bb9, bb15, bb18, ... bb48), where one
+; branch of every conditional flows into the common block bb7.
+; Because bb7 is reached from all of these predecessors, its domain becomes the
+; union of every reaching condition, which pushes ISL AST generation into a
+; high-dimensional, blow-up search space. Without a bound this does not finish
+; in reasonable time.
+; This test case has the same characteristics as the one in
+; PR https://github.com/llvm/llvm-project/pull/203073, but is effectively a
+; "larger problem". The complexity of its structure made this test case bail
+; out of the DeLICM phase, yet it previously got stuck in the ISL AST
+; generation phase.
+;
+; The operation limit of polly-astgen-computeout is set to 1 -- the smallest
+; value that still arms the guard (0 means unbounded) -- so the quota trips
+; as early as possible and the test does not depend on an arbitrary tuned cutoff value.
+
+; RUN: opt %s -passes='polly-custom<ast>' -polly-process-unprofitable \
+; RUN: -polly-astgen-computeout=1 -debug-only=polly-ast \
+; RUN: -disable-output 2>&1 | FileCheck %s
+
+define void @eggs(i32 %arg) {
+bb:
+ br label %bb1
+
+bb1: ; preds = %bb49, %bb
+ %phi = phi i64 [ 1, %bb ], [ %add, %bb49 ]
+ br i1 true, label %bb2, label %bb5
+
+bb2: ; preds = %bb1
+ %icmp = icmp eq i32 %arg, 0
+ br i1 %icmp, label %bb3, label %bb5
+
+bb3: ; preds = %bb2
+ %trunc = trunc i64 %phi to i32
+ %and = and i32 %trunc, 1
+ %icmp4 = icmp eq i32 %and, 0
+ br i1 %icmp4, label %bb9, label %bb5
+
+bb5: ; preds = %bb3, %bb2, %bb1
+ %phi6 = phi i8 [ 0, %bb2 ], [ 1, %bb1 ], [ 0, %bb3 ]
+ br label %bb7
+
+bb7: ; preds = %bb48, %bb45, %bb42, %bb39, %bb36, %bb33, %bb30, %bb27, %bb24, %bb21, %bb18, %bb15, %bb9, %bb5
+ %phi8 = phi i8 [ 1, %bb5 ], [ 0, %bb9 ], [ 0, %bb48 ], [ 0, %bb15 ], [ 0, %bb18 ], [ 0, %bb21 ], [ 0, %bb24 ], [ 0, %bb27 ], [ 0, %bb30 ], [ 0, %bb33 ], [ 0, %bb36 ], [ 0, %bb39 ], [ 0, %bb42 ], [ 0, %bb45 ]
+ store i8 0, ptr null, align 1
+ br label %bb49
+
+bb9: ; preds = %bb3
+ %and10 = and i32 %trunc, 2
+ %icmp11 = icmp eq i32 %and10, 0
+ %and12 = and i32 %trunc, 4
+ %icmp13 = icmp eq i32 %and12, 0
+ %and14 = and i1 %icmp11, %icmp13
+ br i1 %and14, label %bb15, label %bb7
+
+bb15: ; preds = %bb9
+ %and16 = and i32 %trunc, 8
+ %icmp17 = icmp eq i32 %and16, 0
+ br i1 %icmp17, label %bb18, label %bb7
+
+bb18: ; preds = %bb15
+ %and19 = and i32 %trunc, 16
+ %icmp20 = icmp eq i32 %and19, 0
+ br i1 %icmp20, label %bb21, label %bb7
+
+bb21: ; preds = %bb18
+ %and22 = and i32 %trunc, 32
+ %icmp23 = icmp eq i32 %and22, 0
+ br i1 %icmp23, label %bb24, label %bb7
+
+bb24: ; preds = %bb21
+ %and25 = and i32 %trunc, 64
+ %icmp26 = icmp eq i32 %and25, 0
+ br i1 %icmp26, label %bb27, label %bb7
+
+bb27: ; preds = %bb24
+ %and28 = and i32 %trunc, 128
+ %icmp29 = icmp eq i32 %and28, 0
+ br i1 %icmp29, label %bb30, label %bb7
+
+bb30: ; preds = %bb27
+ %and31 = and i32 %trunc, 256
+ %icmp32 = icmp eq i32 %and31, 0
+ br i1 %icmp32, label %bb33, label %bb7
+
+bb33: ; preds = %bb30
+ %and34 = and i32 %trunc, 512
+ %icmp35 = icmp eq i32 %and34, 0
+ br i1 %icmp35, label %bb36, label %bb7
+
+bb36: ; preds = %bb33
+ %and37 = and i32 %trunc, 1024
+ %icmp38 = icmp eq i32 %and37, 0
+ br i1 %icmp38, label %bb39, label %bb7
+
+bb39: ; preds = %bb36
+ %and40 = and i32 %trunc, 2048
+ %icmp41 = icmp eq i32 %and40, 0
+ br i1 %icmp41, label %bb42, label %bb7
+
+bb42: ; preds = %bb39
+ %and43 = and i32 %trunc, 4096
+ %icmp44 = icmp eq i32 %and43, 0
+ br i1 %icmp44, label %bb45, label %bb7
+
+bb45: ; preds = %bb42
+ %and46 = and i32 %trunc, 8192
+ %icmp47 = icmp eq i32 %and46, 0
+ br i1 %icmp47, label %bb48, label %bb7
+
+bb48: ; preds = %bb45
+ br i1 false, label %bb49, label %bb7
+
+bb49: ; preds = %bb48, %bb7
+ %add = add i64 %phi, 1
+ %icmp50 = icmp eq i64 %phi, 65536
+ br i1 %icmp50, label %bb51, label %bb1
+
+bb51: ; preds = %bb49
+ ret void
+}
+
+; CHECK: AST generation for SCoP in function 'eggs' exceeded operation limit (operations). Skipping.
More information about the llvm-commits
mailing list