Skip to content

Commit 4b692a9

Browse files
authored
[SPIRV] Expand RWBuffer load and store from HLSL (llvm#122355)
The code pattern that clang will generate for HLSL has changed from the original plan. This allows the SPIR-V backend to generate code for the current code generation. It looks for patterns of the form: ``` %1 = @llvm.spv.resource.handlefrombinding %2 = @llvm.spv.resource.getpointer(%1, index) load/store %2 ``` These three llvm-ir instruction are treated as a single unit that will 1. Generate or find the global variable identified by the call to `resource.handlefrombinding`. 2. Generate an OpLoad of the variable to get the handle to the image. 3. Generate an OpImageRead or OpImageWrite using that handle with the given index. This will generate the OpLoad in the same BB as the read/write. Note: Now that `resource.handlefrombinding` is not processed on its own, many existing tests had to be removed. We do not have intrinsics that are able to use handles to sampled images, input attachments, etc., so we cannot generate the load of the handle. These tests are removed for now, and will be added when those resource types are fully implemented.
1 parent c83e5e8 commit 4b692a9

23 files changed

+240
-581
lines changed

llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,14 @@ bool expectIgnoredInIRTranslation(const Instruction *I) {
264264
const auto *II = dyn_cast<IntrinsicInst>(I);
265265
if (!II)
266266
return false;
267-
return II->getIntrinsicID() == Intrinsic::invariant_start;
267+
switch (II->getIntrinsicID()) {
268+
case Intrinsic::invariant_start:
269+
case Intrinsic::spv_resource_handlefrombinding:
270+
case Intrinsic::spv_resource_getpointer:
271+
return true;
272+
default:
273+
return false;
274+
}
268275
}
269276

270277
bool allowEmitFakeUse(const Value *Arg) {
@@ -737,7 +744,13 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
737744
{"__spirv_GenericCastToPtrExplicit_ToLocal", 0},
738745
{"__spirv_GenericCastToPtrExplicit_ToPrivate", 0}};
739746
// TODO: maybe improve performance by caching demangled names
740-
if (Function *CalledF = CI->getCalledFunction()) {
747+
748+
auto *II = dyn_cast<IntrinsicInst>(I);
749+
if (II && II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
750+
auto *ImageType = cast<TargetExtType>(II->getOperand(0)->getType());
751+
assert(ImageType->getTargetExtName() == "spirv.Image");
752+
Ty = ImageType->getTypeParameter(0);
753+
} else if (Function *CalledF = CI->getCalledFunction()) {
741754
std::string DemangledName =
742755
getOclOrSpirvBuiltinDemangledName(CalledF->getName());
743756
if (DemangledName.length() > 0)

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,9 +1114,12 @@ SPIRVGlobalRegistry::getSPIRVTypeForVReg(Register VReg,
11141114
return nullptr;
11151115
}
11161116

1117-
SPIRVType *SPIRVGlobalRegistry::getResultType(Register VReg) {
1118-
MachineInstr *Instr = getVRegDef(CurMF->getRegInfo(), VReg);
1119-
return getSPIRVTypeForVReg(Instr->getOperand(1).getReg());
1117+
SPIRVType *SPIRVGlobalRegistry::getResultType(Register VReg,
1118+
MachineFunction *MF) {
1119+
if (!MF)
1120+
MF = CurMF;
1121+
MachineInstr *Instr = getVRegDef(MF->getRegInfo(), VReg);
1122+
return getSPIRVTypeForVReg(Instr->getOperand(1).getReg(), MF);
11201123
}
11211124

11221125
SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVType(

llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ class SPIRVGlobalRegistry {
377377
const MachineFunction *MF = nullptr) const;
378378

379379
// Return the result type of the instruction defining the register.
380-
SPIRVType *getResultType(Register VReg);
380+
SPIRVType *getResultType(Register VReg, MachineFunction *MF = nullptr);
381381

382382
// Whether the given VReg has a SPIR-V type mapped to it yet.
383383
bool hasSPIRVTypeForVReg(Register VReg) const {

llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Lines changed: 134 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
301301

302302
bool selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType,
303303
MachineInstr &I) const;
304-
305304
bool selectImageWriteIntrinsic(MachineInstr &I) const;
305+
bool selectResourceGetPointer(Register &ResVReg, const SPIRVType *ResType,
306+
MachineInstr &I) const;
306307

307308
// Utilities
308309
std::pair<Register, bool>
@@ -332,10 +333,15 @@ class SPIRVInstructionSelector : public InstructionSelector {
332333
SPIRVType *widenTypeToVec4(const SPIRVType *Type, MachineInstr &I) const;
333334
bool extractSubvector(Register &ResVReg, const SPIRVType *ResType,
334335
Register &ReadReg, MachineInstr &InsertionPoint) const;
336+
bool generateImageRead(Register &ResVReg, const SPIRVType *ResType,
337+
Register ImageReg, Register IdxReg, DebugLoc Loc,
338+
MachineInstr &Pos) const;
335339
bool BuildCOPY(Register DestReg, Register SrcReg, MachineInstr &I) const;
336340
bool loadVec3BuiltinInputID(SPIRV::BuiltIn::BuiltIn BuiltInValue,
337341
Register ResVReg, const SPIRVType *ResType,
338342
MachineInstr &I) const;
343+
bool loadHandleBeforePosition(Register &HandleReg, const SPIRVType *ResType,
344+
GIntrinsic &HandleDef, MachineInstr &Pos) const;
339345
};
340346

341347
} // end anonymous namespace
@@ -1043,6 +1049,25 @@ bool SPIRVInstructionSelector::selectLoad(Register ResVReg,
10431049
MachineInstr &I) const {
10441050
unsigned OpOffset = isa<GIntrinsic>(I) ? 1 : 0;
10451051
Register Ptr = I.getOperand(1 + OpOffset).getReg();
1052+
1053+
auto *PtrDef = getVRegDef(*MRI, Ptr);
1054+
auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
1055+
if (IntPtrDef &&
1056+
IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
1057+
Register ImageReg = IntPtrDef->getOperand(2).getReg();
1058+
Register NewImageReg =
1059+
MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
1060+
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
1061+
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
1062+
*ImageDef, I)) {
1063+
return false;
1064+
}
1065+
1066+
Register IdxReg = IntPtrDef->getOperand(3).getReg();
1067+
return generateImageRead(ResVReg, ResType, NewImageReg, IdxReg,
1068+
I.getDebugLoc(), I);
1069+
}
1070+
10461071
auto MIB = BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
10471072
.addDef(ResVReg)
10481073
.addUse(GR.getSPIRVTypeID(ResType))
@@ -1062,6 +1087,29 @@ bool SPIRVInstructionSelector::selectStore(MachineInstr &I) const {
10621087
unsigned OpOffset = isa<GIntrinsic>(I) ? 1 : 0;
10631088
Register StoreVal = I.getOperand(0 + OpOffset).getReg();
10641089
Register Ptr = I.getOperand(1 + OpOffset).getReg();
1090+
1091+
auto *PtrDef = getVRegDef(*MRI, Ptr);
1092+
auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
1093+
if (IntPtrDef &&
1094+
IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
1095+
Register ImageReg = IntPtrDef->getOperand(2).getReg();
1096+
Register NewImageReg =
1097+
MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
1098+
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
1099+
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
1100+
*ImageDef, I)) {
1101+
return false;
1102+
}
1103+
1104+
Register IdxReg = IntPtrDef->getOperand(3).getReg();
1105+
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
1106+
TII.get(SPIRV::OpImageWrite))
1107+
.addUse(NewImageReg)
1108+
.addUse(IdxReg)
1109+
.addUse(StoreVal)
1110+
.constrainAllUses(TII, TRI, RBI);
1111+
}
1112+
10651113
MachineBasicBlock &BB = *I.getParent();
10661114
auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpStore))
10671115
.addUse(Ptr)
@@ -3066,6 +3114,9 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
30663114
case Intrinsic::spv_resource_load_typedbuffer: {
30673115
return selectReadImageIntrinsic(ResVReg, ResType, I);
30683116
}
3117+
case Intrinsic::spv_resource_getpointer: {
3118+
return selectResourceGetPointer(ResVReg, ResType, I);
3119+
}
30693120
case Intrinsic::spv_discard: {
30703121
return selectDiscard(ResVReg, ResType, I);
30713122
}
@@ -3083,27 +3134,7 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
30833134
bool SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
30843135
const SPIRVType *ResType,
30853136
MachineInstr &I) const {
3086-
3087-
uint32_t Set = foldImm(I.getOperand(2), MRI);
3088-
uint32_t Binding = foldImm(I.getOperand(3), MRI);
3089-
uint32_t ArraySize = foldImm(I.getOperand(4), MRI);
3090-
Register IndexReg = I.getOperand(5).getReg();
3091-
bool IsNonUniform = ArraySize > 1 && foldImm(I.getOperand(6), MRI);
3092-
3093-
MachineIRBuilder MIRBuilder(I);
3094-
Register VarReg = buildPointerToResource(ResType, Set, Binding, ArraySize,
3095-
IndexReg, IsNonUniform, MIRBuilder);
3096-
3097-
if (IsNonUniform)
3098-
buildOpDecorate(ResVReg, I, TII, SPIRV::Decoration::NonUniformEXT, {});
3099-
3100-
// TODO: For now we assume the resource is an image, which needs to be
3101-
// loaded to get the handle. That will not be true for storage buffers.
3102-
return BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
3103-
.addDef(ResVReg)
3104-
.addUse(GR.getSPIRVTypeID(ResType))
3105-
.addUse(VarReg)
3106-
.constrainAllUses(TII, TRI, RBI);
3137+
return true;
31073138
}
31083139

