Skip to content

Commit f629570

Browse files
committed
[SPIRV] Add reads from image buffer for shaders.
This commit adds an intrinsic that will read from an image buffer. We chose to match the name of the DXIL intrinsic for simplicity in clang. We cannot reuse the existing openCL readimage function because that is not a reserved name in HLSL. I considered trying to refactor generateReadImageInst, so that we could share code between the two implementations. However, most of the code in generateReadImageInst is concerned with trying to figure out which type of image read is being done. Once we factor out the code that will be common, then we end up with just a single call to the MIRBuilder being common.
1 parent e236a52 commit f629570

File tree

8 files changed

+259
-0
lines changed

8 files changed

+259
-0
lines changed

llvm/include/llvm/IR/IntrinsicsSPIRV.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,10 @@ let TargetPrefix = "spv" in {
101101
[IntrNoMem]>;
102102
def int_spv_firstbituhigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
103103
def int_spv_firstbitshigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
104+
105+
// Read a value from the image buffer. It does not translate directly to a
106+
// single OpImageRead because the result type is not necessarily a 4 element
107+
// vector.
108+
def int_spv_typedBufferLoad
109+
: DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_i32_ty]>;
104110
}

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,11 @@ SPIRVGlobalRegistry::getSPIRVTypeForVReg(Register VReg,
10561056
return nullptr;
10571057
}
10581058

