Skip to content

Conversation

@wenju-he
Copy link
Contributor

Previously undef pointer operand is only supported for select inst, where undef in generic AS behaves like take the other side.

This PR extends the support to other instructions, e.g. phi inst. Defer joining and inferring constant pointer operand until all other operand AS states considered.

Previously undef pointer operand is only supported for select inst,
where undef in generic AS behaves like `take the other side`.

This PR extends the support to other instructions, e.g. phi inst.
Defer joining and inferring constant pointer operand until all other
operand AS states considered.
@llvmbot
Copy link
Member

llvmbot commented Sep 18, 2025

@llvm/pr-subscribers-backend-amdgpu

Author: Wenju He (wenju-he)

Changes

Previously undef pointer operand is only supported for select inst, where undef in generic AS behaves like take the other side.

This PR extends the support to other instructions, e.g. phi inst. Defer joining and inferring constant pointer operand until all other operand AS states considered.


Full diff: https://github.com/llvm/llvm-project/pull/159548.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp (+39-61)
  • (added) llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll (+36)
diff --git a/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp b/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
index a097d338a42ca..fffed5c7ce9aa 100644
--- a/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
+++ b/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
@@ -1002,70 +1002,48 @@ bool InferAddressSpacesImpl::updateAddressSpace(
   // isAddressExpression should guarantee that V is an operator or an argument.
   assert(isa<Operator>(V) || isa<Argument>(V));
 
-  if (isa<Operator>(V) &&
-      cast<Operator>(V).getOpcode() == Instruction::Select) {
-    const Operator &Op = cast<Operator>(V);
-    Value *Src0 = Op.getOperand(1);
-    Value *Src1 = Op.getOperand(2);
-
-    auto I = InferredAddrSpace.find(Src0);
-    unsigned Src0AS = (I != InferredAddrSpace.end())
-                          ? I->second
-                          : Src0->getType()->getPointerAddressSpace();
-
-    auto J = InferredAddrSpace.find(Src1);
-    unsigned Src1AS = (J != InferredAddrSpace.end())
-                          ? J->second
-                          : Src1->getType()->getPointerAddressSpace();
-
-    auto *C0 = dyn_cast<Constant>(Src0);
-    auto *C1 = dyn_cast<Constant>(Src1);
-
-    // If one of the inputs is a constant, we may be able to do a constant
-    // addrspacecast of it. Defer inferring the address space until the input
-    // address space is known.
-    if ((C1 && Src0AS == UninitializedAddressSpace) ||
-        (C0 && Src1AS == UninitializedAddressSpace))
-      return false;
-
-    if (C0 && isSafeToCastConstAddrSpace(C0, Src1AS))
-      NewAS = Src1AS;
-    else if (C1 && isSafeToCastConstAddrSpace(C1, Src0AS))
-      NewAS = Src0AS;
-    else
-      NewAS = joinAddressSpaces(Src0AS, Src1AS);
+  unsigned AS = TTI->getAssumedAddrSpace(&V);
+  if (AS != UninitializedAddressSpace) {
+    // Use the assumed address space directly.
+    NewAS = AS;
   } else {
-    unsigned AS = TTI->getAssumedAddrSpace(&V);
-    if (AS != UninitializedAddressSpace) {
-      // Use the assumed address space directly.
-      NewAS = AS;
-    } else {
-      // Otherwise, infer the address space from its pointer operands.
-      for (Value *PtrOperand : getPointerOperands(V, *DL, TTI)) {
-        auto I = InferredAddrSpace.find(PtrOperand);
-        unsigned OperandAS;
-        if (I == InferredAddrSpace.end()) {
-          OperandAS = PtrOperand->getType()->getPointerAddressSpace();
-          if (OperandAS == FlatAddrSpace) {
-            // Check AC for assumption dominating V.
-            unsigned AS = getPredicatedAddrSpace(*PtrOperand, &V);
-            if (AS != UninitializedAddressSpace) {
-              LLVM_DEBUG(dbgs()
-                         << "  deduce operand AS from the predicate addrspace "
-                         << AS << '\n');
-              OperandAS = AS;
-              // Record this use with the predicated AS.
-              PredicatedAS[std::make_pair(&V, PtrOperand)] = OperandAS;
-            }
+    // Otherwise, infer the address space from its pointer operands.
+    SmallVector<Constant *, 2> ConstantPtrOps;
+    for (Value *PtrOperand : getPointerOperands(V, *DL, TTI)) {
+      auto I = InferredAddrSpace.find(PtrOperand);
+      unsigned OperandAS;
+      if (I == InferredAddrSpace.end()) {
+        OperandAS = PtrOperand->getType()->getPointerAddressSpace();
+        if (auto *C = dyn_cast<Constant>(PtrOperand);
+            C && OperandAS == FlatAddrSpace) {
+          ConstantPtrOps.push_back(C);
+          continue;
+        }
+        if (OperandAS == FlatAddrSpace) {
+          // Check AC for assumption dominating V.
+          unsigned AS = getPredicatedAddrSpace(*PtrOperand, &V);
+          if (AS != UninitializedAddressSpace) {
+            LLVM_DEBUG(dbgs()
+                       << "  deduce operand AS from the predicate addrspace "
+                       << AS << '\n');
+            OperandAS = AS;
+            // Record this use with the predicated AS.
+            PredicatedAS[std::make_pair(&V, PtrOperand)] = OperandAS;
           }
-        } else
-          OperandAS = I->second;
+        }
+      } else
+        OperandAS = I->second;
 
-        // join(flat, *) = flat. So we can break if NewAS is already flat.
-        NewAS = joinAddressSpaces(NewAS, OperandAS);
-        if (NewAS == FlatAddrSpace)
-          break;
-      }
+      // join(flat, *) = flat. So we can break if NewAS is already flat.
+      NewAS = joinAddressSpaces(NewAS, OperandAS);
+      if (NewAS == FlatAddrSpace)
+        break;
+    }
+    if (NewAS != FlatAddrSpace && NewAS != UninitializedAddressSpace) {
+      if (any_of(ConstantPtrOps, [&](Constant *C) {
+            return !isSafeToCastConstAddrSpace(C, NewAS);
+          }))
+        NewAS = FlatAddrSpace;
     }
   }
 
diff --git a/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll
new file mode 100644
index 0000000000000..c1fd05ceb8a4e
--- /dev/null
+++ b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll
@@ -0,0 +1,36 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -S -passes=infer-address-spaces %s | FileCheck %s
+
+define void @phi_undef(ptr addrspace(1) %arg, <2 x ptr addrspace(1)> %arg1) {
+; CHECK-LABEL: @phi_undef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = call i1 @is_leader()
+; CHECK-NEXT:    br i1 [[TMP0]], label [[LEADER:%.*]], label [[MERGE:%.*]]
+; CHECK:       leader:
+; CHECK-NEXT:    br label [[MERGE]]
+; CHECK:       merge:
+; CHECK-NEXT:    [[I:%.*]] = phi ptr addrspace(1) [ [[ARG:%.*]], [[LEADER]] ], [ undef, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[I2:%.*]] = phi <2 x ptr addrspace(1)> [ [[ARG1:%.*]], [[LEADER]] ], [ undef, [[ENTRY]] ]
+; CHECK-NEXT:    [[TMP1:%.*]] = addrspacecast <2 x ptr addrspace(1)> [[I2]] to <2 x ptr>
+; CHECK-NEXT:    [[J:%.*]] = load i8, ptr addrspace(1) [[I]], align 1
+; CHECK-NEXT:    [[J1:%.*]] = icmp eq <2 x ptr> [[TMP1]], zeroinitializer
+; CHECK-NEXT:    ret void
+;
+entry:
+  %0 = call i1 @is_leader()
+  br i1 %0, label %leader, label %merge
+
+leader:
+  %cast = addrspacecast ptr addrspace(1) %arg to ptr
+  %cast1 = addrspacecast <2 x ptr addrspace(1)> %arg1 to <2 x ptr>
+  br label %merge
+
+merge:
+  %i = phi ptr [%cast, %leader], [undef, %entry]
+  %i1 = phi <2 x ptr> [%cast1, %leader], [undef, %entry]
+  %j = load i8, ptr %i, align 1
+  %j1 = icmp eq <2 x ptr> %i1, zeroinitializer
+  ret void
+}
+
+declare i1 @is_leader()

@llvmbot
Copy link
Member

llvmbot commented Sep 18, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Wenju He (wenju-he)

Changes

Previously undef pointer operand is only supported for select inst, where undef in generic AS behaves like take the other side.

This PR extends the support to other instructions, e.g. phi inst. Defer joining and inferring constant pointer operand until all other operand AS states considered.


Full diff: https://github.com/llvm/llvm-project/pull/159548.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp (+39-61)
  • (added) llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll (+36)
diff --git a/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp b/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
index a097d338a42ca..fffed5c7ce9aa 100644
--- a/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
+++ b/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp
@@ -1002,70 +1002,48 @@ bool InferAddressSpacesImpl::updateAddressSpace(
   // isAddressExpression should guarantee that V is an operator or an argument.
   assert(isa<Operator>(V) || isa<Argument>(V));
 
-  if (isa<Operator>(V) &&
-      cast<Operator>(V).getOpcode() == Instruction::Select) {
-    const Operator &Op = cast<Operator>(V);
-    Value *Src0 = Op.getOperand(1);
-    Value *Src1 = Op.getOperand(2);
-
-    auto I = InferredAddrSpace.find(Src0);
-    unsigned Src0AS = (I != InferredAddrSpace.end())
-                          ? I->second
-                          : Src0->getType()->getPointerAddressSpace();
-
-    auto J = InferredAddrSpace.find(Src1);
-    unsigned Src1AS = (J != InferredAddrSpace.end())
-                          ? J->second
-                          : Src1->getType()->getPointerAddressSpace();
-
-    auto *C0 = dyn_cast<Constant>(Src0);
-    auto *C1 = dyn_cast<Constant>(Src1);
-
-    // If one of the inputs is a constant, we may be able to do a constant
-    // addrspacecast of it. Defer inferring the address space until the input
-    // address space is known.
-    if ((C1 && Src0AS == UninitializedAddressSpace) ||
-        (C0 && Src1AS == UninitializedAddressSpace))
-      return false;
-
-    if (C0 && isSafeToCastConstAddrSpace(C0, Src1AS))
-      NewAS = Src1AS;
-    else if (C1 && isSafeToCastConstAddrSpace(C1, Src0AS))
-      NewAS = Src0AS;
-    else
-      NewAS = joinAddressSpaces(Src0AS, Src1AS);
+  unsigned AS = TTI->getAssumedAddrSpace(&V);
+  if (AS != UninitializedAddressSpace) {
+    // Use the assumed address space directly.
+    NewAS = AS;
   } else {
-    unsigned AS = TTI->getAssumedAddrSpace(&V);
-    if (AS != UninitializedAddressSpace) {
-      // Use the assumed address space directly.
-      NewAS = AS;
-    } else {
-      // Otherwise, infer the address space from its pointer operands.
-      for (Value *PtrOperand : getPointerOperands(V, *DL, TTI)) {
-        auto I = InferredAddrSpace.find(PtrOperand);
-        unsigned OperandAS;
-        if (I == InferredAddrSpace.end()) {
-          OperandAS = PtrOperand->getType()->getPointerAddressSpace();
-          if (OperandAS == FlatAddrSpace) {
-            // Check AC for assumption dominating V.
-            unsigned AS = getPredicatedAddrSpace(*PtrOperand, &V);
-            if (AS != UninitializedAddressSpace) {
-              LLVM_DEBUG(dbgs()
-                         << "  deduce operand AS from the predicate addrspace "
-                         << AS << '\n');
-              OperandAS = AS;
-              // Record this use with the predicated AS.
-              PredicatedAS[std::make_pair(&V, PtrOperand)] = OperandAS;
-            }
+    // Otherwise, infer the address space from its pointer operands.
+    SmallVector<Constant *, 2> ConstantPtrOps;
+    for (Value *PtrOperand : getPointerOperands(V, *DL, TTI)) {
+      auto I = InferredAddrSpace.find(PtrOperand);
+      unsigned OperandAS;
+      if (I == InferredAddrSpace.end()) {
+        OperandAS = PtrOperand->getType()->getPointerAddressSpace();
+        if (auto *C = dyn_cast<Constant>(PtrOperand);
+            C && OperandAS == FlatAddrSpace) {
+          ConstantPtrOps.push_back(C);
+          continue;
+        }
+        if (OperandAS == FlatAddrSpace) {
+          // Check AC for assumption dominating V.
+          unsigned AS = getPredicatedAddrSpace(*PtrOperand, &V);
+          if (AS != UninitializedAddressSpace) {
+            LLVM_DEBUG(dbgs()
+                       << "  deduce operand AS from the predicate addrspace "
+                       << AS << '\n');
+            OperandAS = AS;
+            // Record this use with the predicated AS.
+            PredicatedAS[std::make_pair(&V, PtrOperand)] = OperandAS;
           }
-        } else
-          OperandAS = I->second;
+        }
+      } else
+        OperandAS = I->second;
 
-        // join(flat, *) = flat. So we can break if NewAS is already flat.
-        NewAS = joinAddressSpaces(NewAS, OperandAS);
-        if (NewAS == FlatAddrSpace)
-          break;
-      }
+      // join(flat, *) = flat. So we can break if NewAS is already flat.
+      NewAS = joinAddressSpaces(NewAS, OperandAS);
+      if (NewAS == FlatAddrSpace)
+        break;
+    }
+    if (NewAS != FlatAddrSpace && NewAS != UninitializedAddressSpace) {
+      if (any_of(ConstantPtrOps, [&](Constant *C) {
+            return !isSafeToCastConstAddrSpace(C, NewAS);
+          }))
+        NewAS = FlatAddrSpace;
     }
   }
 
diff --git a/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll
new file mode 100644
index 0000000000000..c1fd05ceb8a4e
--- /dev/null
+++ b/llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll
@@ -0,0 +1,36 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -S -passes=infer-address-spaces %s | FileCheck %s
+
+define void @phi_undef(ptr addrspace(1) %arg, <2 x ptr addrspace(1)> %arg1) {
+; CHECK-LABEL: @phi_undef(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = call i1 @is_leader()
+; CHECK-NEXT:    br i1 [[TMP0]], label [[LEADER:%.*]], label [[MERGE:%.*]]
+; CHECK:       leader:
+; CHECK-NEXT:    br label [[MERGE]]
+; CHECK:       merge:
+; CHECK-NEXT:    [[I:%.*]] = phi ptr addrspace(1) [ [[ARG:%.*]], [[LEADER]] ], [ undef, [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[I2:%.*]] = phi <2 x ptr addrspace(1)> [ [[ARG1:%.*]], [[LEADER]] ], [ undef, [[ENTRY]] ]
+; CHECK-NEXT:    [[TMP1:%.*]] = addrspacecast <2 x ptr addrspace(1)> [[I2]] to <2 x ptr>
+; CHECK-NEXT:    [[J:%.*]] = load i8, ptr addrspace(1) [[I]], align 1
+; CHECK-NEXT:    [[J1:%.*]] = icmp eq <2 x ptr> [[TMP1]], zeroinitializer
+; CHECK-NEXT:    ret void
+;
+entry:
+  %0 = call i1 @is_leader()
+  br i1 %0, label %leader, label %merge
+
+leader:
+  %cast = addrspacecast ptr addrspace(1) %arg to ptr
+  %cast1 = addrspacecast <2 x ptr addrspace(1)> %arg1 to <2 x ptr>
+  br label %merge
+
+merge:
+  %i = phi ptr [%cast, %leader], [undef, %entry]
+  %i1 = phi <2 x ptr> [%cast1, %leader], [undef, %entry]
+  %j = load i8, ptr %i, align 1
+  %j1 = icmp eq <2 x ptr> %i1, zeroinitializer
+  ret void
+}
+
+declare i1 @is_leader()

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR extends the InferAddressSpaces pass to handle undef pointer operands in phi instructions, similar to existing support for select instructions. The change defers joining and inferring constant pointer operand address spaces until all other operand address space states are considered.

  • Removes the special-case handling for select instructions only
  • Adds generic handling for constant pointer operands in the flat address space across all instruction types
  • Defers constant address space casting decisions until non-constant operands are processed

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp Refactors address space inference to handle constant operands generically instead of select-specific handling
llvm/test/Transforms/InferAddressSpaces/AMDGPU/phi-undef.ll Adds test case verifying phi instructions with undef operands are properly handled

Comment on lines +1017 to +1018
if (auto *C = dyn_cast<Constant>(PtrOperand);
C && OperandAS == FlatAddrSpace) {
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using a more conventional if statement instead of the compound condition in the declaration. The current syntax, while valid, may reduce readability and is less commonly used in LLVM codebase.

Suggested change
if (auto *C = dyn_cast<Constant>(PtrOperand);
C && OperandAS == FlatAddrSpace) {
auto *C = dyn_cast<Constant>(PtrOperand);
if (C && OperandAS == FlatAddrSpace) {

Copilot uses AI. Check for mistakes.
break;
}
if (NewAS != FlatAddrSpace && NewAS != UninitializedAddressSpace) {
if (any_of(ConstantPtrOps, [&](Constant *C) {
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lambda capture [&] captures all variables by reference which may be excessive. Consider capturing only the specific variable needed: [NewAS] since that's the only variable used in the lambda.

Suggested change
if (any_of(ConstantPtrOps, [&](Constant *C) {
if (any_of(ConstantPtrOps, [NewAS](Constant *C) {

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on not using reference capture

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on not using reference capture

this is also captured:

llvm-project/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp:1045:21: error: 'this' cannot be implicitly captured in this context
 1045 |             return !isSafeToCastConstAddrSpace(C, NewAS);
      |                     ^
llvm-project/llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp:1044:40: note: explicitly capture 'this'
 1044 |       if (any_of(ConstantPtrOps, [NewAS](Constant *C) {
      |                                        ^
      |                                        , this

@github-actions
Copy link

github-actions bot commented Sep 18, 2025

✅ With the latest revision this PR passed the undef deprecator.

; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -S -passes=infer-address-spaces %s | FileCheck %s

define void @phi_undef(ptr addrspace(1) %arg, <2 x ptr addrspace(1)> %arg1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also test poison

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also test poison

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can share the same test file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall I delete the undef test? pre-commit testing fails with error error: some formatters failed: undef deprecator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can share the same test file

merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I suppose it's not adding that much. Given that we are only rewriting the undefs to a different type, this transform should be undef safe anyway (not entirely sure that is true if we're making use of assume calls)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I suppose it's not adding that much. Given that we are only rewriting the undefs to a different type, this transform should be undef safe anyway (not entirely sure that is true if we're making use of assume calls)

done, removed undef test in 9c0780f and keep only poison test.

; CHECK-NEXT: ret void
;
entry:
%0 = call i1 @is_leader()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use named values

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use named values

done

@wenju-he wenju-he requested a review from arsenm September 18, 2025 10:49
@wenju-he wenju-he merged commit f7629f5 into llvm:main Sep 19, 2025
9 checks passed
@wenju-he wenju-he deleted the InferAddressSpaces-undef-phi branch September 19, 2025 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants