[llvm] [SPIRV] Legalize byte-buffer reinterpretation ptrcasts (#192523) (PR #212999)

Tim Corringham via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 25 04:04:03 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 01/17] [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 02/17] 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 03/17] [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 04/17] [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 05/17] 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 06/17] 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.");

>From 4ede8b622528858f476890393b89fe036a66b3e0 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Tue, 4 Aug 2026 15:59:07 +0100
Subject: [PATCH 07/17] Fix formatting

Fix formatting not corrected by the previous change.
---
 llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 517a2b1a3ae1f..131d84209c2a7 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -248,9 +248,9 @@ class SPIRVLegalizePointerCastImpl {
           BaseOffset, ConstantInt::get(BaseOffset->getType(), ByteOffset));
     SmallVector<OperandBundleDef, 1> OpBundles;
     GetPtr->getOperandBundlesAsDefs(OpBundles);
-    CallInst *NewPtr = B.CreateCall(GetPtr->getFunctionType(),
-                                    GetPtr->getCalledOperand(),
-                                    {Handle, NewOffset}, OpBundles);
+    CallInst *NewPtr =
+        B.CreateCall(GetPtr->getFunctionType(), GetPtr->getCalledOperand(),
+                     {Handle, NewOffset}, OpBundles);
     NewPtr->setAttributes(GetPtr->getAttributes());
     NewPtr->setCallingConv(GetPtr->getCallingConv());
     Type *I8Ty = Type::getInt8Ty(B.getContext());
@@ -279,7 +279,7 @@ class SPIRVLegalizePointerCastImpl {
   }
 
   void storeScalarToByteLayout(IRBuilder<> &B, Value *Src, Value *Dst,
-                             Align Alignment) {
+                               Align Alignment) {
     LLVMContext &Ctx = B.getContext();
     Type *I8Ty = Type::getInt8Ty(Ctx);
     const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();

>From 3cb69511ff3776a4a65504d726f77d94eabacd1f Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Wed, 5 Aug 2026 17:01:31 +0100
Subject: [PATCH 08/17] [SPIRV] Extend byte-buffer ptrcast legalization test
 coverage

Add opt and llc tests for RetagDirect (i8), dynamic getpointer offsets,
and float byte-wise load/store paths called out by fuzz-fill on PR #212999.
Strengthen e2e FileCheck lines to tie OpAccessChain results to OpLoad/OpStore.

Co-authored-by: Cursor
---
 .../SPIRV/passes/SPIRVLegalizePointerCast.ll  | 89 +++++++++++++++++++
 .../byte-address-buffer-load-float.ll         | 23 +++++
 .../pointers/byte-address-buffer-load4.ll     |  6 +-
 .../pointers/byte-address-buffer-store.ll     | 10 ++-
 4 files changed, 124 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load-float.ll

diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index eef986566e0e9..67e4d6935dd02 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -176,3 +176,92 @@ entry:
   %val = load <4 x i32>, ptr addrspace(11) %ptr, align 16
   ret void
 }
