Skip to content

Commit 5547c0c

Browse files
[SPIRV] Implement LLVM IR and backend for typed buffer counters (#161425)
This commit implements the backend portion of the typed buffer counter proposal described in https://github.com/llvm/wg-hlsl/blob/main/proposals/0023-typed-buffer-counters.md. This is the second part of the implementation, focusing on the LLVM IR and SPIR-V backend. Specifically, this commit implements the "LLVM IR Generation and Backend Handling" section of the proposal. This includes: - Adding the `llvm.spv.resource.counterhandlefromimplicitbinding` and `llvm.spv.resource.counterhandlefrombinding` intrinsics. - Implementing the selection of these intrinsics in the SPIRV backend to generate the correct `OpVariable` and `OpDecorate` instructions for the counter buffer. - Handling `IncrementCounter` and `DecrementCounter` via a new `llvm.spv.resource.updatecounter` intrinsic, which is lowered to `OpAtomicIAdd`. - Adding a new test file to verify the implementation. Contributes to #137032 --------- Co-authored-by: Marcos Maronas <[email protected]>
1 parent 4b05a12 commit 5547c0c

File tree

6 files changed

+346
-62
lines changed

6 files changed

+346
-62
lines changed

llvm/include/llvm/IR/IntrinsicsSPIRV.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
150150
[llvm_i32_ty, llvm_i32_ty, llvm_i32_ty,
151151
llvm_i32_ty, llvm_ptr_ty],
152152
[IntrNoMem]>;
153+
def int_spv_resource_counterhandlefromimplicitbinding
154+
: DefaultAttrsIntrinsic<[llvm_any_ty],
155+
[llvm_any_ty, llvm_i32_ty, llvm_i32_ty],
156+
[IntrNoMem]>;
157+
def int_spv_resource_counterhandlefrombinding
158+
: DefaultAttrsIntrinsic<[llvm_any_ty],
159+
[llvm_any_ty, llvm_i32_ty, llvm_i32_ty],
160+
[IntrNoMem]>;
153161

154162
def int_spv_firstbituhigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
155163
def int_spv_firstbitshigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,19 @@ class SPIRVInstructionSelector : public InstructionSelector {
307307
bool selectHandleFromBinding(Register &ResVReg, const SPIRVType *ResType,
308308
MachineInstr &I) const;
309309

310+
bool selectCounterHandleFromBinding(Register &ResVReg,
311+
const SPIRVType *ResType,
312+
MachineInstr &I) const;
313+
310314
bool selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType,
311315
MachineInstr &I) const;
312316
bool selectImageWriteIntrinsic(MachineInstr &I) const;
313317
bool selectResourceGetPointer(Register &ResVReg, const SPIRVType *ResType,
314318
MachineInstr &I) const;
315319
bool selectModf(Register ResVReg, const SPIRVType *ResType,
316320
MachineInstr &I) const;
321+
bool selectUpdateCounter(Register &ResVReg, const SPIRVType *ResType,
322+
MachineInstr &I) const;
317323
bool selectFrexp(Register ResVReg, const SPIRVType *ResType,
318324
MachineInstr &I) const;
319325
// Utilities
@@ -3443,6 +3449,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
34433449
case Intrinsic::spv_resource_handlefrombinding: {
34443450
return selectHandleFromBinding(ResVReg, ResType, I);
34453451
}
3452+
case Intrinsic::spv_resource_counterhandlefrombinding:
3453+
return selectCounterHandleFromBinding(ResVReg, ResType, I);
3454+
case Intrinsic::spv_resource_updatecounter:
3455+
return selectUpdateCounter(ResVReg, ResType, I);
34463456
case Intrinsic::spv_resource_store_typedbuffer: {
34473457
return selectImageWriteIntrinsic(I);
34483458
}
@@ -3478,6 +3488,130 @@ bool SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
34783488
*cast<GIntrinsic>(&I), I);
34793489
}
34803490