1059+
SPIRVType *SPIRVGlobalRegistry::getResultType(Register VReg) {
1060+
MachineInstr *Instr = CurMF->getRegInfo().getVRegDef(VReg);
1061+
return getSPIRVTypeForVReg(Instr->getOperand(1).getReg());
1062+
}
1063+
10591064
SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVType(
10601065
const Type *Ty, MachineIRBuilder &MIRBuilder,
10611066
SPIRV::AccessQualifier::AccessQualifier AccessQual, bool EmitIR) {
@@ -1126,6 +1131,21 @@ SPIRVGlobalRegistry::getScalarOrVectorComponentCount(SPIRVType *Type) const {
11261131
: 1;
11271132
}
11281133

1134+
SPIRVType *
1135+
SPIRVGlobalRegistry::getScalarOrVectorComponentType(Register VReg) const {
1136+
return getScalarOrVectorComponentType(getSPIRVTypeForVReg(VReg));
1137+
}
1138+
1139+
SPIRVType *
1140+
SPIRVGlobalRegistry::getScalarOrVectorComponentType(SPIRVType *Type) const {
1141+
if (!Type)
1142+
return nullptr;
1143+
Register ScalarReg = Type->getOpcode() == SPIRV::OpTypeVector
1144+
? Type->getOperand(1).getReg()
1145+
: Type->getOperand(0).getReg();
1146+
return getSPIRVTypeForVReg(ScalarReg);
1147+
}
1148+
11291149
unsigned
11301150
SPIRVGlobalRegistry::getScalarOrVectorBitWidth(const SPIRVType *Type) const {
11311151
assert(Type && "Invalid Type pointer");

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ class SPIRVGlobalRegistry {
353353
SPIRVType *getSPIRVTypeForVReg(Register VReg,
354354
const MachineFunction *MF = nullptr) const;
355355

356+
// Return the result type of the instruction defining the register.
357+
SPIRVType *getResultType(Register VReg);
358+
356359
// Whether the given VReg has a SPIR-V type mapped to it yet.
357360
bool hasSPIRVTypeForVReg(Register VReg) const {
358361
return getSPIRVTypeForVReg(VReg) != nullptr;
@@ -388,6 +391,12 @@ class SPIRVGlobalRegistry {
388391
unsigned getScalarOrVectorComponentCount(Register VReg) const;
389392
unsigned getScalarOrVectorComponentCount(SPIRVType *Type) const;
390393

394+
// Return the component type in a vector if the argument is associated with
395+
// a vector type. Returns the argument itself for a scalar type, and nullptr
396+
// for a missing type.
397+
SPIRVType *getScalarOrVectorComponentType(Register VReg) const;
398+
SPIRVType *getScalarOrVectorComponentType(SPIRVType *Type) const;
399+
391400
// For vectors or scalars of booleans, integers and floats, return the scalar
392401
// type's bitwidth. Otherwise calls llvm_unreachable().
393402
unsigned getScalarOrVectorBitWidth(const SPIRVType *Type) const;

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
264264
void selectHandleFromBinding(Register &ResVReg, const SPIRVType *ResType,
265265
MachineInstr &I) const;
266266

267+
void selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType,
268+
MachineInstr &I) const;
269+
267270
// Utilities
268271
Register buildI32Constant(uint32_t Val, MachineInstr &I,
269272
const SPIRVType *ResType = nullptr) const;
@@ -288,6 +291,12 @@ class SPIRVInstructionSelector : public InstructionSelector {
288291
uint32_t Binding, uint32_t ArraySize,
289292
Register IndexReg, bool IsNonUniform,
290293
MachineIRBuilder MIRBuilder) const;
294+
SPIRVType *getCorrespondingVec4Type(const SPIRVType *Type,
295+
MachineInstr &I) const;
296+
void extractScalarOrVectorFromVector(Register &ResultReg,
297+
const SPIRVType *ResType,
298+
Register &InputReg,
299+
MachineInstr &InsertionPoint) const;
291300
};
292301

293302
} // end anonymous namespace
@@ -2762,6 +2771,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
27622771
selectHandleFromBinding(ResVReg, ResType, I);
27632772
return true;
27642773
}
2774+
case Intrinsic::spv_typedBufferLoad: {
2775+
selectReadImageIntrinsic(ResVReg, ResType, I);
2776+
return true;
2777+
}
27652778
default: {
27662779
std::string DiagMsg;
27672780
raw_string_ostream OS(DiagMsg);
@@ -2798,6 +2811,83 @@ void SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
27982811
.addUse(VarReg);
27992812
}
28002813

2814+
void SPIRVInstructionSelector::selectReadImageIntrinsic(
2815+
Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const {
2816+
2817+
// If the load of the image is in a different basic block, then
2818+
// this will generate invalid code. A proper solution is to move
2819+
// the OpLoad from selectHandleFromBinding here. However, to do
2820+
// that we will need to change the return type of the intrinsic.
2821+
// We will do that when we can, but for now trying to move forward with other
2822+
// issues.
2823+
Register ImageReg = I.getOperand(2).getReg();
2824+
2825+
SPIRVType *ReadType = getCorrespondingVec4Type(ResType, I);
2826+
Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType));
2827+
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageRead))
2828+
.addDef(ReadReg)
2829+
.addUse(GR.getSPIRVTypeID(ReadType))
2830+
.addUse(ImageReg)
2831+
.addUse(I.getOperand(3).getReg());
2832+
2833+
extractScalarOrVectorFromVector(ResVReg, ResType, ReadReg, I);
2834+
}
2835+
2836+
void SPIRVInstructionSelector::extractScalarOrVectorFromVector(
2837+
Register &ResultReg, const SPIRVType *ResType, Register &InputReg,
2838+
MachineInstr &InsertionPoint) const {
2839+
SPIRVType *InputType = GR.getResultType(InputReg);
2840+
assert(InputType->getOpcode() == SPIRV::OpTypeVector);
2841+
2842+
if (ResType->getOpcode() != SPIRV::OpTypeVector) {
2843+
assert(ResType == GR.getScalarOrVectorComponentType(InputType));
2844+
BuildMI(*InsertionPoint.getParent(), InsertionPoint,
2845+
InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract))
2846+
.addDef(ResultReg)
2847+
.addUse(GR.getSPIRVTypeID(ResType))
2848+
.addUse(InputReg)
2849+
.addImm(0);
2850+
return;
2851+
}
2852+
2853+
uint64_t InputSize = GR.getScalarOrVectorComponentCount(InputType);
2854+
uint64_t VectorSize = GR.getScalarOrVectorComponentCount(ResType);
2855+
if (VectorSize == InputSize) {
2856+
BuildMI(*InsertionPoint.getParent(), InsertionPoint,
2857+
InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCopyObject))
2858+
.addDef(ResultReg)
2859+
.addUse(GR.getSPIRVTypeID(ResType))
2860+
.addUse(InputReg);
2861+
return;
2862+
}
2863+
2864+
assert(VectorSize < InputSize &&
2865+
"Cannot extract more element than there are in the input.");
2866+
SmallVector<Register> ComponentRegisters;
2867+
SPIRVType *ScalarType = GR.getScalarOrVectorComponentType(ResType);
2868+
const TargetRegisterClass *ScalarRegClass = GR.getRegClass(ScalarType);
2869+
for (uint64_t i = 0; i < VectorSize; i++) {
2870+
Register ComponentReg = MRI->createVirtualRegister(ScalarRegClass);
2871+
BuildMI(*InsertionPoint.getParent(), InsertionPoint,
2872+
InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract))
2873+
.addDef(ComponentReg)
2874+
.addUse(ScalarType->getOperand(0).getReg())
2875+
.addUse(InputReg)
2876+
.addImm(i);
2877+
ComponentRegisters.emplace_back(ComponentReg);
2878+
}
2879+
2880+
MachineInstrBuilder MIB = BuildMI(*InsertionPoint.getParent(), InsertionPoint,
2881+
InsertionPoint.getDebugLoc(),
2882+
TII.get(SPIRV::OpCompositeConstruct))
2883+
.addDef(ResultReg)
2884+
.addUse(GR.getSPIRVTypeID(ResType));
2885+
2886+
for (Register ComponentReg : ComponentRegisters) {
2887+
MIB.addUse(ComponentReg);
2888+
}
2889+
}
2890+
28012891
Register SPIRVInstructionSelector::buildPointerToResource(
28022892
const SPIRVType *ResType, uint32_t Set, uint32_t Binding,
28032893
uint32_t ArraySize, Register IndexReg, bool IsNonUniform,
@@ -3300,6 +3390,24 @@ bool SPIRVInstructionSelector::selectSpvThreadId(Register ResVReg,
33003390
return MIB.constrainAllUses(TII, TRI, RBI);
33013391
}
33023392

3393+
SPIRVType *
3394+
SPIRVInstructionSelector::getCorrespondingVec4Type(const SPIRVType *Type,
3395+
MachineInstr &I) const {
3396+
MachineIRBuilder MIRBuilder(I);
3397+
if (Type->getOpcode() != SPIRV::OpTypeVector) {
3398+
return GR.getOrCreateSPIRVVectorType(Type, 4, MIRBuilder);
3399+
}
3400+
3401+
uint64_t VectorSize = Type->getOperand(2).getImm();
3402+
if (VectorSize == 4) {
3403+
return Type;
3404+
}
3405+
3406+
Register ScalarTypeReg = Type->getOperand(1).getReg();
3407+
const SPIRVType *ScalarType = GR.getSPIRVTypeForVReg(ScalarTypeReg);
3408+
return GR.getOrCreateSPIRVVectorType(ScalarType, 4, MIRBuilder);
3409+
}
3410+
33033411
namespace llvm {
33043412
InstructionSelector *
33053413
createSPIRVInstructionSelector(const SPIRVTargetMachine &TM,

llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,11 @@ void RequirementHandler::initAvailableCapabilitiesForVulkan(
719719
Capability::UniformTexelBufferArrayNonUniformIndexingEXT,
720720
Capability::StorageTexelBufferArrayNonUniformIndexingEXT});
721721
}
722+
723+
// Became core in Vulkan 1.3
724+
if (ST.isAtLeastSPIRVVer(VersionTuple(1, 6))) {
725+
addAvailableCaps({Capability::StorageImageReadWithoutFormat});
726+
}
722727
}
723728

