[llvm] [SPIR-V] Use `OpImageFetch` instead of `OpImageRead` when loading from read-only `Buffer` resource. (PR #163626)

Lucie Choi via llvm-commits llvm-commits at lists.llvm.org
Wed Oct 15 13:32:29 PDT 2025


https://github.com/luciechoi created https://github.com/llvm/llvm-project/pull/163626

Currently, the spir-v validator fails if `OpImageRead` instruction is used when loading from read-only `Buffer`. 
### Unit Test
```hlsl
RWBuffer<uint> rwbuff;
Buffer<uint>  buff;

[numthreads(1,1,1)]
void main() {
    rwbuff[99] = buff[98];
    rwbuff[97] = rwbuff[96];
}
```
This also unblocks adding a test case that adds a special extension when using a non-uniform index on `Buffer` arrays. (https://github.com/llvm/llvm-project/pull/162540).

Resolves https://github.com/llvm/llvm-project/issues/162891

>From d77195c3fcd66a03fc3e4c7f573d5b851f011cc7 Mon Sep 17 00:00:00 2001
From: luciechoi <ychoi0407 at gmail.com>
Date: Thu, 2 Oct 2025 19:29:51 +0000
Subject: [PATCH 1/3] Adding debug configs

---
 .vscode/launch.json | 19 +++++++++++++
 .vscode/tasks.json  | 69 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 88 insertions(+)
 create mode 100644 .vscode/launch.json
 create mode 100644 .vscode/tasks.json

diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000000..00088ca83e535
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,19 @@
+{
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Launch clang-dxc",
+      "type": "cppdbg",
+      "request": "launch",
+      "program": "${workspaceFolder}/build-debug/bin/clang-dxc",
+      "args": [
+        "${workspaceFolder}/issues/asdouble.hlsl",
+        "-T",
+        "cs_6_8",
+        "-spirv"
+      ],
+      "cwd": "${workspaceFolder}",
+      "stopAtEntry": false
+    }
+  ]
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000000000..3ec7716957501
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,69 @@
+// cmake ~/llvm-project/llvm -GNinja -DCMAKE_BUILD_TYPE=Debug \
+//             -DLLVM_ENABLE_PROJECTS='clang' \
+//             -DLLVM_TARGETS_TO_BUILD='X86;SPIRV' \
+//             -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD='DirectX' \
+//             -DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON \
+//             -DLLVM_OPTIMIZED_TABLEGEN=1 \
+//             -DLLVM_ENABLE_LLD=1 \
+//             -DLLVM_USE_SPLIT_DWARF=1 \
+//             -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang \
+//             -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+//             -DCMAKE_INSTALL_PREFIX=run
+
+{
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "label": "delete build",
+      "type": "shell",
+      "command": "rm",
+      "args": [
+        "-rf",
+        "~/llvm-project/build-debug/"
+      ]
+    },
+    {
+      "label": "generate build",
+      "type": "shell",
+      "command": "cmake",
+      "args": [
+		"-B", "~/llvm-project/build-debug/",
+        "-S", "~/llvm-project/llvm",
+        "-GNinja",
+        "-DCMAKE_BUILD_TYPE=Debug",
+		"-DLLVM_ENABLE_PROJECTS='clang'",
+		"-DLLVM_TARGETS_TO_BUILD='X86;SPIRV'",
+		"-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD='DirectX'",
+		"-DLLVM_INCLUDE_SPIRV_TOOLS_TESTS=ON",
+		"-DLLVM_OPTIMIZED_TABLEGEN=1",
+		"-DLLVM_ENABLE_LLD=1",
+		"-DLLVM_USE_SPLIT_DWARF=1",
+		"-DCMAKE_CXX_COMPILER=clang++",
+		"-DCMAKE_C_COMPILER=clang",
+		"-DCMAKE_EXPORT_COMPILE_COMMANDS=1",
+		"-DCMAKE_INSTALL_PREFIX=run"
+      ],
+    },
+    {
+      "label": "build",
+      "type": "shell",
+      "command": "cd ~/llvm-project/build-debug && ninja",
+      "args": [],
+    },
+    {
+      "label": "link paths",
+      "type": "shell",
+      "command": "cd ~/llvm-project/build-debug && ln -s clang clang-dxc",
+      "args": [],
+    },
+    {
+      "label": "CMake: BUILD (Debug)",
+      "dependsOn": ["generate build", "build", "link paths"],
+      "dependsOrder": "sequence",
+      "group": {
+        "kind": "build",
+        "isDefault": true
+      }
+    }
+  ]
+}
\ No newline at end of file

