[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