[llvm] 9a3e91d - [SPIR-V] Preserve pointer-to-pointer element type for T*& parameters (#203113)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 13 01:23:29 PDT 2026


Author: Dmitry Sidorov
Date: 2026-06-13T10:23:25+02:00
New Revision: 9a3e91daa7798e8695a68b01a8ff9b76e54f8524

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

LOG: [SPIR-V] Preserve pointer-to-pointer element type for T*& parameters (#203113)

SPIR-V backend was collapsing T*& reference parameters down to T*,
dropping a level of indirection during element-type deduction. This
patch keeps the pointer-to-pointer level intact, fixing it both within a
function (a T** reloaded from its alloca) and across calls via
cross-function parameter propagation.

Fixes HeCBench/topk on HIP.

Added: 
    llvm/test/CodeGen/SPIRV/pointers/type-deduce-ptr-to-ptr-arg-alloca-store.ll

Modified: 
    llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index eec733b17cf63..89b6dad3d5239 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -545,6 +545,17 @@ static bool IsKernelArgInt8(Function *F, StoreInst *SI) {
          isa<Argument>(SI->getValueOperand());
 }
 
+// A pointer-typed local holds a pointer, so its deduced pointee must stay a
+// pointer.
+static bool tracesToPointerAlloca(Value *V) {
+  using namespace PatternMatch;
+  V = V->stripPointerCasts();
+  if (auto *AI = dyn_cast<AllocaInst>(V))
+    return isUntypedPointerTy(AI->getAllocatedType());
+  return match(
+      V, m_AnyIntrinsic<Intrinsic::spv_alloca, Intrinsic::spv_alloca_array>());
+}
+
 // Maybe restore original function return type.
 static inline Type *restoreMutatedType(SPIRVGlobalRegistry *GR, Instruction *I,
                                        Type *Ty) {
@@ -1402,8 +1413,18 @@ void SPIRVEmitIntrinsics::deduceOperandElementType(
                                  StructuredGEPInst::getPointerOperandIndex()));
   } else if (auto *Ref = dyn_cast<LoadInst>(I)) {
     KnownElemTy = I->getType();
-    if (isUntypedPointerTy(KnownElemTy))
-      return;
+    if (isUntypedPointerTy(KnownElemTy)) {
+      // A T** loaded back from its alloca comes out opaque, dropping type info.
+      // When the load is a pointer-to-pointer, type the alloca as that pointer.
+      Type *LoadedElemTy = GR->findDeducedElementType(I);
+      if (!LoadedElemTy || !isPointerTyOrWrapper(LoadedElemTy))
+        return;
+      Value *Root = Ref->getPointerOperand()->stripPointerCasts();
+      if (!isa<AllocaInst>(Root))
+        return;
+      KnownElemTy = getTypedPointerWrapper(LoadedElemTy,
+                                           getPointerAddressSpace(KnownElemTy));
+    }
     Type *PointeeTy = GR->findDeducedElementType(Ref->getPointerOperand());
     if (PointeeTy && !isUntypedPointerTy(PointeeTy))
       return;
@@ -1508,7 +1529,12 @@ void SPIRVEmitIntrinsics::deduceOperandElementType(
       continue;
     Value *OpTyVal = getNormalizedPoisonValue(KnownElemTy);
     Type *OpTy = Op->getType();
-    if (Op->hasUseList() &&
+    // Do not let a non-pointer element type clobber an already-deduced pointer
+    // pointee.
+    bool WouldClobberPtrWithNonPtr = Ty && isPointerTyOrWrapper(Ty) &&
+                                     !isPointerTyOrWrapper(KnownElemTy) &&
+                                     tracesToPointerAlloca(Op);
+    if (Op->hasUseList() && !WouldClobberPtrWithNonPtr &&
         (!Ty || AskTy || isUntypedPointerTy(Ty) || isTodoType(Op))) {
       Type *PrevElemTy = GR->findDeducedElementType(Op);
       GR->addDeducedElementType(Op, normalizeType(KnownElemTy));
@@ -2129,6 +2155,15 @@ void SPIRVEmitIntrinsics::replacePointerOperandWithPtrCast(
     }
   }
 
+  // Never replace an already-deduced pointer pointee with a non-pointer one.
+  // The conflicting use comes from a mis-deduced expected type. Leave the
+  // operand untouched rather than emitting a ptrcast that re-introduces
+  // the collapsed type at the use site.
+  if (PointerElemTy && isPointerTyOrWrapper(PointerElemTy) &&
+      !isPointerTyOrWrapper(ExpectedElementType) &&
+      tracesToPointerAlloca(Pointer))
+    return;
+
   if (isa<Instruction>(Pointer) || isa<Argument>(Pointer)) {
     if (FirstPtrCastOrAssignPtrType) {
       // If this would be the first spv_ptrcast, do not emit spv_ptrcast and

diff  --git a/llvm/test/CodeGen/SPIRV/pointers/type-deduce-ptr-to-ptr-arg-alloca-store.ll b/llvm/test/CodeGen/SPIRV/pointers/type-deduce-ptr-to-ptr-arg-alloca-store.ll
new file mode 100644
index 0000000000000..183861a8e88db
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/type-deduce-ptr-to-ptr-arg-alloca-store.ll
@@ -0,0 +1,27 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; A T*& parameter stored to an alloca must remain be a pointer-to-a-pointer.
+
+; CHECK-DAG: %[[#F32:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#PtrF:]] = OpTypePointer Generic %[[#F32]]
+; CHECK-DAG: %[[#PtrPtrF:]] = OpTypePointer Generic %[[#PtrF]]
+; CHECK-DAG: %[[#FnPtrPtrF:]] = OpTypePointer Function %[[#PtrPtrF]]
+
+; CHECK: OpFunction
+; CHECK: %[[#Var:]] = OpVariable %[[#FnPtrPtrF]] Function
+; CHECK: %[[#Load:]] = OpLoad %[[#PtrPtrF]] %[[#Var]]
+; CHECK: OpStore %[[#Load]] %[[#]]
+
+define spir_func void @set_buf(ptr addrspace(4) %in, ptr addrspace(4) %in_buf) {
+entry:
+  %in.addr = alloca ptr addrspace(4), align 8
+  %in_buf.addr = alloca ptr addrspace(4), align 8
+  store ptr addrspace(4) %in, ptr %in.addr, align 8
+  store ptr addrspace(4) %in_buf, ptr %in_buf.addr, align 8
+  %0 = load ptr addrspace(4), ptr %in.addr, align 8
+  %g = getelementptr inbounds float, ptr addrspace(4) %0, i64 1
+  %1 = load ptr addrspace(4), ptr %in_buf.addr, align 8
+  store ptr addrspace(4) %0, ptr addrspace(4) %1, align 8
+  ret void
+}


        


More information about the llvm-commits mailing list