+
+ at outI8 = addrspace(10) global i8 zeroinitializer
+ at outI32 = addrspace(10) global i32 zeroinitializer
+ at outF = addrspace(10) global float zeroinitializer
+
+; Single-byte access uses RetagDirect (retag + typed access), not byte-wise
+; decomposition.
+
+define void @byteBufferStoreI8() {
+; CHECK-LABEL: define void @byteBufferStoreI8(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: store i8 42, ptr addrspace(11)
+; CHECK-NOT: 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)
+  %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+  store i8 42, ptr addrspace(11) %ptr, align 1
+  ret void
+}
+
+define void @byteBufferLoadI8() {
+; CHECK-LABEL: define void @byteBufferLoadI8(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load i8, ptr addrspace(11)
+; CHECK-NOT: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: store i8 {{.*}}, ptr addrspace(10) @outI8
+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 i8, ptr addrspace(11) %ptr, align 1
+  store i8 %val, ptr addrspace(10) @outI8, align 1
+  ret void
+}
+
+; Non-constant getpointer offset forces dynamic add in gepByteOffset.
+
+define void @byteBufferStoreDynamicOffset(i32 %byteOff) {
+; CHECK-LABEL: define void @byteBufferStoreDynamicOffset(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: add i32 {{.*}}%byteOff, 1
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 {{%}}
+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 %byteOff)
+  store i32 42, ptr addrspace(11) %ptr, align 4
+  ret void
+}
+
+define void @byteBufferLoadDynamicOffset(i32 %byteOff) {
+; CHECK-LABEL: define void @byteBufferLoadDynamicOffset(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: add i32 {{.*}}%byteOff, 1
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: store i32 {{.*}}, ptr addrspace(10) @outI32
+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 %byteOff)
+  %val = load i32, ptr addrspace(11) %ptr, align 4
+  store i32 %val, ptr addrspace(10) @outI32, align 4
+  ret void
+}
+
+; Float access exercises bitcast in byte-wise combine/decompose paths.
+
+define void @byteBufferStoreFloat() {
+; CHECK-LABEL: define void @byteBufferStoreFloat(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: store i8
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
+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 float 1.000000e+00, ptr addrspace(11) %ptr, align 4
+  ret void
+}
+
+define void @byteBufferLoadFloat() {
+; CHECK-LABEL: define void @byteBufferLoadFloat(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: bitcast i32 {{.*}} to float
+; CHECK: store float {{.*}}, ptr addrspace(10) @outF
+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 float, ptr addrspace(11) %ptr, align 4
+  store float %val, ptr addrspace(10) @outF, align 4
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load-float.ll b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load-float.ll
new file mode 100644
index 0000000000000..28abedfb1955b
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load-float.ll
@@ -0,0 +1,23 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; FIXME: spirv-val rejects the generated OpStore for float global sinks (separate
+; from ptrcast legalization); legalizer coverage is in SPIRVLegalizePointerCast.ll.
+
+; CHECK-DAG: [[AC:%[0-9]+]] = OpAccessChain {{.*}}
+; CHECK-DAG: OpLoad {{.*}} [[AC]]
+; CHECK-DAG: OpBitcast
+; CHECK-NOT: OpTypeVector
+; CHECK: OpStore {{.*}}
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+ at out = addrspace(10) global float zeroinitializer, align 4
+
+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)
+  %val = load float, ptr addrspace(11) %ptr, align 4
+  store float %val, ptr addrspace(10) @out, align 4
+  ret void
+}
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
diff --git a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll
index 91aedb7df9d2a..70f3c70dee09e 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-load4.ll
@@ -1,9 +1,11 @@
 ; 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-DAG: [[AC:%[0-9]+]] = OpAccessChain {{.*}}
+; CHECK: OpLoad {{.*}} [[AC]]
 ; CHECK-NOT: OpTypeVector
+; CHECK: OpCompositeInsert
+; CHECK: OpStore {{.*}}
 
 @.str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
 @out = addrspace(10) global <4 x i32> zeroinitializer, align 16
diff --git a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll
index 6eaba6f548619..f13d93025373e 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/byte-address-buffer-store.ll
@@ -1,8 +1,14 @@
 ; 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
+; CHECK-DAG: [[AC0:%[0-9]+]] = OpAccessChain {{.*}} %13 %13
+; CHECK: OpStore [[AC0]]
+; CHECK-DAG: [[AC1:%[0-9]+]] = OpAccessChain {{.*}} %13 %17
+; CHECK: OpStore [[AC1]]
+; CHECK-DAG: [[AC2:%[0-9]+]] = OpAccessChain {{.*}} %13 %15
+; CHECK: OpStore [[AC2]]
+; CHECK-DAG: [[AC3:%[0-9]+]] = OpAccessChain {{.*}} %13 %14
+; CHECK: OpStore [[AC3]]
 
 @.str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
 

>From 5b3d5a06309eae3a7dec8b2274e58a07d598597f Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 6 Aug 2026 20:05:18 +0100
Subject: [PATCH 09/17] [SPIRV] Legalize atomicrmw through byte-buffer ptrcasts

Extend SPIRVLegalizePointerCast to decompose typed atomicrmw operations on
RWByteAddressBuffer pointers into per-byte i8 atomics, matching the existing
load/store byte-layout path and enabling InterlockedOr/InterlockedOr64 lowering.

Co-authored-by: Cursor
---
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 101 ++++++++++++++++++
 .../SPIRV/passes/SPIRVLegalizePointerCast.ll  |  48 +++++++++
 2 files changed, 149 insertions(+)

diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 131d84209c2a7..cf59f6a245284 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -49,6 +49,7 @@
 #include "SPIRVUtils.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/IntrinsicsSPIRV.h"
 #include "llvm/Transforms/Utils/Cloning.h"
@@ -390,6 +391,93 @@ class SPIRVLegalizePointerCastImpl {
     return true;
   }
 
+  static bool atomicRMWIsByteDecomposable(AtomicRMWInst::BinOp Op) {
+    switch (Op) {
+    case AtomicRMWInst::Or:
+    case AtomicRMWInst::And:
+    case AtomicRMWInst::Xor:
+    case AtomicRMWInst::Xchg:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  AtomicRMWInst *createMatchingAtomicRMW(IRBuilder<> &B, AtomicRMWInst *Template,
+                                         Value *Ptr, Value *Val) {
+    return B.CreateAtomicRMW(Template->getOperation(), Ptr, Val,
+                             Template->getAlign(), Template->getOrdering(),
+                             Template->getSyncScopeID());
+  }
+
+  Value *atomicRMWScalarToByteLayout(IRBuilder<> &B, AtomicRMWInst *Template,
+                                     Value *OriginalPtr, Value *Val,
+                                     Align Alignment) {
+    LLVMContext &Ctx = B.getContext();
+    Type *I8Ty = Type::getInt8Ty(Ctx);
+    const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
+    Type *AccessTy = Val->getType();
+    Value *IntVal = bitcastScalarToInt(B, Val);
+    unsigned NumBytes = DL.getTypeStoreSize(AccessTy);
+    Type *IntTy = IntegerType::get(Ctx, DL.getTypeStoreSizeInBits(AccessTy));
+    Value *OldIntVal = ConstantInt::get(IntTy, 0);
+
+    for (unsigned I = 0; I < NumBytes; ++I) {
+      Value *Shifted =
+          I == 0 ? IntVal
+                 : B.CreateLShr(IntVal, ConstantInt::get(IntTy, 8 * I));
+      Value *Byte = B.CreateTrunc(Shifted, I8Ty);
+      buildAssignType(B, I8Ty, Byte);
+      Value *Ptr = gepByteOffset(B, OriginalPtr, I);
+      AtomicRMWInst *OldByte = createMatchingAtomicRMW(B, Template, Ptr, Byte);
+      OldByte->setAlignment(commonAlignment(Alignment, I));
+      buildAssignType(B, I8Ty, OldByte);
+      Value *Extended = B.CreateZExt(OldByte, IntTy);
+      buildAssignType(B, IntTy, Extended);
+      Value *ShiftedOld =
+          I == 0 ? Extended
+                 : B.CreateShl(Extended, ConstantInt::get(IntTy, 8 * I));
+      if (I != 0)
+        buildAssignType(B, IntTy, ShiftedOld);
+      OldIntVal = I == 0 ? ShiftedOld : B.CreateOr(OldIntVal, ShiftedOld);
+      buildAssignType(B, IntTy, OldIntVal);
+    }
+
+    Value *Result = bitcastIntToScalar(B, OldIntVal, AccessTy);
+    if (Result != OldIntVal)
+      buildAssignType(B, AccessTy, Result);
+    return Result;
+  }
+
+  bool tryReinterpretAtomicRMW(IRBuilder<> &B, AtomicRMWInst *IllegalRMW,
+                               Value *OriginalPtr, Value *CastedPtr) {
+    Type *AccessTy = IllegalRMW->getValOperand()->getType();
+    ReinterpretKind Kind =
+        classifyReinterpretAccess(B, AccessTy, OriginalPtr, CastedPtr);
+    if (Kind == ReinterpretKind::None)
+      return false;
+
+    if (Kind == ReinterpretKind::ByteWise) {
+      if (!atomicRMWIsByteDecomposable(IllegalRMW->getOperation()))
+        return false;
+      Value *Result = atomicRMWScalarToByteLayout(
+          B, IllegalRMW, OriginalPtr, IllegalRMW->getValOperand(),
+          IllegalRMW->getAlign());
+      GR->replaceAllUsesWith(IllegalRMW, Result, /* DeleteOld= */ true);
+      DeadInstructions.push_back(IllegalRMW);
+      return true;
+    }
+
+    GR->buildAssignPtr(B, AccessTy, OriginalPtr);
+    AtomicRMWInst *NewRMW = createMatchingAtomicRMW(
+        B, IllegalRMW, OriginalPtr, IllegalRMW->getValOperand());
+    NewRMW->setAlignment(IllegalRMW->getAlign());
+    buildAssignType(B, AccessTy, NewRMW);
+    GR->replaceAllUsesWith(IllegalRMW, NewRMW, /* DeleteOld= */ true);
+    DeadInstructions.push_back(IllegalRMW);
+    return true;
+  }
+
   bool tryReinterpretStore(IRBuilder<> &B, Type *AccessTy, Value *OriginalPtr,
                            Value *CastedPtr, Value *StoreSrc, Align Alignment) {
     ReinterpretKind Kind =
@@ -769,6 +857,14 @@ class SPIRVLegalizePointerCastImpl {
     DeadInstructions.push_back(IllegalStore);
   }
 
+  void transformAtomicRMW(IRBuilder<> &B, AtomicRMWInst *IllegalRMW,
+                          Value *CastedOperand, Value *OriginalOperand) {
+    B.SetInsertPoint(IllegalRMW);
+    if (tryReinterpretAtomicRMW(B, IllegalRMW, OriginalOperand, CastedOperand))
+      return;
+    llvm_unreachable("Failed to legalize atomicrmw through ptrcast.");
+  }
+
   void legalizePointerCast(IntrinsicInst *II) {
     Value *CastedOperand = II;
     Value *OriginalOperand = II->getOperand(0);
@@ -790,6 +886,11 @@ class SPIRVLegalizePointerCastImpl {
         continue;
       }
 
+      if (AtomicRMWInst *RMW = dyn_cast<AtomicRMWInst>(User)) {
+        transformAtomicRMW(B, RMW, CastedOperand, OriginalOperand);
+        continue;
+      }
+
       if (IntrinsicInst *Intrin = dyn_cast<IntrinsicInst>(User)) {
         if (Intrin->getIntrinsicID() == Intrinsic::spv_assign_ptr_type) {
           DeadInstructions.push_back(Intrin);
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 67e4d6935dd02..761369ef29d08 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -265,3 +265,51 @@ entry:
   store float %val, ptr addrspace(10) @outF, align 4
   ret void
 }
+
+ at outI64 = addrspace(10) global i64 zeroinitializer
+
+; InterlockedOr on RWByteAddressBuffer lowers to atomicrmw or through i8 ptrcast.
+
+define void @byteBufferAtomicOrI32() {
+; CHECK-LABEL: define void @byteBufferAtomicOrI32(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 42 monotonic, align 4
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 2)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 2
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 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)
+  %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+  %old = atomicrmw or ptr addrspace(11) %ptr, i32 42 monotonic, align 4
+  store i32 %old, ptr addrspace(10) @outI32, align 4
+  ret void
+}
+
+define void @byteBufferAtomicOrI64() {
+; CHECK-LABEL: define void @byteBufferAtomicOrI64(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 42 monotonic, align 8
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 2)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 2
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 4)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 4
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 5)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 6)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 2
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 7)
+; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 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)
+  %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+  %old = atomicrmw or ptr addrspace(11) %ptr, i64 42 monotonic, align 8
+  store i64 %old, ptr addrspace(10) @outI64, align 8
+  ret void
+}

>From 5d13b812b3c119d39bc90fdd87a4cd120e794f26 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 6 Aug 2026 20:27:57 +0100
Subject: [PATCH 10/17] [SPIRV] Legalize Vulkan atomic scope and memory
 semantics

Remap default CrossDevice sync scope to Device or Workgroup for Vulkan
atomics and fences, and upgrade monotonic ordering to AcquireRelease when
storage-class semantics are required so InterlockedOr shaders pass spirv-val.

Co-authored-by: Cursor
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 25 +++++----
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 55 ++++++++++++-------
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          | 36 ++++++++++++
 llvm/lib/Target/SPIRV/SPIRVUtils.h            | 15 +++++
 .../test/CodeGen/SPIRV/vulkan-atomic-scope.ll | 26 +++++++++
 5 files changed, 127 insertions(+), 30 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 513b3149dd28f..7fb5f8c97fc03 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2590,19 +2590,24 @@ SPIRVEmitIntrinsicsImpl::visitAtomicCmpXchgInst(AtomicCmpXchgInst &I) {
   assert(I.getType()->isAggregateType() && "Aggregate result is expected");
   IRBuilder<> B(I.getParent());
   B.SetInsertPoint(&I);
+  const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(*I.getFunction());
+  unsigned AS = I.getPointerOperand()->getType()->getPointerAddressSpace();
   SmallVector<Value *> Args(I.operands());
-  Args.push_back(B.getInt32(
-      static_cast<uint32_t>(getMemScope(I.getContext(), I.getSyncScopeID()))));
+  Args.push_back(B.getInt32(static_cast<uint32_t>(getMemScopeForAtomic(
+      I.getContext(), I.getSyncScopeID(),
+      addressSpaceToStorageClass(AS, ST), ST.getTargetTriple().isVulkanOS()))));
   // Per SPIR-V spec atomic ops must combine the ordering bits with the
   // storage-class bit.
-  const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(*I.getFunction());
-  unsigned AS = I.getPointerOperand()->getType()->getPointerAddressSpace();
-  uint32_t ScSem = static_cast<uint32_t>(
-      getMemSemanticsForStorageClass(addressSpaceToStorageClass(AS, ST)));
-  Args.push_back(B.getInt32(
-      static_cast<uint32_t>(getMemSemantics(I.getSuccessOrdering())) | ScSem));
-  Args.push_back(B.getInt32(
-      static_cast<uint32_t>(getMemSemantics(I.getFailureOrdering())) | ScSem));
+  SPIRV::StorageClass::StorageClass SC = addressSpaceToStorageClass(AS, ST);
+  uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(SC));
+  Args.push_back(B.getInt32(static_cast<uint32_t>(
+                     getMemSemanticsForAtomic(I.getSuccessOrdering(), SC,
+                                              ST.getTargetTriple().isVulkanOS())) |
+                 ScSem));
+  Args.push_back(B.getInt32(static_cast<uint32_t>(
+                     getMemSemanticsForAtomic(I.getFailureOrdering(), SC,
+                                              ST.getTargetTriple().isVulkanOS())) |
+                 ScSem));
   Instruction *NewI = B.CreateIntrinsicWithoutFolding(
       Intrinsic::spv_cmpxchg, {I.getPointerOperand()->getType()}, {Args});
   replaceMemInstrUses(&I, NewI, B);
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 2ac7398479683..aba5c9caaf029 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -2091,17 +2091,22 @@ bool SPIRVInstructionSelector::selectAtomicLoad(Register ResVReg,
   const MachineMemOperand &MemOp = **I.memoperands_begin();
   assert(MemOp.isAtomic());
 
-  uint32_t Scope =
-      static_cast<uint32_t>(getMemScope(Context, MemOp.getSyncScopeID()));
+  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
+      Context, MemOp.getSyncScopeID(),
+      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI),
+      STI.getTargetTriple().isVulkanOS()));
   Register ScopeReg = buildI32Constant(Scope, I);
 
   AtomicOrdering AO = MemOp.getSuccessOrdering();
-  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(
-      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI)));
-  uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
+  SPIRV::StorageClass::StorageClass PtrSC =
+      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
+  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
+                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+                    StorageClass;
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
-  Register MemSemReg = buildI32Constant(MemSem | StorageClass, I);
+  Register MemSemReg = buildI32Constant(MemSem, I);
 
   MachineIRBuilder MIRBuilder(I);
 
