[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