>From 3260a667bffd9b07ab1bb9c11809103c715402bb Mon Sep 17 00:00:00 2001
From: luciechoi <ychoi0407 at gmail.com>
Date: Mon, 6 Oct 2025 19:38:26 +0000
Subject: [PATCH 2/3] Update tasks.json

---
 .vscode/tasks.json | 77 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 74 insertions(+), 3 deletions(-)

diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 3ec7716957501..e63624c4714c3 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -20,7 +20,11 @@
       "args": [
         "-rf",
         "~/llvm-project/build-debug/"
-      ]
+      ],
+      "group": {
+        "kind": "build",
+        "isDefault": false
+      }
     },
     {
       "label": "generate build",
@@ -48,22 +52,89 @@
       "label": "build",
       "type": "shell",
       "command": "cd ~/llvm-project/build-debug && ninja",
-      "args": [],
+      "args": []
     },
     {
       "label": "link paths",
       "type": "shell",
       "command": "cd ~/llvm-project/build-debug && ln -s clang clang-dxc",
       "args": [],
+      "group": {
+        "kind": "build",
+        "isDefault": false
+      }
     },
     {
       "label": "CMake: BUILD (Debug)",
-      "dependsOn": ["generate build", "build", "link paths"],
+      "dependsOn": ["generate build", "build"],
       "dependsOrder": "sequence",
       "group": {
         "kind": "build",
         "isDefault": true
       }
+    },
+    {
+      "label": "run clang-dxc",
+      "type": "shell",
+      "command": "~/llvm-project/build-debug/bin/clang-dxc",
+      "args": [
+        "${input:hlslFile}",
+        "-T", "cs_6_8",
+        "-spirv",
+        ">",
+        "${input:outputFile}",
+        "2>&1"
+      ],
+      "group": {
+        "kind": "test"
+      }
+    },
+    {
+      "label": "run clang-dxc and emit llvm",
+      "type": "shell",
+      "command": "~/llvm-project/build-debug/bin/clang-dxc",
+      "args": [
+        "${input:hlslFile}",
+        "-T", "cs_6_8",
+        "-spirv",
+        "-Xclang", "-emit-llvm",
+        ">",
+        "${input:outputFile}",
+        "2>&1"
+      ],
+      "group": {
+        "kind": "test"
+      }
+    },
+    {
+      "label": "run clang-dxc and print all passes",
+      "type": "shell",
+      "command": "~/llvm-project/build-debug/bin/clang-dxc",
+      "args": [
+        "${input:hlslFile}",
+        "-T", "cs_6_8",
+        "-spirv",
+        "-mllvm", "-print-before-all",
+        "-mllvm", "--debug-only=spirv-module-analysis",
+        ">", "${input:outputFile}", "2>&1"
+      ],
+      "group": {
+        "kind": "test"
+      }
+    },
+  ],
+  "inputs": [
+    {
+      "type": "promptString",
+      "id": "hlslFile",
+      "description": "Path to your hlsl file to test",
+      "default": ""
+    },
+    {
+      "type": "promptString",
+      "id": "outputFile",
+      "description": "Path to your output file",
+      "default": ""
     }
   ]
 }
\ No newline at end of file

>From 8d038bcaeae1e6c318e5aca19d5f83ca737370a6 Mon Sep 17 00:00:00 2001
From: luciechoi <ychoi0407 at gmail.com>
Date: Wed, 15 Oct 2025 20:27:15 +0000
Subject: [PATCH 3/3] [SPIR-V] Use OpImageFetch instruction when loading from
 read-only buffer resource.