@@ -2240,17 +2245,22 @@ bool SPIRVInstructionSelector::selectAtomicStore(MachineInstr &I) const {
   const MachineMemOperand &MemOp = **I.memoperands_begin();
   assert(MemOp.isAtomic());
 
-  uint32_t Scope =
-      static_cast<uint32_t>(getMemScope(Context, MemOp.getSyncScopeID()));
+  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
+      Context, MemOp.getSyncScopeID(),
+      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI),
+      STI.getTargetTriple().isVulkanOS()));
   Register ScopeReg = buildI32Constant(Scope, I);
 
   AtomicOrdering AO = MemOp.getSuccessOrdering();
-  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(
-      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI)));
-  uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
+  SPIRV::StorageClass::StorageClass PtrSC =
+      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
+  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
+                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+                    StorageClass;
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
-  Register MemSemReg = buildI32Constant(MemSem | StorageClass, I);
+  Register MemSemReg = buildI32Constant(MemSem, I);
   MachineIRBuilder MIRBuilder(I);
 
   if (PointeeType.isTypePtr()) {
@@ -2515,15 +2525,19 @@ bool SPIRVInstructionSelector::selectAtomicRMW(Register ResVReg,
                                                unsigned NegateOpcode) const {
   assert(I.hasOneMemOperand());
   const MachineMemOperand *MemOp = *I.memoperands_begin();
-  uint32_t Scope = static_cast<uint32_t>(getMemScope(
-      GR.CurMF->getFunction().getContext(), MemOp->getSyncScopeID()));
+  Register Ptr = I.getOperand(1).getReg();
+  SPIRV::StorageClass::StorageClass PtrSC = GR.getPointerStorageClass(Ptr);
+  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
+      GR.CurMF->getFunction().getContext(), MemOp->getSyncScopeID(), PtrSC,
+      STI.getTargetTriple().isVulkanOS()));
   Register ScopeReg = buildI32Constant(Scope, I);
 
-  Register Ptr = I.getOperand(1).getReg();
-  uint32_t ScSem = static_cast<uint32_t>(
-      getMemSemanticsForStorageClass(GR.getPointerStorageClass(Ptr)));
+  uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   AtomicOrdering AO = MemOp->getSuccessOrdering();
-  uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO)) | ScSem;
+  uint32_t MemSem = static_cast<uint32_t>(
+                        getMemSemanticsForAtomic(AO, PtrSC,
+                                                 STI.getTargetTriple().isVulkanOS())) |
+                    ScSem;
   Register MemSemReg = buildI32Constant(MemSem, I);
 
   Register ValueReg = I.getOperand(2).getReg();
@@ -2642,8 +2656,9 @@ bool SPIRVInstructionSelector::selectFence(MachineInstr &I) const {
   uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
   Register MemSemReg = buildI32Constant(MemSem, I);
   SyncScope::ID Ord = SyncScope::ID(I.getOperand(1).getImm());
-  uint32_t Scope = static_cast<uint32_t>(
-      getMemScope(GR.CurMF->getFunction().getContext(), Ord));
+  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
+      GR.CurMF->getFunction().getContext(), Ord,
+      SPIRV::StorageClass::Uniform, STI.getTargetTriple().isVulkanOS()));
   Register ScopeReg = buildI32Constant(Scope, I);
   MachineBasicBlock &BB = *I.getParent();
   BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpMemoryBarrier))
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 236da61192f1a..3e7ffd9688708 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -448,6 +448,24 @@ SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) {
   llvm_unreachable(nullptr);
 }
 
+SPIRV::MemorySemantics::MemorySemantics
+getMemSemanticsForAtomic(AtomicOrdering Ord,
+                         SPIRV::StorageClass::StorageClass SC,
+                         bool IsVulkan) {
+  SPIRV::MemorySemantics::MemorySemantics Sem = getMemSemantics(Ord);
+  if (!IsVulkan || Sem != SPIRV::MemorySemantics::None)
+    return Sem;
+
+  switch (getMemSemanticsForStorageClass(SC)) {
+  case SPIRV::MemorySemantics::UniformMemory:
+  case SPIRV::MemorySemantics::WorkgroupMemory:
+  case SPIRV::MemorySemantics::ImageMemory:
+    return SPIRV::MemorySemantics::AcquireRelease;
+  default:
+    return Sem;
+  }
+}
+
 SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id) {
   // Named by
   // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_scope_id.
@@ -474,6 +492,24 @@ SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id) {
   return SPIRV::Scope::CrossDevice;
 }
 
+SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
+                                           SPIRV::StorageClass::StorageClass SC) {
+  if (Scope != SPIRV::Scope::CrossDevice)
+    return Scope;
+  if (SC == SPIRV::StorageClass::Workgroup)
+    return SPIRV::Scope::Workgroup;
+  return SPIRV::Scope::Device;
+}
+
+SPIRV::Scope::Scope getMemScopeForAtomic(LLVMContext &Ctx, SyncScope::ID Id,
+                                         SPIRV::StorageClass::StorageClass SC,
+                                         bool IsVulkan) {
+  SPIRV::Scope::Scope Scope = getMemScope(Ctx, Id);
+  if (!IsVulkan)
+    return Scope;
+  return legalizeScopeForVulkan(Scope, SC);
+}
+
 MachineInstr *getDefInstrMaybeConstant(Register &ConstReg,
                                        const MachineRegisterInfo *MRI) {
   MachineInstr *MI = MRI->getVRegDef(ConstReg);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index eac06d1a7036e..5712beec5919b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -285,8 +285,23 @@ getMemSemanticsForStorageClass(SPIRV::StorageClass::StorageClass SC);
 
 SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord);
 
+// Vulkan requires Acquire, Release, or AcquireRelease when storage-class
+// semantics are present on atomic operations.
+SPIRV::MemorySemantics::MemorySemantics
+getMemSemanticsForAtomic(AtomicOrdering Ord,
+                         SPIRV::StorageClass::StorageClass SC, bool IsVulkan);
+
 SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id);
 
+// Vulkan forbids CrossDevice for atomics and memory barriers. Remap it using
+// the accessed storage class when targeting Vulkan.
+SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
+                                           SPIRV::StorageClass::StorageClass SC);
+
+SPIRV::Scope::Scope getMemScopeForAtomic(LLVMContext &Ctx, SyncScope::ID Id,
+                                         SPIRV::StorageClass::StorageClass SC,
+                                         bool IsVulkan);
+
 // Find def instruction for the given ConstReg, walking through
 // spv_track_constant and ASSIGN_TYPE instructions. Updates ConstReg by def
 // of OpConstant instruction.
diff --git a/llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll b/llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll
new file mode 100644
index 0000000000000..851c742c9d6b1
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll
@@ -0,0 +1,26 @@
+; 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 --target-env vulkan1.3 %}
+
+; Default (system) sync scope must not lower to CrossDevice on Vulkan targets.
+; Monotonic atomics on storage-class memory also need AcquireRelease semantics.
+
+; CHECK-DAG: %[[#Int:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#Scope_Device:]] = OpConstant %[[#Int]] 1{{$}}
+; CHECK-DAG: %[[#MemSem:]] = OpConstant %[[#Int]] 72{{$}}
+; CHECK-NOT: OpConstantNull %[[#Int]]
+
+ 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)
+
+define void @main() #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 = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
+  atomicrmw or ptr addrspace(11) %ptr, i32 42 monotonic, align 4
+  ; CHECK: OpAtomicOr {{.*}} %[[#Scope_Device:]] %[[#MemSem:]]
+  ret void
+}
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }

>From 04541bc627f9a45409143ee002cb26c7a9d8b3e8 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Thu, 6 Aug 2026 20:30:33 +0100
Subject: [PATCH 11/17] Fix formatting

Just clang-format changes
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 20 +++++++++----------
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 19 +++++++++---------
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 13 ++++++------
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          |  8 ++++----
 llvm/lib/Target/SPIRV/SPIRVUtils.h            |  5 +++--
 5 files changed, 34 insertions(+), 31 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 7fb5f8c97fc03..3374d55d84fdb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2594,20 +2594,20 @@ SPIRVEmitIntrinsicsImpl::visitAtomicCmpXchgInst(AtomicCmpXchgInst &I) {
   unsigned AS = I.getPointerOperand()->getType()->getPointerAddressSpace();
   SmallVector<Value *> Args(I.operands());
   Args.push_back(B.getInt32(static_cast<uint32_t>(getMemScopeForAtomic(
-      I.getContext(), I.getSyncScopeID(),
-      addressSpaceToStorageClass(AS, ST), ST.getTargetTriple().isVulkanOS()))));
+      I.getContext(), I.getSyncScopeID(), addressSpaceToStorageClass(AS, ST),
+      ST.getTargetTriple().isVulkanOS()))));
   // Per SPIR-V spec atomic ops must combine the ordering bits with the
   // storage-class bit.
   SPIRV::StorageClass::StorageClass SC = addressSpaceToStorageClass(AS, ST);
   uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(SC));
-  Args.push_back(B.getInt32(static_cast<uint32_t>(
-                     getMemSemanticsForAtomic(I.getSuccessOrdering(), SC,
-                                              ST.getTargetTriple().isVulkanOS())) |
-                 ScSem));
-  Args.push_back(B.getInt32(static_cast<uint32_t>(
-                     getMemSemanticsForAtomic(I.getFailureOrdering(), SC,
-                                              ST.getTargetTriple().isVulkanOS())) |
-                 ScSem));
+  Args.push_back(B.getInt32(
+      static_cast<uint32_t>(getMemSemanticsForAtomic(
+          I.getSuccessOrdering(), SC, ST.getTargetTriple().isVulkanOS())) |
+      ScSem));
+  Args.push_back(B.getInt32(
+      static_cast<uint32_t>(getMemSemanticsForAtomic(
+          I.getFailureOrdering(), SC, ST.getTargetTriple().isVulkanOS())) |
+      ScSem));
   Instruction *NewI = B.CreateIntrinsicWithoutFolding(
       Intrinsic::spv_cmpxchg, {I.getPointerOperand()->getType()}, {Args});
   replaceMemInstrUses(&I, NewI, B);
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index aba5c9caaf029..31d9f9d39c328 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -2100,9 +2100,10 @@ bool SPIRVInstructionSelector::selectAtomicLoad(Register ResVReg,
   AtomicOrdering AO = MemOp.getSuccessOrdering();
   SPIRV::StorageClass::StorageClass PtrSC =
       addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
-  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  uint32_t StorageClass =
+      static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+                        AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
                     StorageClass;
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
@@ -2254,9 +2255,10 @@ bool SPIRVInstructionSelector::selectAtomicStore(MachineInstr &I) const {
   AtomicOrdering AO = MemOp.getSuccessOrdering();
   SPIRV::StorageClass::StorageClass PtrSC =
       addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
-  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  uint32_t StorageClass =
+      static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+                        AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
                     StorageClass;
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
@@ -2534,9 +2536,8 @@ bool SPIRVInstructionSelector::selectAtomicRMW(Register ResVReg,
 
   uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   AtomicOrdering AO = MemOp->getSuccessOrdering();
-  uint32_t MemSem = static_cast<uint32_t>(
-                        getMemSemanticsForAtomic(AO, PtrSC,
-                                                 STI.getTargetTriple().isVulkanOS())) |
+  uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
+                        AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
                     ScSem;
   Register MemSemReg = buildI32Constant(MemSem, I);
 
@@ -2657,8 +2658,8 @@ bool SPIRVInstructionSelector::selectFence(MachineInstr &I) const {
   Register MemSemReg = buildI32Constant(MemSem, I);
   SyncScope::ID Ord = SyncScope::ID(I.getOperand(1).getImm());
   uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
-      GR.CurMF->getFunction().getContext(), Ord,
-      SPIRV::StorageClass::Uniform, STI.getTargetTriple().isVulkanOS()));
+      GR.CurMF->getFunction().getContext(), Ord, SPIRV::StorageClass::Uniform,
+      STI.getTargetTriple().isVulkanOS()));
   Register ScopeReg = buildI32Constant(Scope, I);
   MachineBasicBlock &BB = *I.getParent();
   BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpMemoryBarrier))
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index cf59f6a245284..99474e02bec88 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -48,8 +48,8 @@
 #include "SPIRVTargetMachine.h"
 #include "SPIRVUtils.h"
 #include "llvm/IR/IRBuilder.h"