31093140
bool SPIRVInstructionSelector::selectReadImageIntrinsic(
@@ -3116,42 +3147,75 @@ bool SPIRVInstructionSelector::selectReadImageIntrinsic(
31163147
// We will do that when we can, but for now trying to move forward with other
31173148
// issues.
31183149
Register ImageReg = I.getOperand(2).getReg();
3119-
assert(MRI->getVRegDef(ImageReg)->getParent() == I.getParent() &&
3120-
"The image must be loaded in the same basic block as its use.");
3150+
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
3151+
Register NewImageReg = MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
3152+
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
3153+
*ImageDef, I)) {
3154+
return false;
3155+
}
3156+
3157+
Register IdxReg = I.getOperand(3).getReg();
3158+
DebugLoc Loc = I.getDebugLoc();
3159+
MachineInstr &Pos = I;
31213160

3161+
return generateImageRead(ResVReg, ResType, NewImageReg, IdxReg, Loc, Pos);
3162+
}
3163+
3164+
bool SPIRVInstructionSelector::generateImageRead(Register &ResVReg,
3165+
const SPIRVType *ResType,
3166+
Register ImageReg,
3167+
Register IdxReg, DebugLoc Loc,
3168+
MachineInstr &Pos) const {
31223169
uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType);
31233170
if (ResultSize == 4) {
3124-
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
3125-
TII.get(SPIRV::OpImageRead))
3171+
return BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpImageRead))
31263172
.addDef(ResVReg)
31273173
.addUse(GR.getSPIRVTypeID(ResType))
31283174
.addUse(ImageReg)
3129-
.addUse(I.getOperand(3).getReg())
3175+
.addUse(IdxReg)
31303176
.constrainAllUses(TII, TRI, RBI);
31313177
}
31323178

