[polly] Bound ISL operations during DeLICM scalar collapsing (PR #203073)

Shikhar Jain via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 28 13:06:42 PDT 2026


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

>From fdbde07a1960dfa044aa75ddff3cc2a5b70c9887 Mon Sep 17 00:00:00 2001
From: ShikharJain <shikharj at qti.qualcomm.com>
Date: Sun, 28 Jun 2026 12:52:11 -0700
Subject: [PATCH] [Polly] Bound ISL operations during collapsing scalars to
 store in DeLICM phase

The IslMaxOperationsGuard in DeLICM previously covered only the zone
analysis performed in computeZone(). The subsequent greedyCollapse()
calls collapseScalarsToStore(), which can hang indefinitely and thus
needs to be bounded.

This patch expands the already-present budget polly-delicm-max-ops to
also cover collapseScalarsToStore(). When the quota is exceeded, it
bails out with a proper diagnostic, incrementing DeLICMOutOfQuota.

Also added a unit test case with description.

Fixes: #202045
---
 polly/lib/Transform/DeLICM.cpp                | 60 +++++++++++++--
 .../test/DeLICM/outofquota-greedycollapse.ll  | 73 +++++++++++++++++++
 2 files changed, 126 insertions(+), 7 deletions(-)
 create mode 100644 polly/test/DeLICM/outofquota-greedycollapse.ll

diff --git a/polly/lib/Transform/DeLICM.cpp b/polly/lib/Transform/DeLICM.cpp
index 4deace112f5b4..970576c524281 100644
--- a/polly/lib/Transform/DeLICM.cpp
+++ b/polly/lib/Transform/DeLICM.cpp
@@ -40,7 +40,7 @@ cl::opt<int>
     DelicmMaxOps("polly-delicm-max-ops",
                  cl::desc("Maximum number of isl operations to invest for "
                           "lifetime analysis; 0=no limit"),
-                 cl::init(1000000), cl::cat(PollyCategory));
+                 cl::init(1500000), cl::cat(PollyCategory));
 
 cl::opt<bool> DelicmOverapproximateWrites(
     "polly-delicm-overapproximate-writes",
@@ -545,6 +545,13 @@ class DeLICMImpl final : public ZoneAlgorithm {
   /// The number of PHIs mapped to some array element.
   int NumberOfMappedPHIScalars = 0;
 
+  /// Shared ISL operations budget guarding the expensive zone analysis
+  /// (computeZone) and the scalar-to-store collapsing (greedyCollapse). It is
+  /// constructed dormant (AutoEnter=false) and armed narrowly around each
+  /// dangerous region via IslQuotaScope, so the same budget covers both
+  /// regions without ever nesting two armed scopes.
+  IslMaxOperationsGuard MaxOpGuard;
+
   /// Determine whether two knowledges are conflicting with each other.
   ///
   /// @see Knowledge::isConflicting
@@ -1024,9 +1031,35 @@ class DeLICMImpl final : public ZoneAlgorithm {
     auto TargetAccRel = getAccessRelationFor(TargetStoreMA);
 
     // { Zone[] -> DomTarget[] }
-    // For each point in time, find the next target store instance.
-    auto Target =
-        computeScalarReachingOverwrite(Schedule, TargetDom, false, true);
+    // For each point in time, find the next target store instance. This can be
+    // expensive for SCoPs with many modular/quasi-affine constraints, so bound
+    // it with the shared ISL operations budget.
+    isl::map Target;
+    {
+      IslQuotaScope MaxOpScope = MaxOpGuard.enter();
+      Target = computeScalarReachingOverwrite(Schedule, TargetDom, false, true);
+
+      if (MaxOpScope.hasQuotaExceeded()) {
+        DeLICMOutOfQuota++;
+        assert(
+            isl_ctx_last_error(IslCtx.get()) == isl_error_quota &&
+            "The only reason that these things have not been computed should "
+            "be if the max-operations limit hit");
+        POLLY_DEBUG(
+            dbgs() << "collapseScalarsToStore exceeded max_operations\n");
+        DebugLoc Begin, End;
+        getDebugLocations(getBBPairForRegion(&S->getRegion()), Begin, End);
+        OptimizationRemarkAnalysis R(DEBUG_TYPE, "OutOfQuota", Begin,
+                                     S->getEntry());
+        R << "maximal number of operations exceeded during "
+             "collapseScalarsToStore";
+        S->getFunction().getContext().diagnose(R);
+        return false;
+      }
+
+      if (Target.is_null())
+        return false;
+    }
 
     // { Zone[] -> Element[] }
     // Use the target store's write location as a suggestion to map scalars to.
@@ -1187,7 +1220,9 @@ class DeLICMImpl final : public ZoneAlgorithm {
   }
 
 public:
-  DeLICMImpl(Scop *S, LoopInfo *LI) : ZoneAlgorithm("polly-delicm", S, LI) {}
+  DeLICMImpl(Scop *S, LoopInfo *LI)
+      : ZoneAlgorithm("polly-delicm", S, LI),
+        MaxOpGuard(IslCtx.get(), DelicmMaxOps, /*AutoEnter=*/false) {}
 
   /// Calculate the lifetime (definition to last use) of every array element.
   ///
@@ -1200,14 +1235,14 @@ class DeLICMImpl final : public ZoneAlgorithm {
     isl::union_map EltKnown, EltWritten;
 
     {
-      IslMaxOperationsGuard MaxOpGuard(IslCtx.get(), DelicmMaxOps);
-
+      IslQuotaScope MaxOpScope = MaxOpGuard.enter();
       computeCommon();
 
       EltUnused = computeLifetime();
       EltKnown = computeKnown(true, false);
       EltWritten = computeWritten();
     }
+
     DeLICMAnalyzed++;
 
     if (EltUnused.is_null() || EltKnown.is_null() || EltWritten.is_null()) {
@@ -1239,6 +1274,7 @@ class DeLICMImpl final : public ZoneAlgorithm {
   /// the first processed element claims it.
   void greedyCollapse() {
     bool Modified = false;
+    bool MaxOpQuotaExceeded = false;
 
     for (auto &Stmt : *S) {
       for (auto *MA : Stmt) {
@@ -1332,9 +1368,19 @@ class DeLICMImpl final : public ZoneAlgorithm {
         assert(isCompatibleAccess(MA));
         NumberOfCompatibleTargets++;
         POLLY_DEBUG(dbgs() << "Analyzing target access " << MA << "\n");
+
         if (collapseScalarsToStore(MA))
           Modified = true;
+
+        else {
+          if (MaxOpGuard.hasQuotaExceeded()) {
+            MaxOpQuotaExceeded = true;
+            break;
+          }
+        }
       }
+      if (MaxOpQuotaExceeded)
+        break;
     }
 
     if (Modified)
diff --git a/polly/test/DeLICM/outofquota-greedycollapse.ll b/polly/test/DeLICM/outofquota-greedycollapse.ll
new file mode 100644
index 0000000000000..b60c1de1a1587
--- /dev/null
+++ b/polly/test/DeLICM/outofquota-greedycollapse.ll
@@ -0,0 +1,73 @@
+; RUN: opt %loadNPMPolly '-passes=polly-custom<delicm>' -polly-process-unprofitable -pass-remarks-analysis=polly-delicm -disable-output < %s 2>&1 | FileCheck %s
+
+; This test exercises the ISL operations guard in the DeLICM phase when
+; collapsing scalars to a store. The structure of the SCoP below has a few
+; important characteristics:
+; a) A large loop iteration space.
+; b) A long if-else ladder such that, of the two branches exiting each
+;    conditional block, one is always a predecessor of a common basic block.
+; c) The domain of the common BB is the union of all domains reaching it,
+;    which forces certain ISL operations to handle a high-dimensional,
+;    extremely large search space.
+; Thus it is necessary to bound these operations and bail out instead of
+; hanging indefinitely.
+; With the shared IslMaxOperationsGuard[budget defined by -polly-delicm-max-ops]
+; now armed during collapseScalarsToStore() as well, the analysis aborts
+; once the operations budget is exhausted, emitting an "OutOfQuota" remark
+; and leaving the SCoP unmodified.
+
+; CHECK: maximal number of operations exceeded during collapseScalarsToStore
+
+; ModuleID = '<stdin>'
+source_filename = "<stdin>"
+
+define void @ham(ptr %arg) {
+bb:
+  br label %bb1
+
+bb1:                                              ; preds = %bb19, %bb
+  %phi = phi i64 [ 1, %bb ], [ %add, %bb19 ]
+  %trunc = trunc i64 %phi to i32
+  %and = and i32 %trunc, 1
+  %icmp = icmp eq i32 %and, 0
+  br i1 %icmp, label %bb4, label %bb2
+
+bb2:                                              ; preds = %bb16, %bb13, %bb10, %bb7, %bb4, %bb1
+  %phi3 = phi i8 [ 1, %bb1 ], [ 0, %bb4 ], [ 1, %bb13 ], [ 0, %bb16 ], [ 0, %bb10 ], [ 1, %bb7 ]
+  %getelementptr = getelementptr i8, ptr %arg, i64 %phi
+  store i8 %phi3, ptr %getelementptr, align 1
+  br label %bb19
+
+bb4:                                              ; preds = %bb1
+  %and5 = and i64 %phi, 254
+  %icmp6 = icmp eq i64 %and5, 0
+  br i1 %icmp6, label %bb7, label %bb2
+
+bb7:                                              ; preds = %bb4
+  %and8 = and i32 %trunc, 256
+  %icmp9 = icmp eq i32 %and8, 0
+  br i1 %icmp9, label %bb10, label %bb2
+
+bb10:                                             ; preds = %bb7
+  %and11 = and i64 %phi, 3584
+  %icmp12 = icmp eq i64 %and11, 0
+  br i1 %icmp12, label %bb13, label %bb2
+
+bb13:                                             ; preds = %bb10
+  %and14 = and i32 %trunc, 8192
+  %icmp15 = icmp eq i32 %and14, 0
+  br i1 %icmp15, label %bb16, label %bb2
+
+bb16:                                             ; preds = %bb13
+  %and17 = and i64 %phi, 49152
+  %icmp18 = icmp eq i64 %and17, 0
+  br i1 %icmp18, label %bb19, label %bb2
+
+bb19:                                             ; preds = %bb16, %bb2
+  %add = add i64 %phi, 1
+  %icmp20 = icmp eq i64 %add, 65536
+  br i1 %icmp20, label %bb21, label %bb1
+
+bb21:                                             ; preds = %bb19
+  ret void
+}



More information about the llvm-commits mailing list