Skip to content

Commit 756fe54

Browse files
authored
[SPIRV] Add write to image buffer for shaders. (#115927)
This commit adds an intrinsic that will write to an image buffer. We chose to match the name of the DXIL intrinsic for simplicity in clang. We cannot reuse the existing openCL write_image function because that is not a reserved name in HLSL. There is not much common code to factor out.
1 parent 3250612 commit 756fe54

File tree

6 files changed

+121
-1
lines changed

6 files changed

+121
-1
lines changed

llvm/docs/SPIRVUsage.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,11 @@ SPIR-V backend, along with their descriptions and argument details.
402402
return type is a scalar, then the first element of the vector is \
403403
returned. If the return type is an n-element vector, then the first \
404404
n-elements of the 4-element vector are returned.
405+
* - `int_spv_typedBufferStore`
406+
- void
407+
- `[spirv.Image Image, 32-bit Integer coordinate, vec4 data]`
408+
- Stores the data to the image buffer at the given coordinate. The \
409+
data must be a 4-element vector.
405410

406411
.. _spirv-builtin-functions:
407412

llvm/include/llvm/IR/IntrinsicsSPIRV.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,10 @@ let TargetPrefix = "spv" in {
113113
// vector.
114114
def int_spv_typedBufferLoad
115115
: DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_i32_ty]>;
116+
117+
// Write a value to the image buffer. Translates directly to a single
118+
// OpImageWrite.
119+
def int_spv_typedBufferStore
120+
: DefaultAttrsIntrinsic<[], [llvm_any_ty, llvm_i32_ty, llvm_anyvector_ty]>;
121+
116122
}

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ class SPIRVInstructionSelector : public InstructionSelector {
274274
void selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType,
275275
MachineInstr &I) const;
276276

