Skip to content

Commit 5af7ae5

Browse files
authored
[SPIRV] Implement handle_fromBinding intrinsic. (#111052)
Implement the intrinsic `llvm.spv.handle.fromBinding`, which returns the handle for a global resource. This involves creating a global variable that matches the return-type, set, and binding in the call, and returning the handle to that resource. This commit implements the scalar version. It does not handle arrays of resources yet. It also does not handle storage buffers yet. We do not have the type for the storage buffers designed yet. Part of #81036
1 parent baa51ff commit 5af7ae5

File tree

6 files changed

+164
-9
lines changed

6 files changed

+164
-9
lines changed

llvm/docs/SPIRVUsage.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,13 @@ SPIR-V backend, along with their descriptions and argument details.
381381
- Pointer
382382
- `[8-bit Integer]`
383383
- Creates a resource handle for graphics or compute resources. Facilitates the management and use of resources in shaders.
384+
* - `int_spv_handle_fromBinding`
385+
- spirv.Image
386+
- `[32-bit Integer set, 32-bit Integer binding, 32-bit Integer arraySize, 32-bit Integer index, bool isUniformIndex]`
387+
- Returns the handle for the resource at the given set and binding.\
388+
If `arraySize > 1`, then the binding represents an array of resources\
389+
of the given size, and the handle for the resource at the given index is returned.\
390+
If the index is possibly non-uniform, then `isUniformIndex` must get set to true.
384391

385392
.. _spirv-builtin-functions:
386393

llvm/include/llvm/IR/IntrinsicsSPIRV.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,14 @@ let TargetPrefix = "spv" in {
8585
def int_spv_wave_is_first_lane : DefaultAttrsIntrinsic<[llvm_i1_ty], [], [IntrConvergent]>;
8686
def int_spv_sign : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_any_ty], [IntrNoMem]>;
8787
def int_spv_radians : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty], [IntrNoMem]>;
88+
89+
// Create resource handle given the binding information. Returns a
90+
// type appropriate for the kind of resource given the set id, binding id,
91+
// array size of the binding, as well as an index and an indicator
92+
// whether that index may be non-uniform.
93+
def int_spv_handle_fromBinding
94+
: DefaultAttrsIntrinsic<
95+
[llvm_any_ty],
96+
[llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i1_ty],
97+
[IntrNoMem]>;
8898
}

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,53 @@ Register SPIRVGlobalRegistry::buildGlobalVariable(
713713
return Reg;
714714
}
715715