3133-
SPIRVType *ReadType = widenTypeToVec4(ResType, I);
3179+
SPIRVType *ReadType = widenTypeToVec4(ResType, Pos);
31343180
Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType));
31353181
bool Succeed =
3136-
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageRead))
3182+
BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpImageRead))
31373183
.addDef(ReadReg)
31383184
.addUse(GR.getSPIRVTypeID(ReadType))
31393185
.addUse(ImageReg)
3140-
.addUse(I.getOperand(3).getReg())
3186+
.addUse(IdxReg)
31413187
.constrainAllUses(TII, TRI, RBI);
31423188
if (!Succeed)
31433189
return false;
31443190

31453191
if (ResultSize == 1) {
3146-
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
3192+
return BuildMI(*Pos.getParent(), Pos, Loc,
31473193
TII.get(SPIRV::OpCompositeExtract))
31483194
.addDef(ResVReg)
31493195
.addUse(GR.getSPIRVTypeID(ResType))
31503196
.addUse(ReadReg)
31513197
.addImm(0)
31523198
.constrainAllUses(TII, TRI, RBI);
31533199
}
3154-
return extractSubvector(ResVReg, ResType, ReadReg, I);
3200+
return extractSubvector(ResVReg, ResType, ReadReg, Pos);
3201+
}
3202+
3203+
bool SPIRVInstructionSelector::selectResourceGetPointer(
3204+
Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const {
3205+
#ifdef ASSERT
3206+
// For now, the operand is an image. This will change once we start handling
3207+
// more resource types.
3208+
Register ResourcePtr = I.getOperand(2).getReg();
3209+
SPIRVType *RegType = GR.getResultType(ResourcePtr);
3210+
assert(RegType->getOpcode() == SPIRV::OpTypeImage &&
3211+
"Can only handle texel buffers for now.");
3212+
#endif
3213+
3214+
// For texel buffers, the index into the image is part of the OpImageRead or
3215+
// OpImageWrite instructions. So we will do nothing in this case. This
3216+
// intrinsic will be combined with the load or store when selecting the load
3217+
// or store.
3218+
return true;
31553219
}
31563220

31573221
bool SPIRVInstructionSelector::extractSubvector(
@@ -3203,15 +3267,20 @@ bool SPIRVInstructionSelector::selectImageWriteIntrinsic(
32033267
// We will do that when we can, but for now trying to move forward with other
32043268
// issues.
32053269
Register ImageReg = I.getOperand(1).getReg();
3206-
assert(MRI->getVRegDef(ImageReg)->getParent() == I.getParent() &&
3207-
"The image must be loaded in the same basic block as its use.");
3270+
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
3271+
Register NewImageReg = MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
3272+
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
3273+
*ImageDef, I)) {
3274+
return false;
3275+
}
3276+
32083277
Register CoordinateReg = I.getOperand(2).getReg();
32093278
Register DataReg = I.getOperand(3).getReg();
32103279
assert(GR.getResultType(DataReg)->getOpcode() == SPIRV::OpTypeVector);
32113280
assert(GR.getScalarOrVectorComponentCount(GR.getResultType(DataReg)) == 4);
32123281
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
32133282
TII.get(SPIRV::OpImageWrite))
3214-
.addUse(ImageReg)
3283+
.addUse(NewImageReg)
32153284
.addUse(CoordinateReg)
32163285
.addUse(DataReg)
32173286
.constrainAllUses(TII, TRI, RBI);
@@ -3878,6 +3947,36 @@ SPIRVType *SPIRVInstructionSelector::widenTypeToVec4(const SPIRVType *Type,
38783947
return GR.getOrCreateSPIRVVectorType(ScalarType, 4, MIRBuilder);
38793948
}
38803949

