[llvm] [InferAddressSpaces] Fix null PHI operand when an incoming flat expression is not rewritten (PR #205749)

Fujun Han via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 30 16:48:14 PDT 2026


https://github.com/Peter9606 updated https://github.com/llvm/llvm-project/pull/205749

>From 8af6bf3b502221bd47de79356c1c4392eed7e11b Mon Sep 17 00:00:00 2001
From: Fujun Han <fujun.han at iluvatar.com>
Date: Thu, 25 Jun 2026 16:36:54 +0800
Subject: [PATCH] [InferAddressSpaces] Fix null PHI operand when an incoming
 flat expression is not rewritten

When inferring a PHI into a non-flat address space, an unrewritten flat
incoming instruction is replaced by a poison placeholder during cloning.
If the fixup loop cannot find a rewritten value, insert an addrspacecast
for the operand instead of storing a null incoming value.

Assisted-by: Cursor (claude-opus-4-8-thinking-high)
Signed-off-by: Fujun Han <fujun.han at iluvatar.com>
---
 .../Transforms/Scalar/InferAddressSpaces.cpp  |  39 ++++--
 .../AMDGPU/phi-null-operand.ll                | 132 ++++++++++++++++++
 2 files changed, 160 insertions(+), 11 deletions(-)
 create mode 100644 llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-null-operand.ll

diff --git a/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp b/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
index d11fa0e5c9ce0..754083dcd2834 100644
--- a/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
+++ b/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
@@ -766,6 +766,20 @@ static Value *phiNodeOperandWithNewAddressSpace(AddrSpaceCastInst *NewI,
   return NewI;
 }
 
+// Inserts an addrspacecast for an instruction operand, placing the cast so
+// that it dominates the user. For PHI users, delegates to
+// phiNodeOperandWithNewAddressSpace.
+static Value *insertAddrSpaceCastForUserOperand(Value *Operand, Type *NewPtrTy,
+                                                Instruction *User) {
+  auto *ASC = new AddrSpaceCastInst(Operand, NewPtrTy);
+  if (LLVM_UNLIKELY(User->getOpcode() == Instruction::PHI))
+    return phiNodeOperandWithNewAddressSpace(ASC, Operand);
+
+  ASC->insertBefore(User->getIterator());
+  ASC->setDebugLoc(User->getDebugLoc());
+  return ASC;
+}
+
 // A helper function for cloneInstructionWithNewAddressSpace. Returns the clone
 // of OperandUse.get() in the new address space. If the clone is not ready yet,
 // returns poison in the new address space as a placeholder.
@@ -790,14 +804,7 @@ static Value *operandWithNewAddressSpaceOrCreatePoison(
     // Insert an addrspacecast on that operand before the user.
     unsigned NewAS = I->second;
     Type *NewPtrTy = getPtrOrVecOfPtrsWithNewAS(Operand->getType(), NewAS);
-    auto *NewI = new AddrSpaceCastInst(Operand, NewPtrTy);
-
-    if (LLVM_UNLIKELY(Inst->getOpcode() == Instruction::PHI))
-      return phiNodeOperandWithNewAddressSpace(NewI, Operand);
-
-    NewI->insertBefore(Inst->getIterator());
-    NewI->setDebugLoc(Inst->getDebugLoc());
-    return NewI;
+    return insertAddrSpaceCastForUserOperand(Operand, NewPtrTy, Inst);
   }
 
   PoisonUsesToFix->push_back(&OperandUse);
@@ -1549,9 +1556,19 @@ bool InferAddressSpacesImpl::rewriteWithNewAddressSpaces(
 
     unsigned OperandNo = PoisonUse->getOperandNo();
     assert(isa<PoisonValue>(NewV->getOperand(OperandNo)));
-    WeakTrackingVH NewOp = ValueWithNewAddrSpace.lookup(PoisonUse->get());
-    assert(NewOp &&
-           "poison replacements in ValueWithNewAddrSpace shouldn't be null");
+    Value *NewOp = ValueWithNewAddrSpace.lookup(PoisonUse->get());
+    if (!NewOp) {
+      // The operand was never rewritten into the new address space (e.g. a flat
+      // address expression in a flat-only cycle that stays in the
+      // uninitialized address space). Only instructions reach here;
+      // operandWithNewAddressSpaceOrCreatePoison() handles Constant operands
+      // earlier. Materialize an explicit addrspacecast, mirroring the
+      // PredicatedAS path in that function.
+      Value *Orig = PoisonUse->get();
+      Type *NewPtrTy = NewV->getOperand(OperandNo)->getType();
+      NewOp = insertAddrSpaceCastForUserOperand(Orig, NewPtrTy,
+                                                cast<Instruction>(NewV));
+    }
     NewV->setOperand(OperandNo, NewOp);
   }
 
diff --git a/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-null-operand.ll b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-null-operand.ll
new file mode 100644
index 0000000000000..c36da4e9cd542
--- /dev/null
+++ b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-null-operand.ll
@@ -0,0 +1,132 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -S -passes=infer-address-spaces %s | FileCheck %s
+
+ at g = external global i8, align 1
+
+; Each function infers merge PHI %p into addrspace(1) because %arg.cast
+; represents an addrspace(1) pointer. The other incoming value %xn is a flat
+; getelementptr in a flat-only cycle whose pointer operand is seeded with a
+; different flat address (poison, null, a global, or a constant expression).
+; %xn stays in the uninitialized address space and is never rewritten, so
+; cloning the PHI records a poison placeholder and the fixup loop materializes
+; an addrspacecast on the %loop -> %merge incoming edge.
+
+; Incoming instruction (%xn): flat-only cycle seeded by poison.
+define void @phi_incoming_instruction(ptr addrspace(1) %arg, i1 %c) {
+; CHECK-LABEL: @phi_incoming_instruction(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[MERGE:%.*]], label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[X:%.*]] = phi ptr [ poison, [[ENTRY:%.*]] ], [ [[XN:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[XN]] = getelementptr i8, ptr [[X]], i64 1
+; CHECK-NEXT:    [[TMP0:%.*]] = addrspacecast ptr [[XN]] to ptr addrspace(1)
+; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[MERGE]]
+; CHECK:       merge:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr addrspace(1) [ [[ARG:%.*]], [[ENTRY]] ], [ [[TMP0]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(1) [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %arg.cast = addrspacecast ptr addrspace(1) %arg to ptr
+  br i1 %c, label %merge, label %loop
+
+loop:
+  %x = phi ptr [ poison, %entry ], [ %xn, %loop ]
+  %xn = getelementptr i8, ptr %x, i64 1
+  br i1 %c, label %loop, label %merge
+
+merge:
+  %p = phi ptr [ %arg.cast, %entry ], [ %xn, %loop ]
+  %v = load i32, ptr %p
+  ret void
+}
+
+; Incoming instruction (%xn): flat-only cycle seeded by null.
+define void @phi_incoming_null(ptr addrspace(1) %arg, i1 %c) {
+; CHECK-LABEL: @phi_incoming_null(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[MERGE:%.*]], label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[X:%.*]] = phi ptr [ null, [[ENTRY:%.*]] ], [ [[XN:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[XN]] = getelementptr i8, ptr [[X]], i64 1
+; CHECK-NEXT:    [[TMP0:%.*]] = addrspacecast ptr [[XN]] to ptr addrspace(1)
+; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[MERGE]]
+; CHECK:       merge:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr addrspace(1) [ [[ARG:%.*]], [[ENTRY]] ], [ [[TMP0]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(1) [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %arg.cast = addrspacecast ptr addrspace(1) %arg to ptr
+  br i1 %c, label %merge, label %loop
+
+loop:
+  %x = phi ptr [ null, %entry ], [ %xn, %loop ]
+  %xn = getelementptr i8, ptr %x, i64 1
+  br i1 %c, label %loop, label %merge
+
+merge:
+  %p = phi ptr [ %arg.cast, %entry ], [ %xn, %loop ]
+  %v = load i32, ptr %p
+  ret void
+}
+
+; Incoming instruction (%xn): flat-only cycle seeded by a global address.
+define void @phi_incoming_global(ptr addrspace(1) %arg, i1 %c) {
+; CHECK-LABEL: @phi_incoming_global(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[MERGE:%.*]], label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[X:%.*]] = phi ptr [ @g, [[ENTRY:%.*]] ], [ [[XN:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[XN]] = getelementptr i8, ptr [[X]], i64 1
+; CHECK-NEXT:    [[TMP0:%.*]] = addrspacecast ptr [[XN]] to ptr addrspace(1)
+; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[MERGE]]
+; CHECK:       merge:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr addrspace(1) [ [[ARG:%.*]], [[ENTRY]] ], [ [[TMP0]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i8, ptr addrspace(1) [[P]], align 1
+; CHECK-NEXT:    ret void
+;
+entry:
+  %arg.cast = addrspacecast ptr addrspace(1) %arg to ptr
+  br i1 %c, label %merge, label %loop
+
+loop:
+  %x = phi ptr [ @g, %entry ], [ %xn, %loop ]
+  %xn = getelementptr i8, ptr %x, i64 1
+  br i1 %c, label %loop, label %merge
+
+merge:
+  %p = phi ptr [ %arg.cast, %entry ], [ %xn, %loop ]
+  %v = load i8, ptr %p
+  ret void
+}
+
+; Incoming instruction (%xn): flat-only cycle seeded by a constant expr.
+define void @phi_incoming_constant_expr(ptr addrspace(1) %arg, i1 %c) {
+; CHECK-LABEL: @phi_incoming_constant_expr(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[MERGE:%.*]], label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[X:%.*]] = phi ptr [ inttoptr (i64 4096 to ptr), [[ENTRY:%.*]] ], [ [[XN:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[XN]] = getelementptr i8, ptr [[X]], i64 1
+; CHECK-NEXT:    [[TMP0:%.*]] = addrspacecast ptr [[XN]] to ptr addrspace(1)
+; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[MERGE]]
+; CHECK:       merge:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr addrspace(1) [ [[ARG:%.*]], [[ENTRY]] ], [ [[TMP0]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(1) [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %arg.cast = addrspacecast ptr addrspace(1) %arg to ptr
+  br i1 %c, label %merge, label %loop
+
+loop:
+  %x = phi ptr [ inttoptr (i64 4096 to ptr), %entry ], [ %xn, %loop ]
+  %xn = getelementptr i8, ptr %x, i64 1
+  br i1 %c, label %loop, label %merge
+
+merge:
+  %p = phi ptr [ %arg.cast, %entry ], [ %xn, %loop ]
+  %v = load i32, ptr %p
+  ret void
+}



More information about the llvm-commits mailing list