716+
static std::string buildSpirvTypeName(const SPIRVType *Type,
717+
MachineIRBuilder &MIRBuilder) {
718+
switch (Type->getOpcode()) {
719+
case SPIRV::OpTypeImage: {
720+
Register SampledTypeReg = Type->getOperand(1).getReg();
721+
auto *SampledType = MIRBuilder.getMRI()->getUniqueVRegDef(SampledTypeReg);
722+
std::string TypeName =
723+
"image_" + buildSpirvTypeName(SampledType, MIRBuilder);
724+
for (uint32_t I = 2; I < Type->getNumOperands(); ++I) {
725+
TypeName = (TypeName + '_' + Twine(Type->getOperand(I).getImm())).str();
726+
}
727+
return TypeName;
728+
}
729+
case SPIRV::OpTypeFloat:
730+
return ("f" + Twine(Type->getOperand(1).getImm())).str();
731+
case SPIRV::OpTypeInt:
732+
if (Type->getOperand(2).getImm())
733+
return ("i" + Twine(Type->getOperand(1).getImm())).str();
734+
return ("u" + Twine(Type->getOperand(1).getImm())).str();
735+
default:
736+
llvm_unreachable("Trying to the the name of an unknown type.");
737+
}
738+
}
739+
740+
Register SPIRVGlobalRegistry::getOrCreateGlobalVariableWithBinding(
741+
const SPIRVType *VarType, uint32_t Set, uint32_t Binding,
742+
MachineIRBuilder &MIRBuilder) {
743+
SPIRVType *VarPointerTypeReg = getOrCreateSPIRVPointerType(
744+
VarType, MIRBuilder, SPIRV::StorageClass::UniformConstant);
745+
Register VarReg =
746+
MIRBuilder.getMRI()->createVirtualRegister(&SPIRV::iIDRegClass);
747+
748+
// TODO: The name should come from the llvm-ir, but how that name will be
749+
// passed from the HLSL to the backend has not been decided. Using this place
750+
// holder for now.
751+
std::string Name = ("__resource_" + buildSpirvTypeName(VarType, MIRBuilder) +
752+
"_" + Twine(Set) + "_" + Twine(Binding))
753+
.str();
754+
buildGlobalVariable(VarReg, VarPointerTypeReg, Name, nullptr,
755+
SPIRV::StorageClass::UniformConstant, nullptr, false,
756+
false, SPIRV::LinkageType::Import, MIRBuilder, false);
757+
758+
buildOpDecorate(VarReg, MIRBuilder, SPIRV::Decoration::DescriptorSet, {Set});
759+
buildOpDecorate(VarReg, MIRBuilder, SPIRV::Decoration::Binding, {Binding});
760+
return VarReg;
761+
}
762+
716763
SPIRVType *SPIRVGlobalRegistry::getOpTypeArray(uint32_t NumElems,
717764
SPIRVType *ElemType,
718765
MachineIRBuilder &MIRBuilder,

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ class SPIRVGlobalRegistry {
519519
SPIRV::LinkageType::LinkageType LinkageType,
520520
MachineIRBuilder &MIRBuilder,
521521
bool IsInstSelector);
522+
Register getOrCreateGlobalVariableWithBinding(const SPIRVType *VarType,
523+
uint32_t Set, uint32_t Binding,
524+
MachineIRBuilder &MIRBuilder);
522525

523526
// Convenient helpers for getting types with check for duplicates.
524527
SPIRVType *getOrCreateSPIRVIntegerType(unsigned BitWidth,

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
232232

233233
bool selectUnmergeValues(MachineInstr &I) const;
234234

235+
void selectHandleFromBinding(Register &ResVReg, const SPIRVType *ResType,
236+
MachineInstr &I) const;
237+
235238
// Utilities
236239
Register buildI32Constant(uint32_t Val, MachineInstr &I,
237240
const SPIRVType *ResType = nullptr) const;
@@ -252,6 +255,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
252255
uint32_t Opcode) const;
253256
MachineInstrBuilder buildConstGenericPtr(MachineInstr &I, Register SrcPtr,
254257
SPIRVType *SrcPtrTy) const;
258+
Register buildPointerToResource(const SPIRVType *ResType, uint32_t Set,
259+
uint32_t Binding, uint32_t ArraySize,
260+
MachineIRBuilder MIRBuilder) const;
255261
};
256262

257263
} // end anonymous namespace
@@ -2549,6 +2555,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
25492555
// Discard internal intrinsics.
25502556
case Intrinsic::spv_value_md:
25512557
break;
2558+
case Intrinsic::spv_handle_fromBinding: {
2559+
selectHandleFromBinding(ResVReg, ResType, I);
2560+
return true;
2561+
}
25522562
default: {
25532563
std::string DiagMsg;
25542564
raw_string_ostream OS(DiagMsg);
@@ -2560,6 +2570,34 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
25602570
return true;
25612571
}
25622572

