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

Shikhar Jain via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 31 08:37:11 PDT 2026


https://github.com/ShikharJ-Corp created https://github.com/llvm/llvm-project/pull/220008

…poison

In a versioned SCoP, Polly emits an optimized copy and the original copy joined at polly.merge_new_and_old. When an escaping scalar is defined in a statement whose iteration domain is empty in the optimized range, buildScop prunes that statement in removeStmtNotInDomainMap. This destroys the scalar's escaping MemoryKind::Value MUST_WRITE access, so it never receives a Value ScopArrayInfo. Code generation then leaves the merge PHI referring to the raw value, which is only defined in the original copy and does not dominate the merge edge, tripping the verifier.

Fix: before pruning, give such an escaping scalar a Value ScopArrayInfo so the existing escape machinery runs. This is done by simply registering the SAI of escpaing scalar within  ScopBuilder::buildEscapingDependences. Code generation then allocates a .s2a slot and builds a proper .merge PHI whose optimized edge reloads that slot. Because the defining statement is never emitted on the optimized path, the slot is never stored and the reload is poison. This is sound: the poison only reaches a live use on the exact parameter ranges where the original program was already undefined at that point, so no new undefined behavior is introduced.

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

Fixes #206551

>From 61784f074dc5e1d7dde9053a238ef52ddd19ee23 Mon Sep 17 00:00:00 2001
From: ShikharJain <shikharj at qti.qualcomm.com>
Date: Mon, 31 Aug 2026 08:28:29 -0700
Subject: [PATCH] [Polly] Materialize escaping scalars from empty-domain
 statements as poison In a versioned SCoP, Polly emits an optimized copy and
 the original copy joined at polly.merge_new_and_old. When an escaping scalar
 is defined in a statement whose iteration domain is empty in the optimized
 range, buildScop prunes that statement in removeStmtNotInDomainMap. This
 destroys the scalar's escaping MemoryKind::Value MUST_WRITE access, so it
 never receives a Value ScopArrayInfo. Code generation then leaves the merge
 PHI referring to the raw value, which is only defined in the original copy
 and does not dominate the merge edge, tripping the verifier.

Fix: before pruning, give such an escaping scalar a Value ScopArrayInfo so the
existing escape machinery runs. This is done by simply registering the SAI
of escpaing scalar within  ScopBuilder::buildEscapingDependences.
Code generation then allocates a .s2a slot and builds a proper .merge PHI
whose optimized edge reloads that slot. Because the defining statement is never
emitted on the optimized path, the slot is never stored and the reload is poison.
This is sound: the poison only reaches a live use on the exact parameter ranges
where the original program was already undefined at that point, so no new
undefined behavior is introduced.

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

Fixes #206551
---
 polly/lib/Analysis/ScopBuilder.cpp            |  8 ++-
 .../broken-dominance-escaping-scalar.ll       | 62 +++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)
 create mode 100644 polly/test/CodeGen/broken-dominance-escaping-scalar.ll

diff --git a/polly/lib/Analysis/ScopBuilder.cpp b/polly/lib/Analysis/ScopBuilder.cpp
index 762a930dfea6f..6f2c6171fef78 100644
--- a/polly/lib/Analysis/ScopBuilder.cpp
+++ b/polly/lib/Analysis/ScopBuilder.cpp
@@ -1328,8 +1328,14 @@ 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.
-  if (scop->isEscaping(Inst))
+  // The ensured write lives on a ScopStmt that removeStmtNotInDomainMap() may
+  // delete when its domain is empty, dropping the escaping value's array; pre-
+  // registering the SAI keeps it alive for code generation.
+  if (scop->isEscaping(Inst)) {
     ensureValueWrite(Inst);
+    scop->getOrCreateScopArrayInfo(Inst, Inst->getType(), {},
+                                   MemoryKind::Value);
+  }
 }
 
 void ScopBuilder::addRecordedAssumptions() {
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 0000000000000..a473197b7ab12
--- /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