-#include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/IntrinsicsSPIRV.h"
 #include "llvm/Transforms/Utils/Cloning.h"
@@ -403,8 +403,9 @@ class SPIRVLegalizePointerCastImpl {
     }
   }
 
-  AtomicRMWInst *createMatchingAtomicRMW(IRBuilder<> &B, AtomicRMWInst *Template,
-                                         Value *Ptr, Value *Val) {
+  AtomicRMWInst *createMatchingAtomicRMW(IRBuilder<> &B,
+                                         AtomicRMWInst *Template, Value *Ptr,
+                                         Value *Val) {
     return B.CreateAtomicRMW(Template->getOperation(), Ptr, Val,
                              Template->getAlign(), Template->getOrdering(),
                              Template->getSyncScopeID());
@@ -460,9 +461,9 @@ class SPIRVLegalizePointerCastImpl {
     if (Kind == ReinterpretKind::ByteWise) {
       if (!atomicRMWIsByteDecomposable(IllegalRMW->getOperation()))
         return false;
-      Value *Result = atomicRMWScalarToByteLayout(
-          B, IllegalRMW, OriginalPtr, IllegalRMW->getValOperand(),
-          IllegalRMW->getAlign());
+      Value *Result = atomicRMWScalarToByteLayout(B, IllegalRMW, OriginalPtr,
+                                                  IllegalRMW->getValOperand(),
+                                                  IllegalRMW->getAlign());
       GR->replaceAllUsesWith(IllegalRMW, Result, /* DeleteOld= */ true);
       DeadInstructions.push_back(IllegalRMW);
       return true;
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 3e7ffd9688708..bcac3064b85be 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -450,8 +450,7 @@ SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) {
 
 SPIRV::MemorySemantics::MemorySemantics
 getMemSemanticsForAtomic(AtomicOrdering Ord,
-                         SPIRV::StorageClass::StorageClass SC,
-                         bool IsVulkan) {
+                         SPIRV::StorageClass::StorageClass SC, bool IsVulkan) {
   SPIRV::MemorySemantics::MemorySemantics Sem = getMemSemantics(Ord);
   if (!IsVulkan || Sem != SPIRV::MemorySemantics::None)
     return Sem;
@@ -492,8 +491,9 @@ SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id) {
   return SPIRV::Scope::CrossDevice;
 }
 
-SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
-                                           SPIRV::StorageClass::StorageClass SC) {
+SPIRV::Scope::Scope
+legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
+                       SPIRV::StorageClass::StorageClass SC) {
   if (Scope != SPIRV::Scope::CrossDevice)
     return Scope;
   if (SC == SPIRV::StorageClass::Workgroup)
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 5712beec5919b..014bd63f3312c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -295,8 +295,9 @@ SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id);
 
 // Vulkan forbids CrossDevice for atomics and memory barriers. Remap it using
 // the accessed storage class when targeting Vulkan.
-SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
-                                           SPIRV::StorageClass::StorageClass SC);
+SPIRV::Scope::Scope
+legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
+                       SPIRV::StorageClass::StorageClass SC);
 
 SPIRV::Scope::Scope getMemScopeForAtomic(LLVMContext &Ctx, SyncScope::ID Id,
                                          SPIRV::StorageClass::StorageClass SC,

>From ae1b3d340ddf3322d8c432008418dc17c9c09c30 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Mon, 10 Aug 2026 15:55:38 +0100
Subject: [PATCH 12/17] Address review comments

Renamed a few variables and functions:
  GetPtr to ResourcePtr
  NewPtr to ResourcePtrAtOffset
  bitcastScalarToInt() to scalarToStoreInt()
  bitcastIntToScalar() to storeIntToScalar()

Refactored classifyReinterpretAccess() to be a helper function returning a bool shouldReinterpreByteWise(), and amended usaes accordingly.

No functional changes.
---
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 74 ++++++++-----------
 1 file changed, 32 insertions(+), 42 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 99474e02bec88..d83ed29bc180d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -234,12 +234,12 @@ class SPIRVLegalizePointerCastImpl {
     if (ByteOffset == 0)
       return BasePtr;
 
-    IntrinsicInst *GetPtr = getResourceGetPointer(BasePtr);
-    assert(GetPtr &&
+    IntrinsicInst *ResourcePtr = getResourceGetPointer(BasePtr);
+    assert(ResourcePtr &&
            "byte layout pointer must come from spv.resource.getpointer");
 
-    Value *Handle = GetPtr->getOperand(0);
-    Value *BaseOffset = GetPtr->getOperand(1);
+    Value *Handle = ResourcePtr->getOperand(0);
+    Value *BaseOffset = ResourcePtr->getOperand(1);
     Value *NewOffset;
     if (auto *CI = dyn_cast<ConstantInt>(BaseOffset))
       NewOffset =
@@ -247,19 +247,19 @@ class SPIRVLegalizePointerCastImpl {
     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());
+    SmallVector<OperandBundleDef> OpBundles;
+    ResourcePtr->getOperandBundlesAsDefs(OpBundles);
+    CallInst *ResourcePtrAtOffset = B.CreateCall(
+        ResourcePtr->getFunctionType(), ResourcePtr->getCalledOperand(),
+        {Handle, NewOffset}, OpBundles);
+    ResourcePtrAtOffset->setAttributes(ResourcePtr->getAttributes());
+    ResourcePtrAtOffset->setCallingConv(ResourcePtr->getCallingConv());
     Type *I8Ty = Type::getInt8Ty(B.getContext());
-    GR->buildAssignPtr(B, I8Ty, NewPtr);
-    return NewPtr;
+    GR->buildAssignPtr(B, I8Ty, ResourcePtrAtOffset);
+    return ResourcePtrAtOffset;
   }
 
-  Value *bitcastScalarToInt(IRBuilder<> &B, Value *Scalar) {
+  Value *scalarToStoreInt(IRBuilder<> &B, Value *Scalar) {
     Type *Ty = Scalar->getType();
     const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
     Type *IntTy =
@@ -271,7 +271,7 @@ class SPIRVLegalizePointerCastImpl {
     return B.CreateBitCast(Scalar, IntTy);
   }
 
-  Value *bitcastIntToScalar(IRBuilder<> &B, Value *IntVal, Type *ScalarTy) {
+  Value *storeIntToScalar(IRBuilder<> &B, Value *IntVal, Type *ScalarTy) {
     if (IntVal->getType() == ScalarTy)
       return IntVal;
     if (ScalarTy->isIntOrIntVectorTy())
@@ -284,7 +284,7 @@ class SPIRVLegalizePointerCastImpl {
     LLVMContext &Ctx = B.getContext();
     Type *I8Ty = Type::getInt8Ty(Ctx);
     const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
-    Value *IntVal = bitcastScalarToInt(B, Src);
+    Value *IntVal = scalarToStoreInt(B, Src);
     unsigned NumBytes = DL.getTypeStoreSize(Src->getType());
 
     for (unsigned I = 0; I < NumBytes; ++I) {
@@ -325,42 +325,34 @@ class SPIRVLegalizePointerCastImpl {
       buildAssignType(B, IntTy, IntVal);
     }
 
-    Value *Result = bitcastIntToScalar(B, IntVal, AccessTy);
+    Value *Result = storeIntToScalar(B, IntVal, AccessTy);
     if (Result != IntVal)
       buildAssignType(B, AccessTy, Result);
     return Result;
   }
 
-  enum class ReinterpretKind { None, ByteWise, RetagDirect };
-
   // 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 ReinterpretKind::None;
-
+  bool shouldReinterpretByteWise(IRBuilder<> &B, Type *AccessTy,
+                                 Value *OriginalPtr) {
     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)
-      return ReinterpretKind::ByteWise;
+      return true;
 
-    return ReinterpretKind::RetagDirect;
+    return false;
   }
 
   bool tryReinterpretLoad(IRBuilder<> &B, Type *AccessTy, Value *OriginalPtr,
                           Value *CastedPtr, LoadInst *IllegalLoad) {
-    ReinterpretKind Kind =
-        classifyReinterpretAccess(B, AccessTy, OriginalPtr, CastedPtr);
-    if (Kind == ReinterpretKind::None)
+    Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
+    if (!CastedElemTy || CastedElemTy != AccessTy)
       return false;
 
     Align Alignment = IllegalLoad->getAlign();
-    if (Kind == ReinterpretKind::ByteWise) {
+    if (shouldReinterpretByteWise(B, AccessTy, OriginalPtr)) {
       const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
       Value *Loaded;
       if (auto *VT = dyn_cast<FixedVectorType>(AccessTy)) {
@@ -418,7 +410,7 @@ class SPIRVLegalizePointerCastImpl {
     Type *I8Ty = Type::getInt8Ty(Ctx);
     const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
     Type *AccessTy = Val->getType();
-    Value *IntVal = bitcastScalarToInt(B, Val);
+    Value *IntVal = scalarToStoreInt(B, Val);
     unsigned NumBytes = DL.getTypeStoreSize(AccessTy);
     Type *IntTy = IntegerType::get(Ctx, DL.getTypeStoreSizeInBits(AccessTy));
     Value *OldIntVal = ConstantInt::get(IntTy, 0);
@@ -444,7 +436,7 @@ class SPIRVLegalizePointerCastImpl {
       buildAssignType(B, IntTy, OldIntVal);
     }
 
-    Value *Result = bitcastIntToScalar(B, OldIntVal, AccessTy);
+    Value *Result = storeIntToScalar(B, OldIntVal, AccessTy);
     if (Result != OldIntVal)
       buildAssignType(B, AccessTy, Result);
     return Result;
@@ -453,12 +445,11 @@ class SPIRVLegalizePointerCastImpl {
   bool tryReinterpretAtomicRMW(IRBuilder<> &B, AtomicRMWInst *IllegalRMW,
                                Value *OriginalPtr, Value *CastedPtr) {
     Type *AccessTy = IllegalRMW->getValOperand()->getType();
-    ReinterpretKind Kind =
-        classifyReinterpretAccess(B, AccessTy, OriginalPtr, CastedPtr);
-    if (Kind == ReinterpretKind::None)
+    Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
+    if (!CastedElemTy || CastedElemTy != AccessTy)
       return false;
 
-    if (Kind == ReinterpretKind::ByteWise) {
+    if (shouldReinterpretByteWise(B, AccessTy, OriginalPtr)) {
       if (!atomicRMWIsByteDecomposable(IllegalRMW->getOperation()))
         return false;
       Value *Result = atomicRMWScalarToByteLayout(B, IllegalRMW, OriginalPtr,
@@ -481,12 +472,11 @@ class SPIRVLegalizePointerCastImpl {
 
   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)
+    Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
+    if (!CastedElemTy || CastedElemTy != AccessTy)
       return false;
 
-    if (Kind == ReinterpretKind::ByteWise) {
+    if (shouldReinterpretByteWise(B, AccessTy, OriginalPtr)) {
       const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
       if (auto *VT = dyn_cast<FixedVectorType>(StoreSrc->getType())) {
         unsigned ElemSize = DL.getTypeStoreSize(VT->getElementType());

>From 840ac43ac795b34c302e17c40f6c23caaf81818c Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Mon, 10 Aug 2026 16:54:10 +0100
Subject: [PATCH 13/17] Revert "Fix formatting"

This reverts commit 04541bc627f9a45409143ee002cb26c7a9d8b3e8.
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 20 +++++++++----------
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 19 +++++++++---------
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 13 ++++++------
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          |  8 ++++----
 llvm/lib/Target/SPIRV/SPIRVUtils.h            |  5 ++---
 5 files changed, 31 insertions(+), 34 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 3374d55d84fdb..7fb5f8c97fc03 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2594,20 +2594,20 @@ SPIRVEmitIntrinsicsImpl::visitAtomicCmpXchgInst(AtomicCmpXchgInst &I) {
   unsigned AS = I.getPointerOperand()->getType()->getPointerAddressSpace();
   SmallVector<Value *> Args(I.operands());
   Args.push_back(B.getInt32(static_cast<uint32_t>(getMemScopeForAtomic(
-      I.getContext(), I.getSyncScopeID(), addressSpaceToStorageClass(AS, ST),
-      ST.getTargetTriple().isVulkanOS()))));
+      I.getContext(), I.getSyncScopeID(),
+      addressSpaceToStorageClass(AS, ST), ST.getTargetTriple().isVulkanOS()))));
   // Per SPIR-V spec atomic ops must combine the ordering bits with the
   // storage-class bit.
   SPIRV::StorageClass::StorageClass SC = addressSpaceToStorageClass(AS, ST);
   uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(SC));
-  Args.push_back(B.getInt32(
-      static_cast<uint32_t>(getMemSemanticsForAtomic(
-          I.getSuccessOrdering(), SC, ST.getTargetTriple().isVulkanOS())) |
-      ScSem));
-  Args.push_back(B.getInt32(
-      static_cast<uint32_t>(getMemSemanticsForAtomic(
-          I.getFailureOrdering(), SC, ST.getTargetTriple().isVulkanOS())) |
-      ScSem));
+  Args.push_back(B.getInt32(static_cast<uint32_t>(
+                     getMemSemanticsForAtomic(I.getSuccessOrdering(), SC,
+                                              ST.getTargetTriple().isVulkanOS())) |
+                 ScSem));
+  Args.push_back(B.getInt32(static_cast<uint32_t>(
+                     getMemSemanticsForAtomic(I.getFailureOrdering(), SC,
+                                              ST.getTargetTriple().isVulkanOS())) |
+                 ScSem));
   Instruction *NewI = B.CreateIntrinsicWithoutFolding(
       Intrinsic::spv_cmpxchg, {I.getPointerOperand()->getType()}, {Args});
   replaceMemInstrUses(&I, NewI, B);
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 31d9f9d39c328..aba5c9caaf029 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -2100,10 +2100,9 @@ bool SPIRVInstructionSelector::selectAtomicLoad(Register ResVReg,
   AtomicOrdering AO = MemOp.getSuccessOrdering();
   SPIRV::StorageClass::StorageClass PtrSC =
       addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
-  uint32_t StorageClass =
-      static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                        AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
                     StorageClass;
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
@@ -2255,10 +2254,9 @@ bool SPIRVInstructionSelector::selectAtomicStore(MachineInstr &I) const {
   AtomicOrdering AO = MemOp.getSuccessOrdering();
   SPIRV::StorageClass::StorageClass PtrSC =
       addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
-  uint32_t StorageClass =
-      static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                        AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
                     StorageClass;
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
@@ -2536,8 +2534,9 @@ bool SPIRVInstructionSelector::selectAtomicRMW(Register ResVReg,
 
   uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
   AtomicOrdering AO = MemOp->getSuccessOrdering();
-  uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                        AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
+  uint32_t MemSem = static_cast<uint32_t>(
+                        getMemSemanticsForAtomic(AO, PtrSC,
+                                                 STI.getTargetTriple().isVulkanOS())) |
                     ScSem;
   Register MemSemReg = buildI32Constant(MemSem, I);
 
@@ -2658,8 +2657,8 @@ bool SPIRVInstructionSelector::selectFence(MachineInstr &I) const {
   Register MemSemReg = buildI32Constant(MemSem, I);
   SyncScope::ID Ord = SyncScope::ID(I.getOperand(1).getImm());
   uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
-      GR.CurMF->getFunction().getContext(), Ord, SPIRV::StorageClass::Uniform,
-      STI.getTargetTriple().isVulkanOS()));
+      GR.CurMF->getFunction().getContext(), Ord,
+      SPIRV::StorageClass::Uniform, STI.getTargetTriple().isVulkanOS()));
   Register ScopeReg = buildI32Constant(Scope, I);
   MachineBasicBlock &BB = *I.getParent();
   BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpMemoryBarrier))
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index d83ed29bc180d..920e73eb87825 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -48,8 +48,8 @@
 #include "SPIRVTargetMachine.h"
 #include "SPIRVUtils.h"
 #include "llvm/IR/IRBuilder.h"
-#include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/IntrinsicsSPIRV.h"
 #include "llvm/Transforms/Utils/Cloning.h"
@@ -395,9 +395,8 @@ class SPIRVLegalizePointerCastImpl {
     }
   }
 
-  AtomicRMWInst *createMatchingAtomicRMW(IRBuilder<> &B,
-                                         AtomicRMWInst *Template, Value *Ptr,
-                                         Value *Val) {
+  AtomicRMWInst *createMatchingAtomicRMW(IRBuilder<> &B, AtomicRMWInst *Template,
+                                         Value *Ptr, Value *Val) {
     return B.CreateAtomicRMW(Template->getOperation(), Ptr, Val,
                              Template->getAlign(), Template->getOrdering(),
                              Template->getSyncScopeID());
@@ -452,9 +451,9 @@ class SPIRVLegalizePointerCastImpl {
     if (shouldReinterpretByteWise(B, AccessTy, OriginalPtr)) {
       if (!atomicRMWIsByteDecomposable(IllegalRMW->getOperation()))
         return false;
-      Value *Result = atomicRMWScalarToByteLayout(B, IllegalRMW, OriginalPtr,
-                                                  IllegalRMW->getValOperand(),
-                                                  IllegalRMW->getAlign());
+      Value *Result = atomicRMWScalarToByteLayout(
+          B, IllegalRMW, OriginalPtr, IllegalRMW->getValOperand(),
+          IllegalRMW->getAlign());
       GR->replaceAllUsesWith(IllegalRMW, Result, /* DeleteOld= */ true);
       DeadInstructions.push_back(IllegalRMW);
       return true;
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index bcac3064b85be..3e7ffd9688708 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -450,7 +450,8 @@ SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) {
 
 SPIRV::MemorySemantics::MemorySemantics
 getMemSemanticsForAtomic(AtomicOrdering Ord,
-                         SPIRV::StorageClass::StorageClass SC, bool IsVulkan) {
+                         SPIRV::StorageClass::StorageClass SC,
+                         bool IsVulkan) {
   SPIRV::MemorySemantics::MemorySemantics Sem = getMemSemantics(Ord);
   if (!IsVulkan || Sem != SPIRV::MemorySemantics::None)
     return Sem;
@@ -491,9 +492,8 @@ SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id) {
   return SPIRV::Scope::CrossDevice;
 }
 
-SPIRV::Scope::Scope
-legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
-                       SPIRV::StorageClass::StorageClass SC) {
+SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
+                                           SPIRV::StorageClass::StorageClass SC) {
   if (Scope != SPIRV::Scope::CrossDevice)
     return Scope;
   if (SC == SPIRV::StorageClass::Workgroup)
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 014bd63f3312c..5712beec5919b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -295,9 +295,8 @@ SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id);
 
 // Vulkan forbids CrossDevice for atomics and memory barriers. Remap it using
 // the accessed storage class when targeting Vulkan.
-SPIRV::Scope::Scope
-legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
-                       SPIRV::StorageClass::StorageClass SC);
+SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
+                                           SPIRV::StorageClass::StorageClass SC);
 
 SPIRV::Scope::Scope getMemScopeForAtomic(LLVMContext &Ctx, SyncScope::ID Id,
                                          SPIRV::StorageClass::StorageClass SC,

>From b25da36785e1778f69862012768d7933aeef743f Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Mon, 10 Aug 2026 16:54:34 +0100
Subject: [PATCH 14/17] Revert "[SPIRV] Legalize Vulkan atomic scope and memory
 semantics"

This reverts commit 5d13b812b3c119d39bc90fdd87a4cd120e794f26.

This wasn't intended to be included in the current PR.
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 25 ++++-----
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 55 +++++++------------
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          | 36 ------------
 llvm/lib/Target/SPIRV/SPIRVUtils.h            | 15 -----
 .../test/CodeGen/SPIRV/vulkan-atomic-scope.ll | 26 ---------
 5 files changed, 30 insertions(+), 127 deletions(-)
 delete mode 100644 llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 7fb5f8c97fc03..513b3149dd28f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2590,24 +2590,19 @@ SPIRVEmitIntrinsicsImpl::visitAtomicCmpXchgInst(AtomicCmpXchgInst &I) {
   assert(I.getType()->isAggregateType() && "Aggregate result is expected");
   IRBuilder<> B(I.getParent());
   B.SetInsertPoint(&I);
-  const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(*I.getFunction());
-  unsigned AS = I.getPointerOperand()->getType()->getPointerAddressSpace();
   SmallVector<Value *> Args(I.operands());
-  Args.push_back(B.getInt32(static_cast<uint32_t>(getMemScopeForAtomic(
-      I.getContext(), I.getSyncScopeID(),
-      addressSpaceToStorageClass(AS, ST), ST.getTargetTriple().isVulkanOS()))));
+  Args.push_back(B.getInt32(
+      static_cast<uint32_t>(getMemScope(I.getContext(), I.getSyncScopeID()))));
   // Per SPIR-V spec atomic ops must combine the ordering bits with the
   // storage-class bit.
-  SPIRV::StorageClass::StorageClass SC = addressSpaceToStorageClass(AS, ST);
-  uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(SC));
-  Args.push_back(B.getInt32(static_cast<uint32_t>(
-                     getMemSemanticsForAtomic(I.getSuccessOrdering(), SC,
-                                              ST.getTargetTriple().isVulkanOS())) |
-                 ScSem));
-  Args.push_back(B.getInt32(static_cast<uint32_t>(
-                     getMemSemanticsForAtomic(I.getFailureOrdering(), SC,
-                                              ST.getTargetTriple().isVulkanOS())) |
-                 ScSem));
+  const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(*I.getFunction());
+  unsigned AS = I.getPointerOperand()->getType()->getPointerAddressSpace();
+  uint32_t ScSem = static_cast<uint32_t>(
+      getMemSemanticsForStorageClass(addressSpaceToStorageClass(AS, ST)));
+  Args.push_back(B.getInt32(
+      static_cast<uint32_t>(getMemSemantics(I.getSuccessOrdering())) | ScSem));
+  Args.push_back(B.getInt32(
+      static_cast<uint32_t>(getMemSemantics(I.getFailureOrdering())) | ScSem));
   Instruction *NewI = B.CreateIntrinsicWithoutFolding(
       Intrinsic::spv_cmpxchg, {I.getPointerOperand()->getType()}, {Args});
   replaceMemInstrUses(&I, NewI, B);
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index aba5c9caaf029..2ac7398479683 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -2091,22 +2091,17 @@ bool SPIRVInstructionSelector::selectAtomicLoad(Register ResVReg,
   const MachineMemOperand &MemOp = **I.memoperands_begin();
   assert(MemOp.isAtomic());
 
-  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
-      Context, MemOp.getSyncScopeID(),
-      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI),
-      STI.getTargetTriple().isVulkanOS()));
+  uint32_t Scope =
+      static_cast<uint32_t>(getMemScope(Context, MemOp.getSyncScopeID()));
   Register ScopeReg = buildI32Constant(Scope, I);
 
   AtomicOrdering AO = MemOp.getSuccessOrdering();
-  SPIRV::StorageClass::StorageClass PtrSC =
-      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
-  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
-  uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
-                    StorageClass;
+  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(
+      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI)));
+  uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
-  Register MemSemReg = buildI32Constant(MemSem, I);
+  Register MemSemReg = buildI32Constant(MemSem | StorageClass, I);
 
   MachineIRBuilder MIRBuilder(I);
 
@@ -2245,22 +2240,17 @@ bool SPIRVInstructionSelector::selectAtomicStore(MachineInstr &I) const {
   const MachineMemOperand &MemOp = **I.memoperands_begin();
   assert(MemOp.isAtomic());
 
-  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
-      Context, MemOp.getSyncScopeID(),
-      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI),
-      STI.getTargetTriple().isVulkanOS()));
+  uint32_t Scope =
+      static_cast<uint32_t>(getMemScope(Context, MemOp.getSyncScopeID()));
   Register ScopeReg = buildI32Constant(Scope, I);
 
   AtomicOrdering AO = MemOp.getSuccessOrdering();
-  SPIRV::StorageClass::StorageClass PtrSC =
-      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI);
-  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
-  uint32_t MemSem = static_cast<uint32_t>(getMemSemanticsForAtomic(
-                      AO, PtrSC, STI.getTargetTriple().isVulkanOS())) |
-                    StorageClass;
+  uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(
+      addressSpaceToStorageClass(MemOp.getAddrSpace(), STI)));
+  uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
   if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
     MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
-  Register MemSemReg = buildI32Constant(MemSem, I);
+  Register MemSemReg = buildI32Constant(MemSem | StorageClass, I);
   MachineIRBuilder MIRBuilder(I);
 
   if (PointeeType.isTypePtr()) {
@@ -2525,19 +2515,15 @@ bool SPIRVInstructionSelector::selectAtomicRMW(Register ResVReg,
                                                unsigned NegateOpcode) const {
   assert(I.hasOneMemOperand());
   const MachineMemOperand *MemOp = *I.memoperands_begin();
-  Register Ptr = I.getOperand(1).getReg();
-  SPIRV::StorageClass::StorageClass PtrSC = GR.getPointerStorageClass(Ptr);
-  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
-      GR.CurMF->getFunction().getContext(), MemOp->getSyncScopeID(), PtrSC,
-      STI.getTargetTriple().isVulkanOS()));
+  uint32_t Scope = static_cast<uint32_t>(getMemScope(
+      GR.CurMF->getFunction().getContext(), MemOp->getSyncScopeID()));
   Register ScopeReg = buildI32Constant(Scope, I);
 
-  uint32_t ScSem = static_cast<uint32_t>(getMemSemanticsForStorageClass(PtrSC));
+  Register Ptr = I.getOperand(1).getReg();
+  uint32_t ScSem = static_cast<uint32_t>(
+      getMemSemanticsForStorageClass(GR.getPointerStorageClass(Ptr)));
   AtomicOrdering AO = MemOp->getSuccessOrdering();
-  uint32_t MemSem = static_cast<uint32_t>(
-                        getMemSemanticsForAtomic(AO, PtrSC,
-                                                 STI.getTargetTriple().isVulkanOS())) |
-                    ScSem;
+  uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO)) | ScSem;
   Register MemSemReg = buildI32Constant(MemSem, I);
 
   Register ValueReg = I.getOperand(2).getReg();
@@ -2656,9 +2642,8 @@ bool SPIRVInstructionSelector::selectFence(MachineInstr &I) const {
   uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
   Register MemSemReg = buildI32Constant(MemSem, I);
   SyncScope::ID Ord = SyncScope::ID(I.getOperand(1).getImm());
-  uint32_t Scope = static_cast<uint32_t>(getMemScopeForAtomic(
-      GR.CurMF->getFunction().getContext(), Ord,
-      SPIRV::StorageClass::Uniform, STI.getTargetTriple().isVulkanOS()));
+  uint32_t Scope = static_cast<uint32_t>(
+      getMemScope(GR.CurMF->getFunction().getContext(), Ord));
   Register ScopeReg = buildI32Constant(Scope, I);
   MachineBasicBlock &BB = *I.getParent();
   BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpMemoryBarrier))
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 3e7ffd9688708..236da61192f1a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -448,24 +448,6 @@ SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) {
   llvm_unreachable(nullptr);
 }
 
-SPIRV::MemorySemantics::MemorySemantics
-getMemSemanticsForAtomic(AtomicOrdering Ord,
-                         SPIRV::StorageClass::StorageClass SC,
-                         bool IsVulkan) {
-  SPIRV::MemorySemantics::MemorySemantics Sem = getMemSemantics(Ord);
-  if (!IsVulkan || Sem != SPIRV::MemorySemantics::None)
-    return Sem;
-
-  switch (getMemSemanticsForStorageClass(SC)) {
-  case SPIRV::MemorySemantics::UniformMemory:
-  case SPIRV::MemorySemantics::WorkgroupMemory:
-  case SPIRV::MemorySemantics::ImageMemory:
-    return SPIRV::MemorySemantics::AcquireRelease;
-  default:
-    return Sem;
-  }
-}
-
 SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id) {
   // Named by
   // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_scope_id.
@@ -492,24 +474,6 @@ SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id) {
   return SPIRV::Scope::CrossDevice;
 }
 
-SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
-                                           SPIRV::StorageClass::StorageClass SC) {
-  if (Scope != SPIRV::Scope::CrossDevice)
-    return Scope;
-  if (SC == SPIRV::StorageClass::Workgroup)
-    return SPIRV::Scope::Workgroup;
-  return SPIRV::Scope::Device;
-}
-
-SPIRV::Scope::Scope getMemScopeForAtomic(LLVMContext &Ctx, SyncScope::ID Id,
-                                         SPIRV::StorageClass::StorageClass SC,
-                                         bool IsVulkan) {
-  SPIRV::Scope::Scope Scope = getMemScope(Ctx, Id);
-  if (!IsVulkan)
-    return Scope;
-  return legalizeScopeForVulkan(Scope, SC);
-}
-
 MachineInstr *getDefInstrMaybeConstant(Register &ConstReg,
                                        const MachineRegisterInfo *MRI) {
   MachineInstr *MI = MRI->getVRegDef(ConstReg);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 5712beec5919b..eac06d1a7036e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -285,23 +285,8 @@ getMemSemanticsForStorageClass(SPIRV::StorageClass::StorageClass SC);
 
 SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord);
 
-// Vulkan requires Acquire, Release, or AcquireRelease when storage-class
-// semantics are present on atomic operations.
-SPIRV::MemorySemantics::MemorySemantics
-getMemSemanticsForAtomic(AtomicOrdering Ord,
-                         SPIRV::StorageClass::StorageClass SC, bool IsVulkan);
-
 SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id);
 