277+
void selectImageWriteIntrinsic(MachineInstr &I) const;
278+
277279
// Utilities
278280
std::pair<Register, bool>
279281
buildI32Constant(uint32_t Val, MachineInstr &I,
@@ -2878,6 +2880,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
28782880
case Intrinsic::spv_handle_fromBinding: {
28792881
return selectHandleFromBinding(ResVReg, ResType, I);
28802882
}
2883+
case Intrinsic::spv_typedBufferStore: {
2884+
selectImageWriteIntrinsic(I);
2885+
return true;
2886+
}
28812887
case Intrinsic::spv_typedBufferLoad: {
28822888
selectReadImageIntrinsic(ResVReg, ResType, I);
28832889
return true;
@@ -3000,6 +3006,27 @@ void SPIRVInstructionSelector::extractSubvector(
30003006
MIB.addUse(ComponentReg);
30013007
}
30023008

3009+
void SPIRVInstructionSelector::selectImageWriteIntrinsic(
3010+
MachineInstr &I) const {
3011+
// If the load of the image is in a different basic block, then
3012+
// this will generate invalid code. A proper solution is to move
3013+
// the OpLoad from selectHandleFromBinding here. However, to do
3014+
// that we will need to change the return type of the intrinsic.
3015+
// We will do that when we can, but for now trying to move forward with other
3016+
// issues.
3017+
Register ImageReg = I.getOperand(1).getReg();
3018+
assert(MRI->getVRegDef(ImageReg)->getParent() == I.getParent() &&
3019+
"The image must be loaded in the same basic block as its use.");
3020+
Register CoordinateReg = I.getOperand(2).getReg();
3021+
Register DataReg = I.getOperand(3).getReg();
3022+
assert(GR.getResultType(DataReg)->getOpcode() == SPIRV::OpTypeVector);
3023+
assert(GR.getScalarOrVectorComponentCount(GR.getResultType(DataReg)) == 4);
3024+
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageWrite))
3025+
.addUse(ImageReg)
3026+
.addUse(CoordinateReg)
3027+
.addUse(DataReg);
3028+
}
3029+
30033030
Register SPIRVInstructionSelector::buildPointerToResource(
30043031
const SPIRVType *ResType, uint32_t Set, uint32_t Binding,
30053032
uint32_t ArraySize, Register IndexReg, bool IsNonUniform,

llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ void RequirementHandler::initAvailableCapabilitiesForOpenCL(
662662
addAvailableCaps({Capability::Addresses, Capability::Float16Buffer,
663663
Capability::Kernel, Capability::Vector16,
664664
Capability::Groups, Capability::GenericPointer,
665+
Capability::StorageImageWriteWithoutFormat,
665666
Capability::StorageImageReadWithoutFormat});
666667
if (ST.hasOpenCLFullProfile())
667668
addAvailableCaps({Capability::Int64, Capability::Int64Atomics});
@@ -724,7 +725,8 @@ void RequirementHandler::initAvailableCapabilitiesForVulkan(
724725

725726
// Became core in Vulkan 1.3
726727
if (ST.isAtLeastSPIRVVer(VersionTuple(1, 6)))
727-
addAvailableCaps({Capability::StorageImageReadWithoutFormat});
728+
addAvailableCaps({Capability::StorageImageWriteWithoutFormat,
729+
Capability::StorageImageReadWithoutFormat});
728730
}
729731

730732
} // namespace SPIRV
@@ -1444,6 +1446,13 @@ void addInstrRequirements(const MachineInstr &MI,
14441446
Reqs.addCapability(SPIRV::Capability::StorageImageReadWithoutFormat);
14451447
break;
14461448
}
1449+
case SPIRV::OpImageWrite: {
1450+
Register ImageReg = MI.getOperand(0).getReg();
1451+
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
1452+
if (isImageTypeWithUnknownFormat(TypeDef))
1453+
Reqs.addCapability(SPIRV::Capability::StorageImageWriteWithoutFormat);
1454+
break;
1455+
}
14471456

14481457
default:
14491458
break;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
; RUN: llc -O3 -verify-machineinstrs -mtriple=spirv-vulkan-library %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-library %s -o - -filetype=obj | spirv-val %}
3+
4+
; CHECK-NOT: OpCapability StorageImageReadWithoutFormat
5+
6+
; CHECK-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16
7+
; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7
8+
9+
; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
10+
; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0
11+
; CHECK-DAG: [[v4_int:%[0-9]+]] = OpTypeVector [[int]] 4
12+
; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 R32i {{$}}
13+
; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]]
14+
; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant
15+
16+
17+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
18+
declare <4 x i32> @get_data() #1
19+
20+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
21+
; CHECK-NEXT: OpLabel
22+
define void @RWBufferStore_Vec4_I32() #0 {
23+
; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
24+
%buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
25+
@llvm.spv.handle.fromBinding.tspirv.Image_i32_5_2_0_0_2_24(
26+
i32 16, i32 7, i32 1, i32 0, i1 false)
27+
28+
; CHECK: [[data:%[0-9]+]] = OpFunctionCall
29+
%data = call <4 x i32> @get_data()
30+
; CHECK: OpImageWrite [[buffer]] [[zero]] [[data]]
31+
call void @llvm.spv.typedBufferStore(target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0, <4 x i32> %data)
32+
33+
ret void
34+
}
35+
36+
attributes #0 = { convergent noinline norecurse "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
37+
attributes #1 = { convergent noinline norecurse "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
2+
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
3+
4+
; CHECK: OpCapability StorageImageWriteWithoutFormat
5+
; CHECK-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16
6+
; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7
7+
8+
; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
9+
; CHECK-DAG: [[ten:%[0-9]+]] = OpConstant [[int]] 10
10+
; CHECK-DAG: [[v4_int:%[0-9]+]] = OpTypeVector [[int]] 4
11+
; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 Unknown {{$}}
12+
; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]]
13+
; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant
14+
15+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
16+
declare <4 x i32> @get_data() #1
17+
18+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
19+
; CHECK-NEXT: OpLabel
20+
define void @RWBufferLoad_Vec4_I32() #0 {
21+
; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
22+
%buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 0)
23+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_0(
24+
i32 16, i32 7, i32 1, i32 0, i1 false)
25+
26+
; CHECK: [[data:%[0-9]+]] = OpFunctionCall
27+
%data = call <4 x i32> @get_data()
28+
; CHECK: OpImageWrite [[buffer]] [[ten]] [[data]]
29+
call void @llvm.spv.typedBufferStore(
30+
target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) %buffer0, i32 10, <4 x i32> %data)
31+
32+
ret void
33+
}
34+
35+
attributes #0 = { convergent noinline norecurse "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
36+
attributes #1 = { convergent noinline norecurse "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }

0 commit comments

Comments
 (0)