---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 49 +++++++++++--------
 .../SPIRV/hlsl-resources/TypedBufferLoad.ll   | 36 ++++++++++++++
 2 files changed, 64 insertions(+), 21 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/TypedBufferLoad.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 989950fb8f8b5..d9a4619f3da90 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -352,9 +352,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
   SPIRVType *widenTypeToVec4(const SPIRVType *Type, MachineInstr &I) const;
   bool extractSubvector(Register &ResVReg, const SPIRVType *ResType,
                         Register &ReadReg, MachineInstr &InsertionPoint) const;
-  bool generateImageRead(Register &ResVReg, const SPIRVType *ResType,
-                         Register ImageReg, Register IdxReg, DebugLoc Loc,
-                         MachineInstr &Pos) const;
+  bool generateImageReadOrFetch(Register &ResVReg, const SPIRVType *ResType,
+                                Register ImageReg, Register IdxReg,
+                                DebugLoc Loc, MachineInstr &Pos) const;
   bool BuildCOPY(Register DestReg, Register SrcReg, MachineInstr &I) const;
   bool loadVec3BuiltinInputID(SPIRV::BuiltIn::BuiltIn BuiltInValue,
                               Register ResVReg, const SPIRVType *ResType,
@@ -1317,8 +1317,8 @@ bool SPIRVInstructionSelector::selectLoad(Register ResVReg,
       }
 
       Register IdxReg = IntPtrDef->getOperand(3).getReg();
-      return generateImageRead(ResVReg, ResType, NewHandleReg, IdxReg,
-                               I.getDebugLoc(), I);
+      return generateImageReadOrFetch(ResVReg, ResType, NewHandleReg, IdxReg,
+                                      I.getDebugLoc(), I);
     }
   }
 
@@ -3633,27 +3633,32 @@ bool SPIRVInstructionSelector::selectReadImageIntrinsic(
   DebugLoc Loc = I.getDebugLoc();
   MachineInstr &Pos = I;
 
-  return generateImageRead(ResVReg, ResType, NewImageReg, IdxReg, Loc, Pos);
+  return generateImageReadOrFetch(ResVReg, ResType, NewImageReg, IdxReg, Loc,
+                                  Pos);
 }
 
-bool SPIRVInstructionSelector::generateImageRead(Register &ResVReg,
-                                                 const SPIRVType *ResType,
-                                                 Register ImageReg,
-                                                 Register IdxReg, DebugLoc Loc,
-                                                 MachineInstr &Pos) const {
+bool SPIRVInstructionSelector::generateImageReadOrFetch(
+    Register &ResVReg, const SPIRVType *ResType, Register ImageReg,
+    Register IdxReg, DebugLoc Loc, MachineInstr &Pos) const {
   SPIRVType *ImageType = GR.getSPIRVTypeForVReg(ImageReg);
   assert(ImageType && ImageType->getOpcode() == SPIRV::OpTypeImage &&
          "ImageReg is not an image type.");
+
   bool IsSignedInteger =
       sampledTypeIsSignedInteger(GR.getTypeForSPIRVType(ImageType));
+  // Check if the "sampled" operand of the image type is 2.
+  auto SampledOp = ImageType->getOperand(6);
+  bool IsFetch = (SampledOp.getImm() != 2);
 
   uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType);
   if (ResultSize == 4) {
-    auto BMI = BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpImageRead))
-                   .addDef(ResVReg)
-                   .addUse(GR.getSPIRVTypeID(ResType))
-                   .addUse(ImageReg)
-                   .addUse(IdxReg);
+    auto BMI =
+        BuildMI(*Pos.getParent(), Pos, Loc,
+                TII.get(IsFetch ? SPIRV::OpImageFetch : SPIRV::OpImageRead))
+            .addDef(ResVReg)
+            .addUse(GR.getSPIRVTypeID(ResType))
+            .addUse(ImageReg)
+            .addUse(IdxReg);
 
     if (IsSignedInteger)
       BMI.addImm(0x1000); // SignExtend