-// Vulkan forbids CrossDevice for atomics and memory barriers. Remap it using
-// the accessed storage class when targeting Vulkan.
-SPIRV::Scope::Scope legalizeScopeForVulkan(SPIRV::Scope::Scope Scope,
-                                           SPIRV::StorageClass::StorageClass SC);
-
-SPIRV::Scope::Scope getMemScopeForAtomic(LLVMContext &Ctx, SyncScope::ID Id,
-                                         SPIRV::StorageClass::StorageClass SC,
-                                         bool IsVulkan);
-
 // Find def instruction for the given ConstReg, walking through
 // spv_track_constant and ASSIGN_TYPE instructions. Updates ConstReg by def
 // of OpConstant instruction.
diff --git a/llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll b/llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll
deleted file mode 100644
index 851c742c9d6b1..0000000000000
--- a/llvm/test/CodeGen/SPIRV/vulkan-atomic-scope.ll
+++ /dev/null
@@ -1,26 +0,0 @@
-; 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 --target-env vulkan1.3 %}
-
-; Default (system) sync scope must not lower to CrossDevice on Vulkan targets.
-; Monotonic atomics on storage-class memory also need AcquireRelease semantics.
-
-; CHECK-DAG: %[[#Int:]] = OpTypeInt 32 0
-; CHECK-DAG: %[[#Scope_Device:]] = OpConstant %[[#Int]] 1{{$}}
-; CHECK-DAG: %[[#MemSem:]] = OpConstant %[[#Int]] 72{{$}}
-; CHECK-NOT: OpConstantNull %[[#Int]]
-
- 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)
-
-define void @main() #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 = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
-  atomicrmw or ptr addrspace(11) %ptr, i32 42 monotonic, align 4
-  ; CHECK: OpAtomicOr {{.*}} %[[#Scope_Device:]] %[[#MemSem:]]
-  ret void
-}
-
-attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }

>From 1e01b561fc060da822b917f6181bd68f3055c67b Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Mon, 10 Aug 2026 21:38:14 +0100
Subject: [PATCH 15/17] Revert "[SPIRV] Legalize atomicrmw through byte-buffer
 ptrcasts"

This reverts commit 5b3d5a06309eae3a7dec8b2274e58a07d598597f.

This was not intended to be included in the current PR
---
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 100 ------------------
 .../SPIRV/passes/SPIRVLegalizePointerCast.ll  |  48 ---------
 2 files changed, 148 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 920e73eb87825..f2e4c354a251f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -49,7 +49,6 @@
 #include "SPIRVUtils.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/IntrinsicInst.h"
-#include "llvm/IR/Instructions.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/IntrinsicsSPIRV.h"
 #include "llvm/Transforms/Utils/Cloning.h"
@@ -383,92 +382,6 @@ class SPIRVLegalizePointerCastImpl {
     return true;
   }
 
-  static bool atomicRMWIsByteDecomposable(AtomicRMWInst::BinOp Op) {
-    switch (Op) {
-    case AtomicRMWInst::Or:
-    case AtomicRMWInst::And:
-    case AtomicRMWInst::Xor:
-    case AtomicRMWInst::Xchg:
-      return true;
-    default:
-      return false;
-    }
-  }
-
-  AtomicRMWInst *createMatchingAtomicRMW(IRBuilder<> &B, AtomicRMWInst *Template,
-                                         Value *Ptr, Value *Val) {
-    return B.CreateAtomicRMW(Template->getOperation(), Ptr, Val,
-                             Template->getAlign(), Template->getOrdering(),
-                             Template->getSyncScopeID());
-  }
-
-  Value *atomicRMWScalarToByteLayout(IRBuilder<> &B, AtomicRMWInst *Template,
-                                     Value *OriginalPtr, Value *Val,
-                                     Align Alignment) {
-    LLVMContext &Ctx = B.getContext();
-    Type *I8Ty = Type::getInt8Ty(Ctx);
-    const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
-    Type *AccessTy = Val->getType();
-    Value *IntVal = scalarToStoreInt(B, Val);
-    unsigned NumBytes = DL.getTypeStoreSize(AccessTy);
-    Type *IntTy = IntegerType::get(Ctx, DL.getTypeStoreSizeInBits(AccessTy));
-    Value *OldIntVal = ConstantInt::get(IntTy, 0);
-
-    for (unsigned I = 0; I < NumBytes; ++I) {
-      Value *Shifted =
-          I == 0 ? IntVal
-                 : B.CreateLShr(IntVal, ConstantInt::get(IntTy, 8 * I));
-      Value *Byte = B.CreateTrunc(Shifted, I8Ty);
-      buildAssignType(B, I8Ty, Byte);
-      Value *Ptr = gepByteOffset(B, OriginalPtr, I);
-      AtomicRMWInst *OldByte = createMatchingAtomicRMW(B, Template, Ptr, Byte);
-      OldByte->setAlignment(commonAlignment(Alignment, I));
-      buildAssignType(B, I8Ty, OldByte);
-      Value *Extended = B.CreateZExt(OldByte, IntTy);
-      buildAssignType(B, IntTy, Extended);
-      Value *ShiftedOld =
-          I == 0 ? Extended
-                 : B.CreateShl(Extended, ConstantInt::get(IntTy, 8 * I));
-      if (I != 0)
-        buildAssignType(B, IntTy, ShiftedOld);
-      OldIntVal = I == 0 ? ShiftedOld : B.CreateOr(OldIntVal, ShiftedOld);
-      buildAssignType(B, IntTy, OldIntVal);
-    }
-
-    Value *Result = storeIntToScalar(B, OldIntVal, AccessTy);
-    if (Result != OldIntVal)
-      buildAssignType(B, AccessTy, Result);
-    return Result;
-  }
-
-  bool tryReinterpretAtomicRMW(IRBuilder<> &B, AtomicRMWInst *IllegalRMW,
-                               Value *OriginalPtr, Value *CastedPtr) {
-    Type *AccessTy = IllegalRMW->getValOperand()->getType();
-    Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
-    if (!CastedElemTy || CastedElemTy != AccessTy)
-      return false;
-
-    if (shouldReinterpretByteWise(B, AccessTy, OriginalPtr)) {
-      if (!atomicRMWIsByteDecomposable(IllegalRMW->getOperation()))
-        return false;
-      Value *Result = atomicRMWScalarToByteLayout(
-          B, IllegalRMW, OriginalPtr, IllegalRMW->getValOperand(),
-          IllegalRMW->getAlign());
-      GR->replaceAllUsesWith(IllegalRMW, Result, /* DeleteOld= */ true);
-      DeadInstructions.push_back(IllegalRMW);
-      return true;
-    }
-
-    GR->buildAssignPtr(B, AccessTy, OriginalPtr);
-    AtomicRMWInst *NewRMW = createMatchingAtomicRMW(
-        B, IllegalRMW, OriginalPtr, IllegalRMW->getValOperand());
-    NewRMW->setAlignment(IllegalRMW->getAlign());
-    buildAssignType(B, AccessTy, NewRMW);
-    GR->replaceAllUsesWith(IllegalRMW, NewRMW, /* DeleteOld= */ true);
-    DeadInstructions.push_back(IllegalRMW);
-    return true;
-  }
-
   bool tryReinterpretStore(IRBuilder<> &B, Type *AccessTy, Value *OriginalPtr,
                            Value *CastedPtr, Value *StoreSrc, Align Alignment) {
     Type *CastedElemTy = GR->findDeducedElementType(CastedPtr);
@@ -847,14 +760,6 @@ class SPIRVLegalizePointerCastImpl {
     DeadInstructions.push_back(IllegalStore);
   }
 
-  void transformAtomicRMW(IRBuilder<> &B, AtomicRMWInst *IllegalRMW,
-                          Value *CastedOperand, Value *OriginalOperand) {
-    B.SetInsertPoint(IllegalRMW);
-    if (tryReinterpretAtomicRMW(B, IllegalRMW, OriginalOperand, CastedOperand))
-      return;
-    llvm_unreachable("Failed to legalize atomicrmw through ptrcast.");
-  }
-
   void legalizePointerCast(IntrinsicInst *II) {
     Value *CastedOperand = II;
     Value *OriginalOperand = II->getOperand(0);
@@ -876,11 +781,6 @@ class SPIRVLegalizePointerCastImpl {
         continue;
       }
 
-      if (AtomicRMWInst *RMW = dyn_cast<AtomicRMWInst>(User)) {
-        transformAtomicRMW(B, RMW, CastedOperand, OriginalOperand);
-        continue;
-      }
-
       if (IntrinsicInst *Intrin = dyn_cast<IntrinsicInst>(User)) {
         if (Intrin->getIntrinsicID() == Intrinsic::spv_assign_ptr_type) {
           DeadInstructions.push_back(Intrin);
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 761369ef29d08..67e4d6935dd02 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -265,51 +265,3 @@ entry:
   store float %val, ptr addrspace(10) @outF, align 4
   ret void
 }
-
- at outI64 = addrspace(10) global i64 zeroinitializer
-
-; InterlockedOr on RWByteAddressBuffer lowers to atomicrmw or through i8 ptrcast.
-
-define void @byteBufferAtomicOrI32() {
-; CHECK-LABEL: define void @byteBufferAtomicOrI32(
-; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 42 monotonic, align 4
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 2)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 2
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 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)
-  %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
-  %old = atomicrmw or ptr addrspace(11) %ptr, i32 42 monotonic, align 4
-  store i32 %old, ptr addrspace(10) @outI32, align 4
-  ret void
-}
-
-define void @byteBufferAtomicOrI64() {
-; CHECK-LABEL: define void @byteBufferAtomicOrI64(
-; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 42 monotonic, align 8
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 2)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 2
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 4)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 4
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 5)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 1
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 6)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 2
-; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 7)
-; CHECK: atomicrmw or ptr addrspace(11) {{.*}}, i8 0 monotonic, align 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)
-  %ptr = call ptr addrspace(11) @llvm.spv.resource.getpointer(target("spirv.VulkanBuffer", [0 x i8], 12, 0) %handle, i32 0)
-  %old = atomicrmw or ptr addrspace(11) %ptr, i64 42 monotonic, align 8
-  store i64 %old, ptr addrspace(10) @outI64, align 8
-  ret void
-}

>From 8acb946b641ef936335c362db3e5f8170084a63f Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Mon, 17 Aug 2026 17:25:24 +0100
Subject: [PATCH 16/17] [SPIRV] Add i16 and mixed-type byte-buffer ptrcast
 tests

Extend SPIRVLegalizePointerCast coverage for i16 scalar/vector
byte-wise reinterpretation and mixed i32/i16 accesses on one pointer.

Co-authored-by: Cursor
---
 .../SPIRV/passes/SPIRVLegalizePointerCast.ll  | 96 +++++++++++++++++++
 1 file changed, 96 insertions(+)

diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 67e4d6935dd02..49a319b6d5239 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -265,3 +265,99 @@ entry:
   store float %val, ptr addrspace(10) @outF, align 4
   ret void
 }
+
+ at outI16 = addrspace(10) global i16 zeroinitializer, align 2
+ at outV2I16 = addrspace(10) global <2 x i16> zeroinitializer, align 4
+
+; i16 access uses byte-wise decomposition (2 bytes).
+
+define void @byteBufferStoreI16() {
+; CHECK-LABEL: define void @byteBufferStoreI16(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; 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)
+  store i16 42, ptr addrspace(11) %ptr, align 2
+  ret void
+}
+
+define void @byteBufferLoadI16() {
+; CHECK-LABEL: define void @byteBufferLoadI16(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: zext i8 {{.*}} to i16
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: store i16 {{.*}}, ptr addrspace(10) @outI16
+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 i16, ptr addrspace(11) %ptr, align 2
+  store i16 %val, ptr addrspace(10) @outI16, align 2
+  ret void
+}
+
+; <2 x i16> vector access decomposes per element (4 bytes total).
+
+define void @byteBufferStoreV2I16() {
+; CHECK-LABEL: define void @byteBufferStoreV2I16(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: trunc i16 {{.*}} to i8
+; CHECK: store i8 {{.*}}, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
+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 <2 x i16> <i16 1, i16 2>, ptr addrspace(11) %ptr, align 4
+  ret void
+}
+
+define void @byteBufferLoadV2I16() {
+; CHECK-LABEL: define void @byteBufferLoadV2I16(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: load i8, ptr addrspace(11)
+; CHECK: zext i8 {{.*}} to i16
+; CHECK: call {{.*}}@llvm.spv.insertelt
+; CHECK: call void @llvm.spv.store.v2i16.p10
+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 <2 x i16>, ptr addrspace(11) %ptr, align 4
+  store <2 x i16> %val, ptr addrspace(10) @outV2I16, align 4
+  ret void
+}
+
+; Same base pointer, i32 then i16 typed accesses (distinct ptrcasts).
+
+define void @byteBufferMixedI32ThenI16Store() {
+; CHECK-LABEL: define void @byteBufferMixedI32ThenI16Store(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: store i8 1, ptr addrspace(11)
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 3)
+; CHECK: store i8 2, 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 1, ptr addrspace(11) %ptr, align 4
+  store i16 2, ptr addrspace(11) %ptr, align 2
+  ret void
+}
+
+define void @byteBufferMixedLoadI32ThenI16() {
+; CHECK-LABEL: define void @byteBufferMixedLoadI32ThenI16(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: zext i8 {{.*}} to i32
+; CHECK: call ptr addrspace(11) @llvm.spv.resource.getpointer{{.*}} i32 1)
+; CHECK: zext i8 {{.*}} to i16
+; CHECK: shl i16
+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)
+  %i32val = load i32, ptr addrspace(11) %ptr, align 4
+  %i16val = load i16, ptr addrspace(11) %ptr, align 2
+  store i32 %i32val, ptr addrspace(10) @outI32, align 4
+  store i16 %i16val, ptr addrspace(10) @outI16, align 2
+  ret void
+}

>From b0d83275564bd5f28fe347b553adb87ac50d8f65 Mon Sep 17 00:00:00 2001
From: Tim Corringham <tcorring at amd.com>
Date: Tue, 25 Aug 2026 12:00:42 +0100
Subject: [PATCH 17/17] Address review comments

Refactored code in SPIRVLegalizePointerCast.cpp, and trimmed comments
in SPIRVLegalizePointerCast.ll
---
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 73 ++++++++++---------
 .../SPIRV/passes/SPIRVLegalizePointerCast.ll  | 13 +---
 2 files changed, 42 insertions(+), 44 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index f2e4c354a251f..795c8b7f78979 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -233,29 +233,29 @@ class SPIRVLegalizePointerCastImpl {
     if (ByteOffset == 0)
       return BasePtr;
 
-    IntrinsicInst *ResourcePtr = getResourceGetPointer(BasePtr);
-    assert(ResourcePtr &&
-           "byte layout pointer must come from spv.resource.getpointer");
-
-    Value *Handle = ResourcePtr->getOperand(0);
-    Value *BaseOffset = ResourcePtr->getOperand(1);
-    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> OpBundles;
-    ResourcePtr->getOperandBundlesAsDefs(OpBundles);
-    CallInst *ResourcePtrAtOffset = B.CreateCall(
-        ResourcePtr->getFunctionType(), ResourcePtr->getCalledOperand(),
-        {Handle, NewOffset}, OpBundles);
-    ResourcePtrAtOffset->setAttributes(ResourcePtr->getAttributes());
-    ResourcePtrAtOffset->setCallingConv(ResourcePtr->getCallingConv());
-    Type *I8Ty = Type::getInt8Ty(B.getContext());
-    GR->buildAssignPtr(B, I8Ty, ResourcePtrAtOffset);
-    return ResourcePtrAtOffset;
+    if (IntrinsicInst *ResourcePtr = getResourceGetPointer(BasePtr)) {
+      Value *Handle = ResourcePtr->getOperand(0);
+      Value *BaseOffset = ResourcePtr->getOperand(1);
+      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> OpBundles;
+      ResourcePtr->getOperandBundlesAsDefs(OpBundles);
+      CallInst *ResourcePtrAtOffset = B.CreateCall(
+          ResourcePtr->getFunctionType(), ResourcePtr->getCalledOperand(),
+          {Handle, NewOffset}, OpBundles);
+      ResourcePtrAtOffset->setAttributes(ResourcePtr->getAttributes());
+      ResourcePtrAtOffset->setCallingConv(ResourcePtr->getCallingConv());
+      Type *I8Ty = Type::getInt8Ty(B.getContext());
+      GR->buildAssignPtr(B, I8Ty, ResourcePtrAtOffset);
+      return ResourcePtrAtOffset;
+    }
+    llvm_unreachable(
+        "byte layout pointer must come from spv.resource.getpointer");
   }
 
   Value *scalarToStoreInt(IRBuilder<> &B, Value *Scalar) {
@@ -286,16 +286,21 @@ class SPIRVLegalizePointerCastImpl {
     Value *IntVal = scalarToStoreInt(B, Src);
     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));
+    auto StoreByte = [&](unsigned I, Value *Shifted) {
       Value *Byte = B.CreateTrunc(Shifted, I8Ty);
       buildAssignType(B, I8Ty, Byte);
       Value *Ptr = gepByteOffset(B, Dst, I);
       StoreInst *SI = B.CreateStore(Byte, Ptr);
       SI->setAlignment(commonAlignment(Alignment, I));
+    };
+
+    if (NumBytes > 0)
+      StoreByte(0, IntVal);
+
+    for (unsigned I = 1; I < NumBytes; ++I) {
+      Value *Shifted =
+          B.CreateLShr(IntVal, ConstantInt::get(IntVal->getType(), 8 * I));
+      StoreByte(I, Shifted);
     }
   }
 
@@ -315,12 +320,14 @@ class SPIRVLegalizePointerCastImpl {
       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)
+
+      if (I == 0) {
+        IntVal = Extended;
+      } else {
+        Value *Shifted = B.CreateShl(Extended, ConstantInt::get(IntTy, 8 * I));
         buildAssignType(B, IntTy, Shifted);
-      IntVal = I == 0 ? Shifted : B.CreateOr(IntVal, Shifted);
+        IntVal = B.CreateOr(IntVal, Shifted);
+      }
       buildAssignType(B, IntTy, IntVal);
     }
 
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
index 49a319b6d5239..59171011d72b2 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -82,14 +82,8 @@ 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 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.
+; Byte-addressable buffer tests model HLSL ByteAddressBuffer layout ([0 x i8] handles,
+; typed load/store via getpointer byte offset). emit-intrinsics adds ptrcast; legalize-pointer-cast removes it.
 
 define void @byteBufferStore() {
 ; CHECK-LABEL: define void @byteBufferStore(
@@ -119,9 +113,6 @@ entry:
 
 @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 void @byteBufferStoreViaLoadedHandle() {
 ; CHECK-LABEL: define void @byteBufferStoreViaLoadedHandle(
 ; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast



More information about the llvm-commits mailing list