724729
} // namespace SPIRV
@@ -1005,6 +1010,13 @@ void addOpAccessChainReqs(const MachineInstr &Instr,
10051010
}
10061011
}
10071012

1013+
static bool imageTypeHasUnknownFormat(SPIRVType *TypeInst) {
1014+
if (TypeInst->getOpcode() != SPIRV::OpTypeImage)
1015+
return false;
1016+
assert(TypeInst->getOperand(7).isImm() && "The image format must be an imm.");
1017+
return TypeInst->getOperand(7).getImm() == 0;
1018+
}
1019+
10081020
static void AddDotProductRequirements(const MachineInstr &MI,
10091021
SPIRV::RequirementHandler &Reqs,
10101022
const SPIRVSubtarget &ST) {
@@ -1411,6 +1423,14 @@ void addInstrRequirements(const MachineInstr &MI,
14111423
case SPIRV::OpUDot:
14121424
AddDotProductRequirements(MI, Reqs, ST);
14131425
break;
1426+
case SPIRV::OpImageRead: {
1427+
Register ImageReg = MI.getOperand(2).getReg();
1428+
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
1429+
if (imageTypeHasUnknownFormat(TypeDef))
1430+
Reqs.addCapability(SPIRV::Capability::StorageImageReadWithoutFormat);
1431+
break;
1432+
}
1433+
14141434
default:
14151435
break;
14161436
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
; RUN: llc -O0 -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: [[v2_int:%[0-9]+]] = OpTypeVector [[int]] 2
13+
; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 R32i {{$}}
14+
; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]]
15+
; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant
16+
17+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
18+
; CHECK-NEXT: OpLabel
19+
define void @RWBufferLoad_Vec4_I32() #0 {
20+
; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
21+
%buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
22+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
23+
i32 16, i32 7, i32 1, i32 0, i1 false)
24+
25+
; CHECK: OpImageRead [[v4_int]] [[buffer]] [[zero]]
26+
%data0 = call <4 x i32> @llvm.spv.typedBufferLoad(
27+
target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0)
28+
29+
ret void
30+
}
31+
32+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
33+
; CHECK-NEXT: OpLabel
34+
define void @RWBufferLoad_I32() #0 {
35+
; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
36+
%buffer1 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
37+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
38+
i32 16, i32 7, i32 1, i32 0, i1 false)
39+
40+
; CHECK: [[V:%[0-9]+]] = OpImageRead [[v4_int]] [[buffer]] [[zero]]
41+
; CHECK: OpCompositeExtract [[int]] [[V]] 0
42+
%data1 = call i32 @llvm.spv.typedBufferLoad(
43+
target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer1, i32 0)
44+
45+
ret void
46+
}
47+
48+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
49+
; CHECK-NEXT: OpLabel
50+
define void @RWBufferLoad_Vec2_I32() #0 {
51+
; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
52+
%buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
53+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
54+
i32 16, i32 7, i32 1, i32 0, i1 false)
55+
56+
; CHECK: [[V:%[0-9]+]] = OpImageRead [[v4_int]] [[buffer]] [[zero]]
57+
; CHECK: [[e0:%[0-9]+]] = OpCompositeExtract [[int]] [[V]] 0
58+
; CHECK: [[e1:%[0-9]+]] = OpCompositeExtract [[int]] [[V]] 1
59+
; CHECK: OpCompositeConstruct [[v2_int]] [[e0]] [[e1]]
60+
%data0 = call <2 x i32> @llvm.spv.typedBufferLoad(
61+
target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0)
62+
63+
ret void
64+
}
65+
66+
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" }
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 StorageImageReadWithoutFormat
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: [[zero:%[0-9]+]] = OpConstant [[int]] 0
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+
; CHECK-NEXT: OpLabel
17+
define void @RWBufferLoad_Vec4_I32() #0 {
18+
; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
19+
%buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 0)
20+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_0(
21+
i32 16, i32 7, i32 1, i32 0, i1 false)
22+
23+
; CHECK: OpImageRead [[v4_int]] [[buffer]] [[zero]]
24+
%data0 = call <4 x i32> @llvm.spv.typedBufferLoad(
25+
target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) %buffer0, i32 0)
26+
27+
ret void
28+
}
29+
30+
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" }

0 commit comments

Comments
 (0)