2573+
void SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
2574+
const SPIRVType *ResType,
2575+
MachineInstr &I) const {
2576+
2577+
uint32_t Set = foldImm(I.getOperand(2), MRI);
2578+
uint32_t Binding = foldImm(I.getOperand(3), MRI);
2579+
uint32_t ArraySize = foldImm(I.getOperand(4), MRI);
2580+
2581+
MachineIRBuilder MIRBuilder(I);
2582+
Register VarReg =
2583+
buildPointerToResource(ResType, Set, Binding, ArraySize, MIRBuilder);
2584+
2585+
// TODO: For now we assume the resource is an image, which needs to be
2586+
// loaded to get the handle. That will not be true for storage buffers.
2587+
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
2588+
.addDef(ResVReg)
2589+
.addUse(GR.getSPIRVTypeID(ResType))
2590+
.addUse(VarReg);
2591+
}
2592+
2593+
Register SPIRVInstructionSelector::buildPointerToResource(
2594+
const SPIRVType *ResType, uint32_t Set, uint32_t Binding,
2595+
uint32_t ArraySize, MachineIRBuilder MIRBuilder) const {
2596+
assert(ArraySize == 1 && "Resource arrays are not implemented yet.");
2597+
return GR.getOrCreateGlobalVariableWithBinding(ResType, Set, Binding,
2598+
MIRBuilder);
2599+
}
2600+
25632601
bool SPIRVInstructionSelector::selectAllocaArray(Register ResVReg,
25642602
const SPIRVType *ResType,
25652603
MachineInstr &I) const {
@@ -2863,15 +2901,7 @@ bool SPIRVInstructionSelector::selectSpvThreadId(Register ResVReg,
28632901
// Get Thread ID index. Expecting operand is a constant immediate value,
28642902
// wrapped in a type assignment.
28652903
assert(I.getOperand(2).isReg());
2866-
Register ThreadIdReg = I.getOperand(2).getReg();
2867-
SPIRVType *ConstTy = this->MRI->getVRegDef(ThreadIdReg);
2868-
assert(ConstTy && ConstTy->getOpcode() == SPIRV::ASSIGN_TYPE &&
2869-
ConstTy->getOperand(1).isReg());
2870-
Register ConstReg = ConstTy->getOperand(1).getReg();
2871-
const MachineInstr *Const = this->MRI->getVRegDef(ConstReg);
2872-
assert(Const && Const->getOpcode() == TargetOpcode::G_CONSTANT);
2873-
const llvm::APInt &Val = Const->getOperand(1).getCImm()->getValue();
2874-
const uint32_t ThreadId = Val.getZExtValue();
2904+
const uint32_t ThreadId = foldImm(I.getOperand(2), MRI);
28752905

28762906
// Extract the thread ID from the loaded vector value.
28772907
MachineBasicBlock &BB = *I.getParent();
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
; RUN: llc -verify-machineinstrs -O0 -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-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16
5+
; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7
6+
; CHECK-DAG: OpDecorate [[FloatBufferVar:%[0-9]+]] DescriptorSet 16
7+
; CHECK-DAG: OpDecorate [[FloatBufferVar]] Binding 7
8+
9+
; CHECK-DAG: [[float:%[0-9]+]] = OpTypeFloat 32
10+
; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
11+
; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 R32i {{$}}
12+
; CHECK-DAG: [[RWBufferTypeFloat:%[0-9]+]] = OpTypeImage [[float]] Buffer 2 0 0 2 R32f {{$}}
13+
; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]]
14+
; CHECK-DAG: [[FloatBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeFloat]]
15+
; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant
16+
; CHECK-DAG: [[FloatBufferVar]] = OpVariable [[FloatBufferPtrType]] UniformConstant
17+
18+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
19+
; CHECK-NEXT: OpLabel
20+
define void @RWBufferLoad() #0 {
21+
; CHECK-NEXT: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
22+
%buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
23+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
24+
i32 16, i32 7, i32 1, i32 0, i1 false)
25+
26+
; Make sure we use the same variable with multiple loads.
27+
; CHECK-NEXT: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
28+
%buffer1 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
29+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
30+
i32 16, i32 7, i32 1, i32 0, i1 false)
31+
ret void
32+
}
33+
34+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
35+
; CHECK-NEXT: OpLabel
36+
define void @UseDifferentGlobalVar() #0 {
37+
; Make sure we use a different variable from the first function. They have
38+
; different types.
39+
; CHECK-NEXT: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeFloat]] [[FloatBufferVar]]
40+
%buffer0 = call target("spirv.Image", float, 5, 2, 0, 0, 2, 3)
41+
@llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_3(
42+
i32 16, i32 7, i32 1, i32 0, i1 false)
43+
ret void
44+
}
45+
46+
; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
47+
; CHECK-NEXT: OpLabel
48+
define void @ReuseGlobalVarFromFirstFunction() #0 {
49+
; Make sure we use the same variable as the first function. They should be the
50+
; same in case one function calls the other.
51+
; CHECK-NEXT: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
52+
%buffer1 = 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+
ret void
56+
}
57+
58+
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)