[polly] [Polly] Guard ISL ast gen compute out (PR #201859)

Shikhar Jain via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 17 09:00:18 PDT 2026


https://github.com/ShikharJ-Corp updated https://github.com/llvm/llvm-project/pull/201859

>From ade679bfd52dc3ed67701f2834fde06d7c2970db 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

Uses isl++ (isl-noexceptions.h) to re-write isParallel, thus
making it leak-safe via RAII and enforcing explicit isl::size
error checks. This also changes the function signature: (1) the
name becomes isKnownParallel, (2) the return type becomes a
tri-state isl::boolean, so a failed/quota-exhausted analysis is
no longer reported as "not parallel", and (3) the arguments
become RAII-capable isl++ equivalents. (4) All invocations have
been updated accordingly for this changed signature.

Fixes #201801
---
 polly/include/polly/DependenceInfo.h      |  12 +-
 polly/lib/Analysis/DependenceInfo.cpp     |  65 ++++++-----
 polly/lib/CodeGen/IslAst.cpp              |  50 ++++++---
 polly/test/IstAstInfo/AstGenComputeOut.ll | 130 ++++++++++++++++++++++
 4 files changed, 209 insertions(+), 48 deletions(-)
 create mode 100644 polly/test/IstAstInfo/AstGenComputeOut.ll

diff --git a/polly/include/polly/DependenceInfo.h b/polly/include/polly/DependenceInfo.h
index c4d7b033e0245..19730c4cd2173 100644
--- a/polly/include/polly/DependenceInfo.h
+++ b/polly/include/polly/DependenceInfo.h
@@ -119,11 +119,13 @@ class Dependences final {
   /// @param MinDistancePtr If not nullptr, the minimal dependence distance will
   ///                       be returned at the address of that pointer
   ///
-  /// @return Returns true, if executing parallel the outermost dimension of
-  ///         @p Schedule is valid according to the dependences @p Deps.
-  bool isParallel(__isl_keep isl_union_map *Schedule,
-                  __isl_take isl_union_map *Deps,
-                  __isl_give isl_pw_aff **MinDistancePtr = nullptr) const;
+  /// @return isl::boolean::true if executing parallel the outermost dimension
+  ///         of @p Schedule is valid according to the dependences @p Deps,
+  ///         isl::boolean::false if it is not, and isl::boolean::error() if the
+  ///         result could not be computed (e.g. the ISL operation quota was
+  ///         exhausted during AST generation).
+  isl::boolean isKnownParallel(isl::union_map Schedule, isl::union_map Deps,
+                               isl::pw_aff *MinDistancePtr = nullptr) const;
 
   /// Check if a new schedule is valid.
   ///
diff --git a/polly/lib/Analysis/DependenceInfo.cpp b/polly/lib/Analysis/DependenceInfo.cpp
index 0f208ec74634b..e9016ebcb3d26 100644
--- a/polly/lib/Analysis/DependenceInfo.cpp
+++ b/polly/lib/Analysis/DependenceInfo.cpp
@@ -712,51 +712,58 @@ bool Dependences::isValidSchedule(
 // dimension, then the loop is parallel. The distance is zero in the current
 // dimension if it is a subset of a map with equal values for the current
 // dimension.
-bool Dependences::isParallel(__isl_keep isl_union_map *Schedule,
-                             __isl_take isl_union_map *Deps,
-                             __isl_give isl_pw_aff **MinDistancePtr) const {
-  isl_set *Deltas, *Distance;
-  isl_map *ScheduleDeps;
-  unsigned 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));
-
-  if (isl_union_map_is_empty(Deps)) {
-    isl_union_map_free(Deps);
+isl::boolean Dependences::isKnownParallel(isl::union_map Schedule,
+                                          isl::union_map Deps,
+                                          isl::pw_aff *MinDistancePtr) const {
+  // A null input means an upstream ISL operation failed (e.g. the operation
+  // quota was exhausted). We cannot compute a result, so report the error
+  // state rather than silently propagating the null through the computation.
+  if (Schedule.is_null() || Deps.is_null())
+    return isl::boolean::error();
+
+  Deps = Deps.apply_range(Schedule);
+  Deps = Deps.apply_domain(Schedule);
+
+  isl::boolean UnionMapIsEmpty = Deps.is_empty();
+  if (UnionMapIsEmpty.is_error())
+    return isl::boolean::error();
+  if (UnionMapIsEmpty.is_true())
     return true;
-  }
 
-  ScheduleDeps = isl_map_from_union_map(Deps);
-  Dimension = isl_map_dim(ScheduleDeps, isl_dim_out) - 1;
+  isl::map ScheduleDeps = isl::map::from_union_map(Deps);
+  isl::size NumberOfDimensions = ScheduleDeps.dim(isl::dim::out);
+  if (NumberOfDimensions.is_error())
+    return isl::boolean::error();
+  if (unsigned(NumberOfDimensions) == 0)
+    return false;
+  unsigned Dimension = unsigned(NumberOfDimensions) - 1;
 
   for (unsigned i = 0; i < Dimension; i++)
-    ScheduleDeps = isl_map_equate(ScheduleDeps, isl_dim_out, i, isl_dim_in, i);
+    ScheduleDeps = ScheduleDeps.equate(isl::dim::out, i, isl::dim::in, i);
 
-  Deltas = isl_map_deltas(ScheduleDeps);
-  Distance = isl_set_universe(isl_set_get_space(Deltas));
+  isl::set Deltas = ScheduleDeps.deltas();
+  isl::set Distance = isl::set::universe(Deltas.get_space());
 
   // [0, ..., 0, +] - All zeros and last dimension larger than zero
   for (unsigned i = 0; i < Dimension; i++)
-    Distance = isl_set_fix_si(Distance, isl_dim_set, i, 0);
+    Distance = Distance.fix_si(isl::dim::set, i, 0);
 
-  Distance = isl_set_lower_bound_si(Distance, isl_dim_set, Dimension, 1);
-  Distance = isl_set_intersect(Distance, Deltas);
+  Distance = Distance.lower_bound_si(isl::dim::set, Dimension, 1);
+  Distance = Distance.intersect(Deltas);
 
-  IsParallel = isl_set_is_empty(Distance);
-  if (IsParallel || !MinDistancePtr) {
-    isl_set_free(Distance);
+  isl::boolean IsParallel = Distance.is_empty();
+  if (IsParallel.is_error())
+    return isl::boolean::error();
+  if (IsParallel.is_true() || !MinDistancePtr)
     return IsParallel;
-  }
 
-  Distance = isl_set_project_out(Distance, isl_dim_set, 0, Dimension);
-  Distance = isl_set_coalesce(Distance);
+  Distance = Distance.project_out(isl::dim::set, 0, Dimension);
+  Distance = Distance.coalesce();
 
   // This last step will compute a expression for the minimal value in the
   // distance polyhedron Distance with regards to the first (outer most)
   // dimension.
-  *MinDistancePtr = isl_pw_aff_coalesce(isl_set_dim_min(Distance, 0));
+  *MinDistancePtr = Distance.dim_min(0).coalesce();
 
   return false;
 }
diff --git a/polly/lib/CodeGen/IslAst.cpp b/polly/lib/CodeGen/IslAst.cpp
index 0ea14ae2fc2e0..124977e6889a2 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,28 +209,30 @@ 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();
   isl::union_map Dep = D->getDependences(
       Dependences::TYPE_RAW | Dependences::TYPE_WAW | Dependences::TYPE_WAR);
 
-  if (!D->isParallel(Schedule.get(), Dep.release())) {
+  isl::boolean IsParallel = D->isKnownParallel(Schedule, Dep);
+  if (IsParallel.is_error())
+    return false;
+  if (IsParallel.is_false()) {
     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
-    isl_pw_aff *MinimalDependenceDistanceIsl = nullptr;
-    D->isParallel(Schedule.get(), DepsAll.release(),
-                  &MinimalDependenceDistanceIsl);
-    NodeInfo->MinimalDependenceDistance =
-        isl::manage(MinimalDependenceDistanceIsl);
+    isl::pw_aff MinimalDependenceDistance;
+    isl::boolean IsParallelWithDistance =
+        D->isKnownParallel(Schedule, DepsAll, &MinimalDependenceDistance);
+    if (IsParallelWithDistance.is_false())
+      NodeInfo->MinimalDependenceDistance = MinimalDependenceDistance;
     return false;
   }
 
   isl::union_map RedDeps = D->getDependences(Dependences::TYPE_TC_RED);
-  if (!D->isParallel(Schedule.get(), RedDeps.release()))
+  if (D->isKnownParallel(Schedule, RedDeps).is_false())
     NodeInfo->IsReductionParallel = true;
 
   if (!NodeInfo->IsReductionParallel)
@@ -235,7 +242,7 @@ static bool astScheduleDimIsParallel(const isl::ast_build &Build,
     if (!MaRedPair.second)
       continue;
     isl::union_map MaRedDeps = isl::manage_copy(MaRedPair.second);
-    if (!D->isParallel(Schedule.get(), MaRedDeps.release()))
+    if (D->isKnownParallel(Schedule, MaRedDeps).is_false())
       NodeInfo->BrokenReductions.insert(MaRedPair.first);
   }
   return true;
@@ -315,6 +322,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 +554,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..f43a9d69fe2f8
--- /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 %loadNPMPolly %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