[polly] ce36410 - [Polly] Materialize escaping scalars from empty-domain statements as poison (#220008)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 24 06:57:58 PDT 2026


Author: Shikhar Jain
Date: 2026-09-24T15:57:49+02:00
New Revision: ce36410607a87ee035224e2a6a819b24aa8f0bab

URL: https://github.com/llvm/llvm-project/commit/ce36410607a87ee035224e2a6a819b24aa8f0bab
DIFF: https://github.com/llvm/llvm-project/commit/ce36410607a87ee035224e2a6a819b24aa8f0bab.diff

LOG: [Polly] Materialize escaping scalars from empty-domain statements as poison (#220008)

Problem Summary: In a versioned SCoP, Polly may remove a statement with
an empty iteration domain before building its access relations. If that
statement contains an escaping MemoryKind::Value MUST_WRITE access,
removing the
statement also prevents the corresponding ScopArrayInfo from being
registered. During code generation, Polly then fails to materialize the
escaping scalar, leaving a merge PHI that refers to a value defined only
on the original path.
This causes an LLVM verifier failure because the value does not dominate
all uses.

Fix: When removeStmtNotInDomainMap() is about to remove a statement with
a null or empty domain, register the ScopArrayInfo for its qualifying
escaping scalar accesses before allowing the statement to be removed.
The statement is still pruned normally, but code generation can now
materialize the scalar through a .s2a slot and reload it on the
optimized merge edge. Since the defining statement is absent on that
path, the reload is poison. This is sound because
the poison is used only for parameter ranges where the original program
already has undefined behavior

The new .merge PHI provides a dominating definition on the optimized
edge, which resolves the verifier failure while keeping the region
optimized.

Fixes #206551

Added: 
    polly/test/CodeGen/broken-dominance-escaping-scalar.ll

Modified: 
    polly/lib/Analysis/ScopBuilder.cpp
    polly/lib/Analysis/ScopInfo.cpp

Removed: 
    


################################################################################
diff  --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index b793681e1651f0..5f8c6dc8ccd382 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -1328,6 +1328,16 @@ void ScopBuilder::buildEscapingDependences(Instruction *Inst) {
   // Check for uses of this instruction outside the scop. Because we do not
   // iterate over such instructions and therefore did not "ensure" the existence
   // of a write, we must determine such use here.
+  // ------------------- TODO -------------------------
+  // If domain information for an instruction (via its containing ScopStmt)
+  // is available at this point, then we can perform SAI registration for
+  // escaping scalars that are also doomed (i.e., belong to a ScopStmt with
+  // an invalid domain) and have a must-write access.
+  // However, for this necessary domain information to be available, we need
+  // to reshuffle the ScopBuilder pipeline such that buildDomains() and the
+  // functions that depend only on it are moved before/above
+  // buildAccessFunctions(), since domain calculation and determining the
+  // MAs of an instruction are logically unrelated.
   if (scop->isEscaping(Inst))
     ensureValueWrite(Inst);
 }

diff  --git a/polly/lib/Analysis/ScopInfo.cpp b/polly/lib/Analysis/ScopInfo.cpp
index 166740b57407ad..6757f3ba057ea0 100644
--- a/polly/lib/Analysis/ScopInfo.cpp
+++ b/polly/lib/Analysis/ScopInfo.cpp
@@ -1739,9 +1739,33 @@ void Scop::removeStmts(function_ref<bool(ScopStmt &)> ShouldDelete,
 void Scop::removeStmtNotInDomainMap() {
   removeStmts([this](ScopStmt &Stmt) -> bool {
     isl::set Domain = DomainMap.lookup(Stmt.getEntryBlock());
-    if (Domain.is_null())
-      return true;
-    return Domain.is_empty();
+    if (!Domain.is_null() && !Domain.is_empty())
+      return false;
+
+    // This ScopStmt is being removed. For all the MAs belonging to this
+    // ScopStmt if it is 1) a scalar (MemoryKind::Value) 2) escaping 3) a
+    // must-write access 4) empty domain ScopStmt,  must therefore be preserved
+    // via SAI registration. This allows code generation to create the required
+    // merge PHIs and repair use sites after versioning has pruned the defining
+    // statement from optimized copy (due to its null/empty domain). Without
+    // this, Polly may generate invalid IR with broken dominance.
+    // ----------- TODO ----------
+    // If domain information is available before memory accesses are
+    // determined for a ScopStmt, then we can remove this SAI registration
+    // for escaping scalars whose containing ScopStmt's domain is actually
+    // invalid/null, and instead do this in
+    // ScopBuilder::buildEscapingDependences. A related TODO is also mentioned
+    // there.
+    for (MemoryAccess *MA : Stmt) {
+      if (!MA->isMustWrite() || !MA->isOriginalValueKind())
+        continue;
+      auto *Inst = dyn_cast_or_null<Instruction>(MA->getAccessValue());
+      if (!Inst || !contains(Inst) || !isEscaping(Inst))
+        continue;
+      getOrCreateScopArrayInfo(Inst, Inst->getType(), {}, MemoryKind::Value);
+    }
+
+    return true;
   });
 }
 

diff  --git a/polly/test/CodeGen/broken-dominance-escaping-scalar.ll b/polly/test/CodeGen/broken-dominance-escaping-scalar.ll
new file mode 100644
index 00000000000000..a473197b7ab123
--- /dev/null
+++ b/polly/test/CodeGen/broken-dominance-escaping-scalar.ll
@@ -0,0 +1,62 @@
+; RUN: opt %loadNPMPolly '-passes=polly-custom<codegen>' -S %s | FileCheck %s
+;
+; https://github.com/llvm/llvm-project/issues/206551
+;
+; Regression test for a verifier crash ("Instruction does not dominate all
+; uses!") triggered when an escaping scalar (%cond47.us.us.us) is defined in a
+; statement whose iteration domain is empty. The inner loop trip count depends
+; on a parameter that the runtime check restricts to a range where the loop
+; body never executes, making the defining statement's domain empty. Polly
+; prunes it, but the scalar still escapes via the outer-loop header PHI.
+;
+; Without the fix the generated merge block refers to a definition that only
+; exists in the original (unoptimized) copy, breaking dominance. With the fix
+; the region stays versioned and a proper .merge PHI is built, so we check that
+; the versioning infrastructure (.s2a alloca, .merge PHI, .final_reload) is
+; present and well-formed.
+;
+; CHECK: %cond47.us.us.us.s2a = alloca
+; CHECK: polly.merge_new_and_old:
+; CHECK: %cond47.us.us.us.merge = phi i32 [ %cond47.us.us.us.final_reload, %polly.exiting ], [ %cond47.us.us.us, %for.cond.cleanup6.us.us ]
+; CHECK: %cond47.us.us116.us = phi i32 [ 0, %entry ], [ %cond47.us.us.us.merge, %polly.merge_new_and_old ]
+; CHECK: polly.exiting:
+; CHECK: %cond47.us.us.us.final_reload = load i32, ptr %cond47.us.us.us.s2a
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define void @_Z1iiPA4_A4_iiPA4_A4_cS4_S1_(ptr %j, i32 %k, ptr %0) {
+entry:
+  %sext = shl i32 %k, 24
+  %conv10 = ashr i32 %sext, 24
+  %sub11 = add i32 %conv10, -127
+  %wide.trip.count = zext i32 %sub11 to i64
+  br label %for.cond4.preheader.us.us
+
+for.cond4.preheader.us.us:                        ; preds = %for.cond.cleanup6.us.us, %entry
+  %cond47.us.us116.us = phi i32 [ 0, %entry ], [ %cond47.us.us.us, %for.cond.cleanup6.us.us ]
+  br label %for.body7.us.us.us
+
+for.cond.cleanup6.us.us:                          ; preds = %for.cond9.for.cond.cleanup13_crit_edge.us.us.us
+  br label %for.cond4.preheader.us.us
+
+for.body7.us.us.us:                               ; preds = %for.cond9.for.cond.cleanup13_crit_edge.us.us.us, %for.cond4.preheader.us.us
+  br label %for.cond15.preheader.us.us.us
+
+cond.false.us.us.us.1:                            ; preds = %for.cond15.preheader.us.us.us
+  %cond47.us.us.us = select i1 false, i32 0, i32 0
+  store i32 0, ptr null, align 4
+  %indvars.iv.next = add nsw i64 %indvars.iv, 1
+  %exitcond.not = icmp eq i64 %indvars.iv.next, %wide.trip.count
+  br i1 %exitcond.not, label %for.cond9.for.cond.cleanup13_crit_edge.us.us.us, label %for.cond15.preheader.us.us.us
+
+for.cond15.preheader.us.us.us:                    ; preds = %cond.false.us.us.us.1, %for.body7.us.us.us
+  %indvars.iv = phi i64 [ %indvars.iv.next, %cond.false.us.us.us.1 ], [ 0, %for.body7.us.us.us ]
+  %cond478486.us.us.us = phi i32 [ 1, %cond.false.us.us.us.1 ], [ 0, %for.body7.us.us.us ]
+  %1 = load i32, ptr %0, align 4
+  store i32 0, ptr %j, align 8
+  br label %cond.false.us.us.us.1
+
+for.cond9.for.cond.cleanup13_crit_edge.us.us.us:  ; preds = %cond.false.us.us.us.1
+  br i1 true, label %for.cond.cleanup6.us.us, label %for.body7.us.us.us
+}


        


More information about the llvm-commits mailing list