3491+
bool SPIRVInstructionSelector::selectCounterHandleFromBinding(
3492+
Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const {
3493+
auto &Intr = cast<GIntrinsic>(I);
3494+
assert(Intr.getIntrinsicID() ==
3495+
Intrinsic::spv_resource_counterhandlefrombinding);
3496+
3497+
// Extract information from the intrinsic call.
3498+
Register MainHandleReg = Intr.getOperand(2).getReg();
3499+
auto *MainHandleDef = cast<GIntrinsic>(getVRegDef(*MRI, MainHandleReg));
3500+
assert(MainHandleDef->getIntrinsicID() ==
3501+
Intrinsic::spv_resource_handlefrombinding);
3502+
3503+
uint32_t Set = getIConstVal(Intr.getOperand(4).getReg(), MRI);
3504+
uint32_t Binding = getIConstVal(Intr.getOperand(3).getReg(), MRI);
3505+
uint32_t ArraySize = getIConstVal(MainHandleDef->getOperand(4).getReg(), MRI);
3506+
Register IndexReg = MainHandleDef->getOperand(5).getReg();
3507+
const bool IsNonUniform = false;
3508+
std::string CounterName =
3509+
getStringValueFromReg(MainHandleDef->getOperand(6).getReg(), *MRI) +
3510+
".counter";
3511+
3512+
// Create the counter variable.
3513+
MachineIRBuilder MIRBuilder(I);
3514+
Register CounterVarReg = buildPointerToResource(
3515+
GR.getPointeeType(ResType), GR.getPointerStorageClass(ResType), Set,
3516+
Binding, ArraySize, IndexReg, IsNonUniform, CounterName, MIRBuilder);
3517+
3518+
return BuildCOPY(ResVReg, CounterVarReg, I);
3519+
}
3520+
3521+
bool SPIRVInstructionSelector::selectUpdateCounter(Register &ResVReg,
3522+
const SPIRVType *ResType,
3523+
MachineInstr &I) const {
3524+
auto &Intr = cast<GIntrinsic>(I);
3525+
assert(Intr.getIntrinsicID() == Intrinsic::spv_resource_updatecounter);
3526+
3527+
Register CounterHandleReg = Intr.getOperand(2).getReg();
3528+
Register IncrReg = Intr.getOperand(3).getReg();
3529+
3530+
// The counter handle is a pointer to the counter variable (which is a struct
3531+
// containing an i32). We need to get a pointer to that i32 member to do the
3532+
// atomic operation.
3533+
#ifndef NDEBUG
3534+
SPIRVType *CounterVarType = GR.getSPIRVTypeForVReg(CounterHandleReg);
3535+
SPIRVType *CounterVarPointeeType = GR.getPointeeType(CounterVarType);
3536+
assert(CounterVarPointeeType &&
3537+
CounterVarPointeeType->getOpcode() == SPIRV::OpTypeStruct &&
3538+
"Counter variable must be a struct");
3539+
assert(GR.getPointerStorageClass(CounterVarType) ==
3540+
SPIRV::StorageClass::StorageBuffer &&
3541+
"Counter variable must be in the storage buffer storage class");
3542+
assert(CounterVarPointeeType->getNumOperands() == 2 &&
3543+
"Counter variable must have exactly 1 member in the struct");
3544+
const SPIRVType *MemberType =
3545+
GR.getSPIRVTypeForVReg(CounterVarPointeeType->getOperand(1).getReg());
3546+
assert(MemberType->getOpcode() == SPIRV::OpTypeInt &&
3547+
"Counter variable struct must have a single i32 member");
3548+
#endif
3549+
3550+
// The struct has a single i32 member.
3551+
MachineIRBuilder MIRBuilder(I);
3552+
const Type *LLVMIntType =
3553+
Type::getInt32Ty(I.getMF()->getFunction().getContext());
3554+
3555+
SPIRVType *IntPtrType = GR.getOrCreateSPIRVPointerType(
3556+
LLVMIntType, MIRBuilder, SPIRV::StorageClass::StorageBuffer);
3557+
3558+
auto Zero = buildI32Constant(0, I);
3559+
if (!Zero.second)
3560+
return false;
3561+
3562+
Register PtrToCounter =
3563+
MRI->createVirtualRegister(GR.getRegClass(IntPtrType));
3564+
if (!BuildMI(*I.getParent(), I, I.getDebugLoc(),
3565+
TII.get(SPIRV::OpAccessChain))
3566+
.addDef(PtrToCounter)
3567+
.addUse(GR.getSPIRVTypeID(IntPtrType))
3568+
.addUse(CounterHandleReg)
3569+
.addUse(Zero.first)
3570+
.constrainAllUses(TII, TRI, RBI)) {
3571+
return false;
3572+
}
3573+
3574+
// For UAV/SSBO counters, the scope is Device. The counter variable is not
3575+
// used as a flag. So the memory semantics can be None.
3576+
auto Scope = buildI32Constant(SPIRV::Scope::Device, I);
3577+
if (!Scope.second)
3578+
return false;
3579+
auto Semantics = buildI32Constant(SPIRV::MemorySemantics::None, I);
3580+
if (!Semantics.second)
3581+
return false;
3582+
3583+
int64_t IncrVal = getIConstValSext(IncrReg, MRI);
3584+
auto Incr = buildI32Constant(static_cast<uint32_t>(IncrVal), I);
3585+
if (!Incr.second)
3586+
return false;
3587+
3588+
Register AtomicRes = MRI->createVirtualRegister(GR.getRegClass(ResType));
3589+
if (!BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpAtomicIAdd))
3590+
.addDef(AtomicRes)
3591+
.addUse(GR.getSPIRVTypeID(ResType))
3592+
.addUse(PtrToCounter)
3593+
.addUse(Scope.first)
3594+
.addUse(Semantics.first)
3595+
.addUse(Incr.first)
3596+
.constrainAllUses(TII, TRI, RBI)) {
3597+
return false;
3598+
}
3599+
if (IncrVal >= 0) {
3600+
return BuildCOPY(ResVReg, AtomicRes, I);
3601+
}
3602+
3603+
// In HLSL, IncrementCounter returns the value *before* the increment, while
3604+
// DecrementCounter returns the value *after* the decrement. Both are lowered
3605+
// to the same atomic intrinsic which returns the value *before* the
3606+
// operation. So for decrements (negative IncrVal), we must subtract the
3607+
// increment value from the result to get the post-decrement value.
3608+
return BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpIAddS))
3609+
.addDef(ResVReg)
3610+
.addUse(GR.getSPIRVTypeID(ResType))
3611+
.addUse(AtomicRes)
3612+
.addUse(Incr.first)
3613+
.constrainAllUses(TII, TRI, RBI);
3614+
}
34813615
bool SPIRVInstructionSelector::selectReadImageIntrinsic(
34823616
Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const {
34833617

0 commit comments

Comments
 (0)