3950+
bool SPIRVInstructionSelector::loadHandleBeforePosition(
3951+
Register &HandleReg, const SPIRVType *ResType, GIntrinsic &HandleDef,
3952+
MachineInstr &Pos) const {
3953+
3954+
assert(HandleDef.getIntrinsicID() ==
3955+
Intrinsic::spv_resource_handlefrombinding);
3956+
uint32_t Set = foldImm(HandleDef.getOperand(2), MRI);
3957+
uint32_t Binding = foldImm(HandleDef.getOperand(3), MRI);
3958+
uint32_t ArraySize = foldImm(HandleDef.getOperand(4), MRI);
3959+
Register IndexReg = HandleDef.getOperand(5).getReg();
3960+
bool IsNonUniform = ArraySize > 1 && foldImm(HandleDef.getOperand(6), MRI);
3961+
3962+
MachineIRBuilder MIRBuilder(HandleDef);
3963+
Register VarReg = buildPointerToResource(ResType, Set, Binding, ArraySize,
3964+
IndexReg, IsNonUniform, MIRBuilder);
3965+
3966+
if (IsNonUniform)
3967+
buildOpDecorate(HandleReg, HandleDef, TII, SPIRV::Decoration::NonUniformEXT,
3968+
{});
3969+
3970+
// TODO: For now we assume the resource is an image, which needs to be
3971+
// loaded to get the handle. That will not be true for storage buffers.
3972+
return BuildMI(*Pos.getParent(), Pos, HandleDef.getDebugLoc(),
3973+
TII.get(SPIRV::OpLoad))
3974+
.addDef(HandleReg)
3975+
.addUse(GR.getSPIRVTypeID(ResType))
3976+
.addUse(VarReg)
3977+
.constrainAllUses(TII, TRI, RBI);
3978+
}
3979+
38813980
namespace llvm {
38823981
InstructionSelector *
38833982
createSPIRVInstructionSelector(const SPIRVTargetMachine &TM,

llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,14 +1694,16 @@ void addInstrRequirements(const MachineInstr &MI,
16941694
break;
16951695
case SPIRV::OpImageRead: {
16961696
Register ImageReg = MI.getOperand(2).getReg();
1697-
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
1697+
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(
1698+
ImageReg, const_cast<MachineFunction *>(MI.getMF()));
16981699
if (isImageTypeWithUnknownFormat(TypeDef))
16991700
Reqs.addCapability(SPIRV::Capability::StorageImageReadWithoutFormat);
17001701
break;
17011702
}
17021703
case SPIRV::OpImageWrite: {
17031704
Register ImageReg = MI.getOperand(0).getReg();
1704-
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
1705+
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(
1706+
ImageReg, const_cast<MachineFunction *>(MI.getMF()));
17051707
if (isImageTypeWithUnknownFormat(TypeDef))
17061708
Reqs.addCapability(SPIRV::Capability::StorageImageWriteWithoutFormat);
17071709
break;

0 commit comments

Comments
 (0)