[llvm] [SPIRV] Legalize byte-buffer reinterpretation ptrcasts (#192523) (PR #212999)
Tim Corringham via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 4 07:47:26 PDT 2026
https://github.com/tcorringham updated https://github.com/llvm/llvm-project/pull/212999
>From c7cae9554aa3819bcc9dcf97d6497e4a6ffaacff Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 30 Jul 2026 11:25:37 +0100
Subject: [PATCH 1/6] [HLSL][SPIRV] Legalize reinterpretation ptrcasts for
byte-addressable buffers
When SPIR-V emit-intrinsics inserts an i8->T spv_ptrcast for typed
loads/stores through byte-addressable resource pointers, legalize the
access directly on the original pointer instead of asserting.
Fixes Clang/Vulkan compilation of RWByteAddressBuffer and local resource
array tests (issue #192523).
Co-authored-by: Cursor
---
.../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 71 ++++++++++++++++---
.../SPIRV/passes/SPIRVLegalizePointerCast.ll | 31 ++++++++
.../pointers/byte-address-buffer-store.ll | 17 +++++
3 files changed, 111 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 847eda7880c03..c332666bc86fb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -222,15 +222,59 @@ class SPIRVLegalizePointerCastImpl {
return std::make_pair(GEP, CurrentTy);
}
+ // Returns the pointer operand of a load/store that uses a ptrcast.
+ static Value *getCastedPointerOperand(Instruction *MemI) {
+ if (auto *LI = dyn_cast<LoadInst>(MemI))
+ return LI->getPointerOperand();
+ if (auto *SI = dyn_cast<StoreInst>(MemI))
+ return SI->getPointerOperand();
+ if (auto *II = dyn_cast<IntrinsicInst>(MemI))
+ if (II->getIntrinsicID() == Intrinsic::spv_store)
+ return II->getArgOperand(1);
+ llvm_unreachable("Unexpected memory instruction being legalized.");
+ }
+
+ // Legalizes a reinterpretation ptrcast: the casted pointer's pointee type
+ // matches the access type but differs from the original pointer layout (e.g.
+ // byte-addressable i8 storage accessed as i32).
+ template <typename MemInstTy>
+ bool tryDirectReinterpretAccess(IRBuilder<> &B, Type *AccessTy,
+ Value *OriginalPtr, Value *CastedPtr,
+ Align Alignment, bool IsStore,
+ Value *StoreSrc, MemInstTy *BadMem) {
+ Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
+ if (!CastedElemTy || CastedElemTy != AccessTy)
+ return false;
+
+ GR->buildAssignPtr(B, CastedElemTy, OriginalPtr);
+ if (IsStore) {
+ StoreInst *SI = B.CreateStore(StoreSrc, OriginalPtr);
+ SI->setAlignment(Alignment);
+ } else {
+ LoadInst *LI = B.CreateLoad(AccessTy, OriginalPtr);
+ LI->setAlignment(Alignment);
+ buildAssignType(B, AccessTy, LI);
+ GR->replaceAllUsesWith(BadMem, LI, /* DeleteOld= */ true);
+ DeadInstructions.push_back(BadMem);
+ }
+ return true;
+ }
+
// Builds a legalized load from a pointer, drilling down through
// memory layouts to find a compatible type. Load flags will be
// copied from |BadLoad|, which should be the load being legalized.
Value *buildLegalizedLoad(IRBuilder<> &B, Type *ElementType, Value *Source,
- LoadInst *BadLoad) {
+ LoadInst *BadLoad, Value *CastedPtr) {
auto ResultOpt = getPointerToFirstCompatibleType(
B, Source, BadLoad->getPointerOperandType(), ElementType, false);
- assert(ResultOpt && "Failed to load from aggregate: "
- "Could not find compatible memory layout.");
+ if (!ResultOpt) {
+ if (tryDirectReinterpretAccess(
+ B, ElementType, Source, CastedPtr, BadLoad->getAlign(),
+ /*IsStore=*/false, /*StoreSrc=*/nullptr, BadLoad))
+ return nullptr;
+ llvm_unreachable("Failed to load from aggregate: "
+ "Could not find compatible memory layout.");
+ }
auto [GEP, CurrentTy] = *ResultOpt;
auto *SAT = dyn_cast<ArrayType>(CurrentTy);
@@ -415,7 +459,10 @@ class SPIRVLegalizePointerCastImpl {
Type *ToTy = GR->findDeducedElementType(CastedOperand);
B.SetInsertPoint(LI);
- Value *Output = buildLegalizedLoad(B, ToTy, OriginalOperand, LI);
+ Value *Output =
+ buildLegalizedLoad(B, ToTy, OriginalOperand, LI, CastedOperand);
+ if (!Output)
+ return;
GR->replaceAllUsesWith(LI, Output, /* DeleteOld= */ true);
DeadInstructions.push_back(LI);
@@ -513,11 +560,18 @@ class SPIRVLegalizePointerCastImpl {
// Builds a legalized store to a pointer, drilling down through
// memory layouts to find a compatible type.
void buildLegalizedStore(IRBuilder<> &B, Value *Src, Value *Dst,
- Align Alignment) {
+ Align Alignment, Value *CastedPtr,
+ Instruction *BadStore) {
auto ResultOpt = getPointerToFirstCompatibleType(B, Dst, Dst->getType(),
Src->getType(), true);
- assert(ResultOpt && "Failed to store to aggregate: "
- "Could not find compatible memory layout.");
+ if (!ResultOpt) {
+ if (tryDirectReinterpretAccess(B, Src->getType(), Dst, CastedPtr,
+ Alignment,
+ /*IsStore=*/true, Src, BadStore))
+ return;
+ llvm_unreachable("Failed to store to aggregate: "
+ "Could not find compatible memory layout.");
+ }
auto [GEP, CurrentTy] = *ResultOpt;
auto *DAT = dyn_cast<ArrayType>(CurrentTy);
@@ -552,7 +606,8 @@ class SPIRVLegalizePointerCastImpl {
void transformStore(IRBuilder<> &B, Instruction *BadStore, Value *Src,
Value *Dst, Align Alignment) {
B.SetInsertPoint(BadStore);
- buildLegalizedStore(B, Src, Dst, Alignment);
+ buildLegalizedStore(B, Src, Dst, Alignment,
+ getCastedPointerOperand(BadStore), BadStore);
DeadInstructions.push_back(BadStore);
}
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index a03dc58c5a5b6..4b988807d0062 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -76,3 +76,34 @@ entry:
}
attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+declare target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32, i32, i32, i32, ptr)
+declare ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0), i32)
+
+; Byte-addressable VulkanBuffer getpointer is typed as i8, but typed
+; scalar loads/stores add an i8->T spv_ptrcast. Legalize that by storing
+; directly through the getpointer result.
+
+define spir_func void @byteBufferStore() #0 {
+; CHECK-LABEL: define spir_func void @byteBufferStore(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: store i32 42, ptr addrspace(11)
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+ store i32 42, ptr addrspace(11) %ptr, align 4
+ ret void
+}
+
+define spir_func void @byteBufferLoad() #0 {
+; CHECK-LABEL: define spir_func void @byteBufferLoad(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load i32, ptr addrspace(11)
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+ %val = load i32, ptr addrspace(11) %ptr, align 4
+ ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll
new file mode 100644
index 0000000000000..6eaba6f548619
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll
@@ -0,0 +1,17 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: OpAccessChain
+; CHECK: OpStore
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+define void @main() local_unnamed_addr #0 {
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %ptr = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+ store i32 42, ptr addrspace(11) %ptr, align 4
+ ret void
+}
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
>From e2d509caa4588156b34bd68ee12405b11fe78810 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 30 Jul 2026 12:52:34 +0100
Subject: [PATCH 2/6] Extend test for issue #192523
Extended the test coverage to include the same def-use chain as seen in
the failure case reported in #192523.
---
.../SPIRV/passes/SPIRVLegalizePointerCast.ll | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 4b988807d0062..4a34d82ddd812 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -107,3 +107,22 @@ entry:
%val = load i32, ptr addrspace(11) %ptr, align 4
ret void
}
+
+ at slot = internal global target("spirv.VulkanBuffer", [0 x i8], 12, 0) poison, align 8
+
+; Regression for issue #192523: handle flows through memory before getpointer
+; (as in local RWByteAddressBuffer arrays), then typed store hits i8->i32 ptrcast.
+
+define spir_func void @byteBufferStoreViaLoadedHandle() #0 {
+; CHECK-LABEL: define spir_func void @byteBufferStoreViaLoadedHandle(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot
+; CHECK: store i32 42, ptr addrspace(11)
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ store target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, ptr @slot, align 8
+ %loaded = load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot, align 8
+ %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %loaded, i32 0)
+ store i32 42, ptr addrspace(11) %ptr, align 4
+ ret void
+}
>From 0745d64caf161ff714eeeed51393e74eb9ced129 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 30 Jul 2026 13:19:06 +0100
Subject: [PATCH 3/6] [SPIRV] Clarify and extend byte-buffer ptrcast
legalization tests
Document that [0 x i8] is a storage-buffer layout type, not an HLSL surface
type. Drop unneeded HLSL function attributes from the byte-buffer cases and
add a loaded-handle load regression alongside the existing store path.
Co-authored-by: Cursor
---
.../SPIRV/passes/SPIRVLegalizePointerCast.ll | 39 ++++++++++++++-----
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 4a34d82ddd812..706b1ef2c4dcb 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -82,12 +82,17 @@ attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
declare target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32, i32, i32, i32, ptr)
declare ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0), i32)
-; Byte-addressable VulkanBuffer getpointer is typed as i8, but typed
-; scalar loads/stores add an i8->T spv_ptrcast. Legalize that by storing
-; directly through the getpointer result.
-
-define spir_func void @byteBufferStore() #0 {
-; CHECK-LABEL: define spir_func void @byteBufferStore(
+; Byte-addressable buffer tests model Clang's SPIR-V resource layout for HLSL
+; ByteAddressBuffer/RWByteAddressBuffer: the handle carries [0 x i8] as the
+; OpTypeRuntimeArray element type for storage-buffer blocks. That i8 names
+; byte-addressable layout in LLVM IR; it is not an HLSL surface type. Typed
+; HLSL accesses (e.g. Store(offset, uint)) lower to i32 load/store at a byte
+; offset in getpointer's index operand. emit-intrinsics tags getpointer as i8
+; from the layout, then inserts i8->T spv_ptrcast for the typed access;
+; legalize-pointer-cast removes the ptrcast and keeps the typed access.
+
+define void @byteBufferStore() {
+; CHECK-LABEL: define void @byteBufferStore(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
; CHECK: store i32 42, ptr addrspace(11)
entry:
@@ -97,8 +102,8 @@ entry:
ret void
}
-define spir_func void @byteBufferLoad() #0 {
-; CHECK-LABEL: define spir_func void @byteBufferLoad(
+define void @byteBufferLoad() {
+; CHECK-LABEL: define void @byteBufferLoad(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
; CHECK: load i32, ptr addrspace(11)
entry:
@@ -113,8 +118,8 @@ entry:
; Regression for issue #192523: handle flows through memory before getpointer
; (as in local RWByteAddressBuffer arrays), then typed store hits i8->i32 ptrcast.
-define spir_func void @byteBufferStoreViaLoadedHandle() #0 {
-; CHECK-LABEL: define spir_func void @byteBufferStoreViaLoadedHandle(
+define void @byteBufferStoreViaLoadedHandle() {
+; CHECK-LABEL: define void @byteBufferStoreViaLoadedHandle(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
; CHECK: load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot
; CHECK: store i32 42, ptr addrspace(11)
@@ -126,3 +131,17 @@ entry:
store i32 42, ptr addrspace(11) %ptr, align 4
ret void
}
+
+define void @byteBufferLoadViaLoadedHandle() {
+; CHECK-LABEL: define void @byteBufferLoadViaLoadedHandle(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot
+; CHECK: load i32, ptr addrspace(11)
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ store target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, ptr @slot, align 8
+ %loaded = load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot, align 8
+ %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %loaded, i32 0)
+ %val = load i32, ptr addrspace(11) %ptr, align 4
+ ret void
+}
>From d7dacfb283b3564191840fd341c3a6db89593784 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 30 Jul 2026 17:15:29 +0100
Subject: [PATCH 4/6] [SPIRV] Legalize byte-buffer ptrcasts with per-byte
scalar and vector access
Emit valid uchar storage-buffer access chains by lowering typed scalar
and vector loads/stores to byte-wise i8 accesses via getpointer offsets,
instead of retagging pointers to the access type.
Co-authored-by: Cursor
---
.../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 170 ++++++++++++++++++
.../SPIRV/passes/SPIRVLegalizePointerCast.ll | 39 +++-
.../pointers/byte-address-buffer-load4.ll | 20 +++
3 files changed, 225 insertions(+), 4 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index c332666bc86fb..7a80959158ea8 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -234,6 +234,159 @@ class SPIRVLegalizePointerCastImpl {
llvm_unreachable("Unexpected memory instruction being legalized.");
}
+ static bool isByteLayoutType(Type *Ty, LLVMContext &Ctx) {
+ return Ty == Type::getInt8Ty(Ctx);
+ }
+
+ static IntrinsicInst *getResourceGetPointer(Value *Ptr) {
+ if (auto *II = dyn_cast<IntrinsicInst>(Ptr))
+ if (II->getIntrinsicID() == Intrinsic::spv_resource_getpointer)
+ return II;
+ return nullptr;
+ }
+
+ Value *gepByteOffset(IRBuilder<> &B, Value *BasePtr, Type *I8Ty,
+ unsigned ByteOffset) {
+ if (ByteOffset == 0)
+ return BasePtr;
+
+ IntrinsicInst *GetPtr = getResourceGetPointer(BasePtr);
+ assert(GetPtr &&
+ "byte layout pointer must come from spv.resource.getpointer");
+
+ Value *Handle = GetPtr->getOperand(0);
+ Value *BaseOffset = GetPtr->getOperand(1);
+ Value *NewOffset = BaseOffset;
+ if (ByteOffset != 0) {
+ if (auto *CI = dyn_cast<ConstantInt>(BaseOffset))
+ NewOffset = ConstantInt::get(CI->getType(),
+ CI->getZExtValue() + ByteOffset);
+ else
+ NewOffset = B.CreateAdd(
+ BaseOffset, ConstantInt::get(BaseOffset->getType(), ByteOffset));
+ }
+ SmallVector<OperandBundleDef, 1> OpBundles;
+ GetPtr->getOperandBundlesAsDefs(OpBundles);
+ CallInst *NewPtr = B.CreateCall(GetPtr->getFunctionType(),
+ GetPtr->getCalledOperand(),
+ {Handle, NewOffset}, OpBundles);
+ NewPtr->setAttributes(GetPtr->getAttributes());
+ NewPtr->setCallingConv(GetPtr->getCallingConv());
+ GR->buildAssignPtr(B, I8Ty, NewPtr);
+ return NewPtr;
+ }
+
+ Value *bitcastScalarToInt(IRBuilder<> &B, Value *Scalar) {
+ Type *Ty = Scalar->getType();
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ Type *IntTy =
+ IntegerType::get(B.getContext(), DL.getTypeStoreSizeInBits(Ty));
+ if (Ty == IntTy)
+ return Scalar;
+ if (Ty->isIntOrIntVectorTy())
+ return B.CreateIntCast(Scalar, IntTy, /*isSigned=*/false);
+ return B.CreateBitCast(Scalar, IntTy);
+ }
+
+ Value *bitcastIntToScalar(IRBuilder<> &B, Value *IntVal, Type *ScalarTy) {
+ if (IntVal->getType() == ScalarTy)
+ return IntVal;
+ if (ScalarTy->isIntOrIntVectorTy())
+ return B.CreateIntCast(IntVal, ScalarTy, /*isSigned=*/false);
+ return B.CreateBitCast(IntVal, ScalarTy);
+ }
+
+ void storeScalarToByteLayout(IRBuilder<> &B, Value *Src, Value *Dst,
+ Align Alignment) {
+ LLVMContext &Ctx = B.getContext();
+ Type *I8Ty = Type::getInt8Ty(Ctx);
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ Value *IntVal = bitcastScalarToInt(B, Src);
+ buildAssignType(B, IntVal->getType(), IntVal);
+ unsigned NumBytes = DL.getTypeStoreSize(Src->getType());
+
+ for (unsigned I = 0; I < NumBytes; ++I) {
+ Value *Shifted =
+ I == 0 ? IntVal
+ : B.CreateLShr(IntVal, ConstantInt::get(IntVal->getType(), 8 * I));
+ if (I != 0)
+ buildAssignType(B, IntVal->getType(), Shifted);
+ Value *Byte = B.CreateTrunc(Shifted, I8Ty);
+ buildAssignType(B, I8Ty, Byte);
+ Value *Ptr = gepByteOffset(B, Dst, I8Ty, I);
+ StoreInst *SI = B.CreateStore(Byte, Ptr);
+ SI->setAlignment(commonAlignment(Alignment, I));
+ }
+ }
+
+ void storeValueToByteLayout(IRBuilder<> &B, Value *Src, Value *Dst,
+ Align Alignment) {
+ if (auto *VT = dyn_cast<FixedVectorType>(Src->getType())) {
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ Type *I8Ty = Type::getInt8Ty(B.getContext());
+ unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());
+ for (unsigned I = 0; I < VT->getNumElements(); ++I) {
+ Value *Elem = extractScalarFromVector(B, Src, I);
+ Value *ElemPtr = gepByteOffset(B, Dst, I8Ty, I * ElemSize);
+ storeScalarToByteLayout(B, Elem, ElemPtr,
+ commonAlignment(Alignment, I * ElemSize));
+ }
+ return;
+ }
+ storeScalarToByteLayout(B, Src, Dst, Alignment);
+ }
+
+ Value *loadScalarFromByteLayout(IRBuilder<> &B, Type *AccessTy, Value *Src,
+ Align Alignment) {
+ LLVMContext &Ctx = B.getContext();
+ Type *I8Ty = Type::getInt8Ty(Ctx);
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ unsigned NumBytes = DL.getTypeStoreSize(AccessTy);
+ Type *IntTy = IntegerType::get(Ctx, DL.getTypeStoreSizeInBits(AccessTy));
+ Value *IntVal = ConstantInt::get(IntTy, 0);
+
+ for (unsigned I = 0; I < NumBytes; ++I) {
+ Value *Ptr = gepByteOffset(B, Src, I8Ty, I);
+ LoadInst *LI = B.CreateLoad(I8Ty, Ptr);
+ LI->setAlignment(commonAlignment(Alignment, I));
+ buildAssignType(B, I8Ty, LI);
+ Value *Extended = B.CreateZExt(LI, IntTy);
+ buildAssignType(B, IntTy, Extended);
+ Value *Shifted =
+ I == 0 ? Extended
+ : B.CreateShl(Extended, ConstantInt::get(IntTy, 8 * I));
+ if (I != 0)
+ buildAssignType(B, IntTy, Shifted);
+ IntVal = I == 0 ? Shifted : B.CreateOr(IntVal, Shifted);
+ buildAssignType(B, IntTy, IntVal);
+ }
+
+ Value *Result = bitcastIntToScalar(B, IntVal, AccessTy);
+ if (Result != IntVal)
+ buildAssignType(B, AccessTy, Result);
+ return Result;
+ }
+
+ Value *loadValueFromByteLayout(IRBuilder<> &B, Type *AccessTy, Value *Src,
+ Align Alignment) {
+ if (auto *VT = dyn_cast<FixedVectorType>(AccessTy)) {
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ Type *I8Ty = Type::getInt8Ty(B.getContext());
+ unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());
+ SmallVector<Value *, 4> LoadedElements;
+ for (unsigned I = 0; I < VT->getNumElements(); ++I) {
+ Value *ElemPtr = gepByteOffset(B, Src, I8Ty, I * ElemSize);
+ Value *Elem = loadScalarFromByteLayout(
+ B, VT->getElementType(), ElemPtr,
+ commonAlignment(Alignment, I * ElemSize));
+ buildAssignType(B, VT->getElementType(), Elem);
+ LoadedElements.push_back(Elem);
+ }
+ return buildVectorFromLoadedElements(B, VT, LoadedElements);
+ }
+ return loadScalarFromByteLayout(B, AccessTy, Src, Alignment);
+ }
+
// Legalizes a reinterpretation ptrcast: the casted pointer's pointee type
// matches the access type but differs from the original pointer layout (e.g.
// byte-addressable i8 storage accessed as i32).
@@ -246,6 +399,23 @@ class SPIRVLegalizePointerCastImpl {
if (!CastedElemTy || CastedElemTy != AccessTy)
return false;
+ Type *OriginalElemTy = GR->findDeducedElementType(OriginalPtr);
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ if (OriginalElemTy && isByteLayoutType(OriginalElemTy, B.getContext()) &&
+ AccessTy->isSingleValueType() &&
+ DL.getTypeStoreSize(AccessTy) > 1) {
+ if (IsStore) {
+ storeValueToByteLayout(B, StoreSrc, OriginalPtr, Alignment);
+ } else {
+ Value *Loaded =
+ loadValueFromByteLayout(B, AccessTy, OriginalPtr, Alignment);
+ buildAssignType(B, AccessTy, Loaded);
+ GR->replaceAllUsesWith(BadMem, Loaded, /* DeleteOld= */ true);
+ DeadInstructions.push_back(BadMem);
+ }
+ return true;
+ }
+
GR->buildAssignPtr(B, CastedElemTy, OriginalPtr);
if (IsStore) {
StoreInst *SI = B.CreateStore(StoreSrc, OriginalPtr);
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 706b1ef2c4dcb..eef986566e0e9 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -94,7 +94,9 @@ declare ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuff
define void @byteBufferStore() {
; CHECK-LABEL: define void @byteBufferStore(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
-; CHECK: store i32 42, ptr addrspace(11)
+; CHECK: store i8 42, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: store i8 0, ptr addrspace(11)
entry:
%handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
%ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
@@ -105,7 +107,9 @@ entry:
define void @byteBufferLoad() {
; CHECK-LABEL: define void @byteBufferLoad(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
-; CHECK: load i32, ptr addrspace(11)
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: load i8, ptr addrspace(11)
entry:
%handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
%ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
@@ -122,7 +126,8 @@ define void @byteBufferStoreViaLoadedHandle() {
; CHECK-LABEL: define void @byteBufferStoreViaLoadedHandle(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
; CHECK: load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot
-; CHECK: store i32 42, ptr addrspace(11)
+; CHECK: store i8 42, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
entry:
%handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
store target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, ptr @slot, align 8
@@ -136,7 +141,8 @@ define void @byteBufferLoadViaLoadedHandle() {
; CHECK-LABEL: define void @byteBufferLoadViaLoadedHandle(
; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
; CHECK: load target("spirv.VulkanBuffer", [0 x i8], 12, 0), ptr @slot
-; CHECK: load i32, ptr addrspace(11)
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
entry:
%handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
store target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, ptr @slot, align 8
@@ -145,3 +151,28 @@ entry:
%val = load i32, ptr addrspace(11) %ptr, align 4
ret void
}
+
+define void @byteBufferStore4() {
+; CHECK-LABEL: define void @byteBufferStore4(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: store i8
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 4)
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+ store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr addrspace(11) %ptr, align 16
+ ret void
+}
+
+define void @byteBufferLoad4() {
+; CHECK-LABEL: define void @byteBufferLoad4(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 4)
+; CHECK: call {{.*}}@llvm.spv.insertelt
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+ %val = load <4 x i32>, ptr addrspace(11) %ptr, align 16
+ ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll
new file mode 100644
index 0000000000000..91aedb7df9d2a
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll
@@ -0,0 +1,20 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK-NOT: OpTypeVector
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+ at out = addrspace(10) global <4 x i32> zeroinitializer, align 16
+
+define void @main() local_unnamed_addr #0 {
+entry:
+ %handle = tail call target("spirv.VulkanBuffer", [0 x i8], 12, 0) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %ptr = tail call noundef align 16 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+ %val = load <4 x i32>, ptr addrspace(11) %ptr, align 16
+ store <4 x i32> %val, ptr addrspace(10) @out, align 16
+ ret void
+}
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
>From 9caa8f4b578b2213689ac14265c8782e1ef871ee Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Mon, 3 Aug 2026 15:26:06 +0100
Subject: [PATCH 5/6] Update SPIR-V ptrcast legalization
Extend SPIRVLegalizePointerCast to legalize reinterpretation spv.ptrcasts
on byte-addressable buffers ([0 x i8] / RWByteAddressBuffer).
When Clang emits a typed load or store through such a pointer (e.g. i32,
<4 x i32>), the pass now lowers it to valid logical SPIR-V instead of
hitting an unreachable or producing invalid OpAccessChain into uchar
storage.
Co-authored-by: Cursor
---
.../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 220 ++++++++----------
1 file changed, 103 insertions(+), 117 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 7a80959158ea8..b0e4fcfb6dc2c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -222,22 +222,6 @@ class SPIRVLegalizePointerCastImpl {
return std::make_pair(GEP, CurrentTy);
}
- // Returns the pointer operand of a load/store that uses a ptrcast.
- static Value *getCastedPointerOperand(Instruction *MemI) {
- if (auto *LI = dyn_cast<LoadInst>(MemI))
- return LI->getPointerOperand();
- if (auto *SI = dyn_cast<StoreInst>(MemI))
- return SI->getPointerOperand();
- if (auto *II = dyn_cast<IntrinsicInst>(MemI))
- if (II->getIntrinsicID() == Intrinsic::spv_store)
- return II->getArgOperand(1);
- llvm_unreachable("Unexpected memory instruction being legalized.");
- }
-
- static bool isByteLayoutType(Type *Ty, LLVMContext &Ctx) {
- return Ty == Type::getInt8Ty(Ctx);
- }
-
static IntrinsicInst *getResourceGetPointer(Value *Ptr) {
if (auto *II = dyn_cast<IntrinsicInst>(Ptr))
if (II->getIntrinsicID() == Intrinsic::spv_resource_getpointer)
@@ -245,8 +229,7 @@ class SPIRVLegalizePointerCastImpl {
return nullptr;
}
- Value *gepByteOffset(IRBuilder<> &B, Value *BasePtr, Type *I8Ty,
- unsigned ByteOffset) {
+ Value *gepByteOffset(IRBuilder<> &B, Value *BasePtr, unsigned ByteOffset) {
if (ByteOffset == 0)
return BasePtr;
@@ -256,15 +239,13 @@ class SPIRVLegalizePointerCastImpl {
Value *Handle = GetPtr->getOperand(0);
Value *BaseOffset = GetPtr->getOperand(1);
- Value *NewOffset = BaseOffset;
- if (ByteOffset != 0) {
- if (auto *CI = dyn_cast<ConstantInt>(BaseOffset))
- NewOffset = ConstantInt::get(CI->getType(),
- CI->getZExtValue() + ByteOffset);
- else
- NewOffset = B.CreateAdd(
- BaseOffset, ConstantInt::get(BaseOffset->getType(), ByteOffset));
- }
+ Value *NewOffset;
+ if (auto *CI = dyn_cast<ConstantInt>(BaseOffset))
+ NewOffset =
+ ConstantInt::get(CI->getType(), CI->getZExtValue() + ByteOffset);
+ else
+ NewOffset = B.CreateAdd(
+ BaseOffset, ConstantInt::get(BaseOffset->getType(), ByteOffset));
SmallVector<OperandBundleDef, 1> OpBundles;
GetPtr->getOperandBundlesAsDefs(OpBundles);
CallInst *NewPtr = B.CreateCall(GetPtr->getFunctionType(),
@@ -272,6 +253,7 @@ class SPIRVLegalizePointerCastImpl {
{Handle, NewOffset}, OpBundles);
NewPtr->setAttributes(GetPtr->getAttributes());
NewPtr->setCallingConv(GetPtr->getCallingConv());
+ Type *I8Ty = Type::getInt8Ty(B.getContext());
GR->buildAssignPtr(B, I8Ty, NewPtr);
return NewPtr;
}
@@ -302,40 +284,20 @@ class SPIRVLegalizePointerCastImpl {
Type *I8Ty = Type::getInt8Ty(Ctx);
const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
Value *IntVal = bitcastScalarToInt(B, Src);
- buildAssignType(B, IntVal->getType(), IntVal);
unsigned NumBytes = DL.getTypeStoreSize(Src->getType());
for (unsigned I = 0; I < NumBytes; ++I) {
Value *Shifted =
I == 0 ? IntVal
: B.CreateLShr(IntVal, ConstantInt::get(IntVal->getType(), 8 * I));
- if (I != 0)
- buildAssignType(B, IntVal->getType(), Shifted);
Value *Byte = B.CreateTrunc(Shifted, I8Ty);
buildAssignType(B, I8Ty, Byte);
- Value *Ptr = gepByteOffset(B, Dst, I8Ty, I);
+ Value *Ptr = gepByteOffset(B, Dst, I);
StoreInst *SI = B.CreateStore(Byte, Ptr);
SI->setAlignment(commonAlignment(Alignment, I));
}
}
- void storeValueToByteLayout(IRBuilder<> &B, Value *Src, Value *Dst,
- Align Alignment) {
- if (auto *VT = dyn_cast<FixedVectorType>(Src->getType())) {
- const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
- Type *I8Ty = Type::getInt8Ty(B.getContext());
- unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());
- for (unsigned I = 0; I < VT->getNumElements(); ++I) {
- Value *Elem = extractScalarFromVector(B, Src, I);
- Value *ElemPtr = gepByteOffset(B, Dst, I8Ty, I * ElemSize);
- storeScalarToByteLayout(B, Elem, ElemPtr,
- commonAlignment(Alignment, I * ElemSize));
- }
- return;
- }
- storeScalarToByteLayout(B, Src, Dst, Alignment);
- }
-
Value *loadScalarFromByteLayout(IRBuilder<> &B, Type *AccessTy, Value *Src,
Align Alignment) {
LLVMContext &Ctx = B.getContext();
@@ -346,7 +308,7 @@ class SPIRVLegalizePointerCastImpl {
Value *IntVal = ConstantInt::get(IntTy, 0);
for (unsigned I = 0; I < NumBytes; ++I) {
- Value *Ptr = gepByteOffset(B, Src, I8Ty, I);
+ Value *Ptr = gepByteOffset(B, Src, I);
LoadInst *LI = B.CreateLoad(I8Ty, Ptr);
LI->setAlignment(commonAlignment(Alignment, I));
buildAssignType(B, I8Ty, LI);
@@ -367,80 +329,105 @@ class SPIRVLegalizePointerCastImpl {
return Result;
}
- Value *loadValueFromByteLayout(IRBuilder<> &B, Type *AccessTy, Value *Src,
- Align Alignment) {
- if (auto *VT = dyn_cast<FixedVectorType>(AccessTy)) {
- const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
- Type *I8Ty = Type::getInt8Ty(B.getContext());
- unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());
- SmallVector<Value *, 4> LoadedElements;
- for (unsigned I = 0; I < VT->getNumElements(); ++I) {
- Value *ElemPtr = gepByteOffset(B, Src, I8Ty, I * ElemSize);
- Value *Elem = loadScalarFromByteLayout(
- B, VT->getElementType(), ElemPtr,
- commonAlignment(Alignment, I * ElemSize));
- buildAssignType(B, VT->getElementType(), Elem);
- LoadedElements.push_back(Elem);
- }
- return buildVectorFromLoadedElements(B, VT, LoadedElements);
- }
- return loadScalarFromByteLayout(B, AccessTy, Src, Alignment);
- }
+ enum class ReinterpretKind { None, ByteWise, RetagDirect };
- // Legalizes a reinterpretation ptrcast: the casted pointer's pointee type
- // matches the access type but differs from the original pointer layout (e.g.
- // byte-addressable i8 storage accessed as i32).
- template <typename MemInstTy>
- bool tryDirectReinterpretAccess(IRBuilder<> &B, Type *AccessTy,
- Value *OriginalPtr, Value *CastedPtr,
- Align Alignment, bool IsStore,
- Value *StoreSrc, MemInstTy *BadMem) {
+ // Classifies a ptrcast reinterpretation: casted pointee matches the access
+ // type but differs from the original storage layout (e.g. i8 byte buffer as
+ // i32). ByteWise means multi-byte access must use per-byte i8 load/store.
+ ReinterpretKind classifyReinterpretAccess(IRBuilder<> &B, Type *AccessTy,
+ Value *OriginalPtr,
+ Value *CastedPtr) {
Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
if (!CastedElemTy || CastedElemTy != AccessTy)
- return false;
+ return ReinterpretKind::None;
Type *OriginalElemTy = GR->findDeducedElementType(OriginalPtr);
const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
- if (OriginalElemTy && isByteLayoutType(OriginalElemTy, B.getContext()) &&
+ if (OriginalElemTy && OriginalElemTy == Type::getInt8Ty(B.getContext()) &&
AccessTy->isSingleValueType() &&
- DL.getTypeStoreSize(AccessTy) > 1) {
- if (IsStore) {
- storeValueToByteLayout(B, StoreSrc, OriginalPtr, Alignment);
+ DL.getTypeStoreSize(AccessTy) > 1)
+ return ReinterpretKind::ByteWise;
+
+ return ReinterpretKind::RetagDirect;
+ }
+
+ bool tryReinterpretLoad(IRBuilder<> &B, Type *AccessTy, Value *OriginalPtr,
+ Value *CastedPtr, LoadInst *IllegalLoad) {
+ ReinterpretKind Kind =
+ classifyReinterpretAccess(B, AccessTy, OriginalPtr, CastedPtr);
+ if (Kind == ReinterpretKind::None)
+ return false;
+
+ Align Alignment = IllegalLoad->getAlign();
+ if (Kind == ReinterpretKind::ByteWise) {
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ Value *Loaded;
+ if (auto *VT = dyn_cast<FixedVectorType>(AccessTy)) {
+ unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());
+ SmallVector<Value *, 4> LoadedElements;
+ for (unsigned I = 0; I < VT->getNumElements(); ++I) {
+ Value *ElemPtr = gepByteOffset(B, OriginalPtr, I * ElemSize);
+ LoadedElements.push_back(loadScalarFromByteLayout(
+ B, VT->getElementType(), ElemPtr,
+ commonAlignment(Alignment, I * ElemSize)));
+ }
+ Loaded = buildVectorFromLoadedElements(B, VT, LoadedElements);
} else {
- Value *Loaded =
- loadValueFromByteLayout(B, AccessTy, OriginalPtr, Alignment);
+ Loaded = loadScalarFromByteLayout(B, AccessTy, OriginalPtr, Alignment);
buildAssignType(B, AccessTy, Loaded);
- GR->replaceAllUsesWith(BadMem, Loaded, /* DeleteOld= */ true);
- DeadInstructions.push_back(BadMem);
}
+ GR->replaceAllUsesWith(IllegalLoad, Loaded, /* DeleteOld= */ true);
+ DeadInstructions.push_back(IllegalLoad);
return true;
}
- GR->buildAssignPtr(B, CastedElemTy, OriginalPtr);
- if (IsStore) {
- StoreInst *SI = B.CreateStore(StoreSrc, OriginalPtr);
- SI->setAlignment(Alignment);
- } else {
- LoadInst *LI = B.CreateLoad(AccessTy, OriginalPtr);
- LI->setAlignment(Alignment);
- buildAssignType(B, AccessTy, LI);
- GR->replaceAllUsesWith(BadMem, LI, /* DeleteOld= */ true);
- DeadInstructions.push_back(BadMem);
+ GR->buildAssignPtr(B, AccessTy, OriginalPtr);
+ LoadInst *LI = B.CreateLoad(AccessTy, OriginalPtr);
+ LI->setAlignment(Alignment);
+ buildAssignType(B, AccessTy, LI);
+ GR->replaceAllUsesWith(IllegalLoad, LI, /* DeleteOld= */ true);
+ DeadInstructions.push_back(IllegalLoad);
+ return true;
+ }
+
+ bool tryReinterpretStore(IRBuilder<> &B, Type *AccessTy, Value *OriginalPtr,
+ Value *CastedPtr, Value *StoreSrc, Align Alignment) {
+ ReinterpretKind Kind =
+ classifyReinterpretAccess(B, AccessTy, OriginalPtr, CastedPtr);
+ if (Kind == ReinterpretKind::None)
+ return false;
+
+ if (Kind == ReinterpretKind::ByteWise) {
+ const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+ if (auto *VT = dyn_cast<FixedVectorType>(StoreSrc->getType())) {
+ unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());
+ for (unsigned I = 0; I < VT->getNumElements(); ++I) {
+ Value *Elem = extractScalarFromVector(B, StoreSrc, I);
+ Value *ElemPtr = gepByteOffset(B, OriginalPtr, I * ElemSize);
+ storeScalarToByteLayout(B, Elem, ElemPtr,
+ commonAlignment(Alignment, I * ElemSize));
+ }
+ } else {
+ storeScalarToByteLayout(B, StoreSrc, OriginalPtr, Alignment);
+ }
+ return true;
}
+
+ GR->buildAssignPtr(B, AccessTy, OriginalPtr);
+ StoreInst *SI = B.CreateStore(StoreSrc, OriginalPtr);
+ SI->setAlignment(Alignment);
return true;
}
// Builds a legalized load from a pointer, drilling down through
// memory layouts to find a compatible type. Load flags will be
- // copied from |BadLoad|, which should be the load being legalized.
+ // copied from |IllegalLoad|, which should be the load being legalized.
Value *buildLegalizedLoad(IRBuilder<> &B, Type *ElementType, Value *Source,
- LoadInst *BadLoad, Value *CastedPtr) {
+ LoadInst *IllegalLoad, Value *CastedPtr) {
auto ResultOpt = getPointerToFirstCompatibleType(
- B, Source, BadLoad->getPointerOperandType(), ElementType, false);
+ B, Source, IllegalLoad->getPointerOperandType(), ElementType, false);
if (!ResultOpt) {
- if (tryDirectReinterpretAccess(
- B, ElementType, Source, CastedPtr, BadLoad->getAlign(),
- /*IsStore=*/false, /*StoreSrc=*/nullptr, BadLoad))
+ if (tryReinterpretLoad(B, ElementType, Source, CastedPtr, IllegalLoad))
return nullptr;
llvm_unreachable("Failed to load from aggregate: "
"Could not find compatible memory layout.");
@@ -455,19 +442,20 @@ class SPIRVLegalizePointerCastImpl {
if (ElementType == CurrentTy) {
LoadInst *LI = B.CreateLoad(ElementType, GEP);
- LI->setAlignment(BadLoad->getAlign());
+ LI->setAlignment(IllegalLoad->getAlign());
buildAssignType(B, ElementType, LI);
return LI;
}
if (SVT && DVT)
- return loadVectorFromVector(B, SVT, DVT, GEP, BadLoad->getAlign());
+ return loadVectorFromVector(B, SVT, DVT, GEP, IllegalLoad->getAlign());
if (SAT && DVT && SAT->getElementType() == DVT->getElementType())
- return loadVectorFromArray(B, DVT, GEP, BadLoad->getAlign());
+ return loadVectorFromArray(B, DVT, GEP, IllegalLoad->getAlign());
if (MAT && DVT && MAT->getElementType() == DVT->getElementType())
- return loadVectorFromMatrixArray(B, DVT, GEP, MAT, BadLoad->getAlign());
+ return loadVectorFromMatrixArray(B, DVT, GEP, MAT, IllegalLoad->getAlign());
llvm_unreachable("Failed to load from aggregate.");
}
+
Value *
buildVectorFromLoadedElements(IRBuilder<> &B, FixedVectorType *TargetType,
SmallVector<Value *, 4> &LoadedElements) {
@@ -527,6 +515,7 @@ class SPIRVLegalizePointerCastImpl {
}
return buildVectorFromLoadedElements(B, TargetType, LoadedElements);
}
+
// Loads elements from an array and constructs a vector.
Value *loadVectorFromArray(IRBuilder<> &B, FixedVectorType *TargetType,
Value *Source, Align OriginalAlign) {
@@ -731,13 +720,11 @@ class SPIRVLegalizePointerCastImpl {
// memory layouts to find a compatible type.
void buildLegalizedStore(IRBuilder<> &B, Value *Src, Value *Dst,
Align Alignment, Value *CastedPtr,
- Instruction *BadStore) {
+ Instruction *IllegalStore) {
auto ResultOpt = getPointerToFirstCompatibleType(B, Dst, Dst->getType(),
Src->getType(), true);
if (!ResultOpt) {
- if (tryDirectReinterpretAccess(B, Src->getType(), Dst, CastedPtr,
- Alignment,
- /*IsStore=*/true, Src, BadStore))
+ if (tryReinterpretStore(B, Src->getType(), Dst, CastedPtr, Src, Alignment))
return;
llvm_unreachable("Failed to store to aggregate: "
"Could not find compatible memory layout.");
@@ -773,12 +760,11 @@ class SPIRVLegalizePointerCastImpl {
// Transforms a store instruction (or SPV intrinsic) using a ptrcast as
// operand into a valid logical SPIR-V store with no ptrcast.
- void transformStore(IRBuilder<> &B, Instruction *BadStore, Value *Src,
- Value *Dst, Align Alignment) {
- B.SetInsertPoint(BadStore);
- buildLegalizedStore(B, Src, Dst, Alignment,
- getCastedPointerOperand(BadStore), BadStore);
- DeadInstructions.push_back(BadStore);
+ void transformStore(IRBuilder<> &B, Instruction *IllegalStore, Value *Src,
+ Value *Dst, Value *CastedOperand, Align Alignment) {
+ B.SetInsertPoint(IllegalStore);
+ buildLegalizedStore(B, Src, Dst, Alignment, CastedOperand, IllegalStore);
+ DeadInstructions.push_back(IllegalStore);
}
void legalizePointerCast(IntrinsicInst *II) {
@@ -798,7 +784,7 @@ class SPIRVLegalizePointerCastImpl {
if (StoreInst *SI = dyn_cast<StoreInst>(User)) {
transformStore(B, SI, SI->getValueOperand(), OriginalOperand,
- SI->getAlign());
+ CastedOperand, SI->getAlign());
continue;
}
@@ -819,7 +805,7 @@ class SPIRVLegalizePointerCastImpl {
if (ConstantInt *C = dyn_cast<ConstantInt>(Intrin->getOperand(3)))
Alignment = Align(C->getZExtValue());
transformStore(B, Intrin, Intrin->getArgOperand(0), OriginalOperand,
- Alignment);
+ CastedOperand, Alignment);
continue;
}
}
>From 2bd7610de539240b68636ac0202427ad90aeb180 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Tue, 4 Aug 2026 15:46:06 +0100
Subject: [PATCH 6/6] Fix clang-format issues
Correct code formatting.
---
llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index b0e4fcfb6dc2c..517a2b1a3ae1f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -289,7 +289,8 @@ class SPIRVLegalizePointerCastImpl {
for (unsigned I = 0; I < NumBytes; ++I) {
Value *Shifted =
I == 0 ? IntVal
- : B.CreateLShr(IntVal, ConstantInt::get(IntVal->getType(), 8 * I));
+ : B.CreateLShr(IntVal,
+ ConstantInt::get(IntVal->getType(), 8 * I));
Value *Byte = B.CreateTrunc(Shifted, I8Ty);
buildAssignType(B, I8Ty, Byte);
Value *Ptr = gepByteOffset(B, Dst, I);
@@ -344,8 +345,7 @@ class SPIRVLegalizePointerCastImpl {
Type *OriginalElemTy = GR->findDeducedElementType(OriginalPtr);
const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
if (OriginalElemTy && OriginalElemTy == Type::getInt8Ty(B.getContext()) &&
- AccessTy->isSingleValueType() &&
- DL.getTypeStoreSize(AccessTy) > 1)
+ AccessTy->isSingleValueType() && DL.getTypeStoreSize(AccessTy) > 1)
return ReinterpretKind::ByteWise;
return ReinterpretKind::RetagDirect;
@@ -451,7 +451,8 @@ class SPIRVLegalizePointerCastImpl {
if (SAT && DVT && SAT->getElementType() == DVT->getElementType())
return loadVectorFromArray(B, DVT, GEP, IllegalLoad->getAlign());
if (MAT && DVT && MAT->getElementType() == DVT->getElementType())
- return loadVectorFromMatrixArray(B, DVT, GEP, MAT, IllegalLoad->getAlign());
+ return loadVectorFromMatrixArray(B, DVT, GEP, MAT,
+ IllegalLoad->getAlign());
llvm_unreachable("Failed to load from aggregate.");
}
@@ -724,7 +725,8 @@ class SPIRVLegalizePointerCastImpl {
auto ResultOpt = getPointerToFirstCompatibleType(B, Dst, Dst->getType(),
Src->getType(), true);
if (!ResultOpt) {
- if (tryReinterpretStore(B, Src->getType(), Dst, CastedPtr, Src, Alignment))
+ if (tryReinterpretStore(B, Src->getType(), Dst, CastedPtr, Src,
+ Alignment))
return;
llvm_unreachable("Failed to store to aggregate: "
"Could not find compatible memory layout.");
More information about the llvm-commits
mailing list