@@ -3662,11 +3667,13 @@ bool SPIRVInstructionSelector::generateImageRead(Register &ResVReg,
 
   SPIRVType *ReadType = widenTypeToVec4(ResType, Pos);
   Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType));
-  auto BMI = BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpImageRead))
-                 .addDef(ReadReg)
-                 .addUse(GR.getSPIRVTypeID(ReadType))
-                 .addUse(ImageReg)
-                 .addUse(IdxReg);
+  auto BMI =
+      BuildMI(*Pos.getParent(), Pos, Loc,
+              TII.get(IsFetch ? SPIRV::OpImageFetch : SPIRV::OpImageRead))
+          .addDef(ReadReg)
+          .addUse(GR.getSPIRVTypeID(ReadType))
+          .addUse(ImageReg)
+          .addUse(IdxReg);
   if (IsSignedInteger)
     BMI.addImm(0x1000); // SignExtend
   bool Succeed = BMI.constrainAllUses(TII, TRI, RBI);
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/TypedBufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/TypedBufferLoad.ll
new file mode 100644
index 0000000000000..cbb9d11e406b5
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/TypedBufferLoad.ll
@@ -0,0 +1,36 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
+
+; When accessing read-only `Buffer` types, SPIR-V should use `OpImageFetch` instead of `OpImageRead`.
+; https://github.com/llvm/llvm-project/issues/162891
+
+; CHECK-DAG: OpCapability SampledBuffer
+; CHECK-DAG: OpCapability ImageBuffer
+; CHECK-DAG: [[TypeInt:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[TypeImageBuffer:%[0-9]+]] = OpTypeImage [[TypeInt]] Buffer 2 0 0 1 Unknown
+; CHECK-DAG: [[TypePtrImageBuffer:%[0-9]+]] = OpTypePointer UniformConstant [[TypeImageBuffer]]
+; CHECK-DAG: [[TypeVector:%[0-9]+]] = OpTypeVector [[TypeInt]] 4
+; CHECK-DAG: [[Index:%[0-9]+]] = OpConstant [[TypeInt]] 98
+; CHECK-DAG: [[Variable:%[0-9]+]] = OpVariable [[TypePtrImageBuffer]] UniformConstant
+ at .str = private unnamed_addr constant [7 x i8] c"rwbuff\00", align 1
+ at .str.2 = private unnamed_addr constant [5 x i8] c"buff\00", align 1
+
+define void @main() local_unnamed_addr #0 {
+entry:
+  %0 = tail call target("spirv.Image", i32, 5, 2, 0, 0, 2, 33) @llvm.spv.resource.handlefromimplicitbinding.tspirv.Image_i32_5_2_0_0_2_33t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+  %1 = tail call target("spirv.Image", i32, 5, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.Image_i32_5_2_0_0_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str.2)
+  %2 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_i32_5_2_0_0_1_0t(target("spirv.Image", i32, 5, 2, 0, 0, 1, 0) %1, i32 98)
+; CHECK: [[Load:%[0-9]+]] = OpLoad [[TypeImageBuffer]] [[Variable]]
+; CHECK: [[ImageFetch:%[0-9]+]] = OpImageFetch [[TypeVector]] [[Load]] [[Index]]
+; CHECK: {{.*}} = OpCompositeExtract [[TypeInt]] [[ImageFetch]] 0
+  %3 = load i32, ptr addrspace(11) %2, align 4
+  %4 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_i32_5_2_0_0_2_33t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 33) %0, i32 99)
+  store i32 %3, ptr addrspace(11) %4, align 4
+  %5 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_i32_5_2_0_0_2_33t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 33) %0, i32 96)
+; CHECK: {{%[0-9]+}} = OpLoad {{.*}}
+; CHECK: {{%[0-9]+}} = OpImageRead {{.*}}
+  %6 = load i32, ptr addrspace(11) %5, align 4
+  %7 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_i32_5_2_0_0_2_33t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 33) %0, i32 97)
+  store i32 %6, ptr addrspace(11) %7, align 4
+  ret void
+}



More information about the llvm-commits mailing list