Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SPIRV] Expand RWBuffer load and store from HLSL #122355

Merged
merged 3 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,14 @@ bool expectIgnoredInIRTranslation(const Instruction *I) {
const auto *II = dyn_cast<IntrinsicInst>(I);
if (!II)
return false;
return II->getIntrinsicID() == Intrinsic::invariant_start;
switch (II->getIntrinsicID()) {
case Intrinsic::invariant_start:
case Intrinsic::spv_resource_handlefrombinding:
case Intrinsic::spv_resource_getpointer:
return true;
default:
return false;
}
}

bool allowEmitFakeUse(const Value *Arg) {
Expand Down Expand Up @@ -725,6 +732,14 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
if (Ty)
break;
}
} else if (auto *II = dyn_cast<IntrinsicInst>(I)) {
s-perron marked this conversation as resolved.
Show resolved Hide resolved
if (II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
auto *ImageType = cast<TargetExtType>(II->getOperand(0)->getType());
assert(ImageType->getTargetExtName() == "spirv.Image");
Ty = ImageType->getTypeParameter(0);
// TODO: Need to look at the use to see if it needs to be a vector of the
// type.
}
} else if (auto *CI = dyn_cast<CallInst>(I)) {
static StringMap<unsigned> ResTypeByArg = {
{"to_global", 0},
Expand Down
9 changes: 6 additions & 3 deletions llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1114,9 +1114,12 @@ SPIRVGlobalRegistry::getSPIRVTypeForVReg(Register VReg,
return nullptr;
}

SPIRVType *SPIRVGlobalRegistry::getResultType(Register VReg) {
MachineInstr *Instr = getVRegDef(CurMF->getRegInfo(), VReg);
return getSPIRVTypeForVReg(Instr->getOperand(1).getReg());
SPIRVType *SPIRVGlobalRegistry::getResultType(Register VReg,
MachineFunction *MF) {
if (!MF)
MF = CurMF;
MachineInstr *Instr = getVRegDef(MF->getRegInfo(), VReg);
return getSPIRVTypeForVReg(Instr->getOperand(1).getReg(), MF);
}

SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVType(
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ class SPIRVGlobalRegistry {
const MachineFunction *MF = nullptr) const;

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

// Whether the given VReg has a SPIR-V type mapped to it yet.
bool hasSPIRVTypeForVReg(Register VReg) const {
Expand Down
169 changes: 134 additions & 35 deletions llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,9 @@ class SPIRVInstructionSelector : public InstructionSelector {

bool selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;

bool selectImageWriteIntrinsic(MachineInstr &I) const;
bool selectResourceGetPointer(Register &ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;

// Utilities
std::pair<Register, bool>
Expand Down Expand Up @@ -307,10 +308,15 @@ class SPIRVInstructionSelector : public InstructionSelector {
SPIRVType *widenTypeToVec4(const SPIRVType *Type, MachineInstr &I) const;
bool extractSubvector(Register &ResVReg, const SPIRVType *ResType,
Register &ReadReg, MachineInstr &InsertionPoint) const;
bool generateImageRead(Register &ResVReg, const SPIRVType *ResType,
Register ImageReg, Register IdxReg, DebugLoc Loc,
MachineInstr &Pos) const;
bool BuildCOPY(Register DestReg, Register SrcReg, MachineInstr &I) const;
bool loadVec3BuiltinInputID(SPIRV::BuiltIn::BuiltIn BuiltInValue,
Register ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;
bool loadHandleBeforePosition(Register &HandleReg, const SPIRVType *ResType,
GIntrinsic &HandleDef, MachineInstr &Pos) const;
};

} // end anonymous namespace
Expand Down Expand Up @@ -1018,6 +1024,25 @@ bool SPIRVInstructionSelector::selectLoad(Register ResVReg,
MachineInstr &I) const {
unsigned OpOffset = isa<GIntrinsic>(I) ? 1 : 0;
Register Ptr = I.getOperand(1 + OpOffset).getReg();

auto *PtrDef = getVRegDef(*MRI, Ptr);
auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
if (IntPtrDef &&
IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
Keenuts marked this conversation as resolved.
Show resolved Hide resolved
Register ImageReg = IntPtrDef->getOperand(2).getReg();
Register NewImageReg =
MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
*ImageDef, I)) {
return false;
}

Register IdxReg = IntPtrDef->getOperand(3).getReg();
return generateImageRead(ResVReg, ResType, NewImageReg, IdxReg,
I.getDebugLoc(), I);
}

auto MIB = BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
Expand All @@ -1037,6 +1062,29 @@ bool SPIRVInstructionSelector::selectStore(MachineInstr &I) const {
unsigned OpOffset = isa<GIntrinsic>(I) ? 1 : 0;
Register StoreVal = I.getOperand(0 + OpOffset).getReg();
Register Ptr = I.getOperand(1 + OpOffset).getReg();

auto *PtrDef = getVRegDef(*MRI, Ptr);
auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
if (IntPtrDef &&
IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
Register ImageReg = IntPtrDef->getOperand(2).getReg();
Register NewImageReg =
MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
*ImageDef, I)) {
return false;
}

Register IdxReg = IntPtrDef->getOperand(3).getReg();
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
TII.get(SPIRV::OpImageWrite))
.addUse(NewImageReg)
.addUse(IdxReg)
.addUse(StoreVal)
.constrainAllUses(TII, TRI, RBI);
}

MachineBasicBlock &BB = *I.getParent();
auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpStore))
.addUse(Ptr)
Expand Down Expand Up @@ -3007,6 +3055,9 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
case Intrinsic::spv_resource_load_typedbuffer: {
return selectReadImageIntrinsic(ResVReg, ResType, I);
}
case Intrinsic::spv_resource_getpointer: {
return selectResourceGetPointer(ResVReg, ResType, I);
}
case Intrinsic::spv_discard: {
return selectDiscard(ResVReg, ResType, I);
}
Expand All @@ -3024,27 +3075,7 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
bool SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {

uint32_t Set = foldImm(I.getOperand(2), MRI);
uint32_t Binding = foldImm(I.getOperand(3), MRI);
uint32_t ArraySize = foldImm(I.getOperand(4), MRI);
Register IndexReg = I.getOperand(5).getReg();
bool IsNonUniform = ArraySize > 1 && foldImm(I.getOperand(6), MRI);

MachineIRBuilder MIRBuilder(I);
Register VarReg = buildPointerToResource(ResType, Set, Binding, ArraySize,
IndexReg, IsNonUniform, MIRBuilder);

if (IsNonUniform)
buildOpDecorate(ResVReg, I, TII, SPIRV::Decoration::NonUniformEXT, {});

// TODO: For now we assume the resource is an image, which needs to be
// loaded to get the handle. That will not be true for storage buffers.
return BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(VarReg)
.constrainAllUses(TII, TRI, RBI);
return true;
}

bool SPIRVInstructionSelector::selectReadImageIntrinsic(
Expand All @@ -3057,42 +3088,75 @@ bool SPIRVInstructionSelector::selectReadImageIntrinsic(
// We will do that when we can, but for now trying to move forward with other
// issues.
Register ImageReg = I.getOperand(2).getReg();
assert(MRI->getVRegDef(ImageReg)->getParent() == I.getParent() &&
"The image must be loaded in the same basic block as its use.");
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
Register NewImageReg = MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
*ImageDef, I)) {
return false;
}

Register IdxReg = I.getOperand(3).getReg();
DebugLoc Loc = I.getDebugLoc();
MachineInstr &Pos = I;

return generateImageRead(ResVReg, ResType, NewImageReg, IdxReg, Loc, Pos);
}

bool SPIRVInstructionSelector::generateImageRead(Register &ResVReg,
const SPIRVType *ResType,
Register ImageReg,
Register IdxReg, DebugLoc Loc,
MachineInstr &Pos) const {
uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType);
if (ResultSize == 4) {
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
TII.get(SPIRV::OpImageRead))
return BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpImageRead))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(ImageReg)
.addUse(I.getOperand(3).getReg())
.addUse(IdxReg)
.constrainAllUses(TII, TRI, RBI);
}

SPIRVType *ReadType = widenTypeToVec4(ResType, I);
SPIRVType *ReadType = widenTypeToVec4(ResType, Pos);
Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType));
bool Succeed =
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageRead))
BuildMI(*Pos.getParent(), Pos, Loc, TII.get(SPIRV::OpImageRead))
.addDef(ReadReg)
.addUse(GR.getSPIRVTypeID(ReadType))
.addUse(ImageReg)
.addUse(I.getOperand(3).getReg())
.addUse(IdxReg)
.constrainAllUses(TII, TRI, RBI);
if (!Succeed)
return false;

if (ResultSize == 1) {
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
return BuildMI(*Pos.getParent(), Pos, Loc,
TII.get(SPIRV::OpCompositeExtract))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(ReadReg)
.addImm(0)
.constrainAllUses(TII, TRI, RBI);
}
return extractSubvector(ResVReg, ResType, ReadReg, I);
return extractSubvector(ResVReg, ResType, ReadReg, Pos);
}

bool SPIRVInstructionSelector::selectResourceGetPointer(
Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const {
#ifdef ASSERT
// For now, the operand is an image. This will change once we start handling
// more resource types.
Register ResourcePtr = I.getOperand(2).getReg();
SPIRVType *RegType = GR.getResultType(ResourcePtr);
assert(RegType->getOpcode() == SPIRV::OpTypeImage &&
"Can only handle texel buffers for now.");
#endif

// For texel buffers, the index into the image is part of the OpImageRead or
// OpImageWrite instructions. So we will do nothing in this case. This
// intrinsic will be combined with the load or store when selecting the load
// or store.
return true;
}

bool SPIRVInstructionSelector::extractSubvector(
Expand Down Expand Up @@ -3144,15 +3208,20 @@ bool SPIRVInstructionSelector::selectImageWriteIntrinsic(
// We will do that when we can, but for now trying to move forward with other
// issues.
Register ImageReg = I.getOperand(1).getReg();
assert(MRI->getVRegDef(ImageReg)->getParent() == I.getParent() &&
"The image must be loaded in the same basic block as its use.");
auto *ImageDef = cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
Register NewImageReg = MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
*ImageDef, I)) {
return false;
}

Register CoordinateReg = I.getOperand(2).getReg();
Register DataReg = I.getOperand(3).getReg();
assert(GR.getResultType(DataReg)->getOpcode() == SPIRV::OpTypeVector);
assert(GR.getScalarOrVectorComponentCount(GR.getResultType(DataReg)) == 4);
return BuildMI(*I.getParent(), I, I.getDebugLoc(),
TII.get(SPIRV::OpImageWrite))
.addUse(ImageReg)
.addUse(NewImageReg)
.addUse(CoordinateReg)
.addUse(DataReg)
.constrainAllUses(TII, TRI, RBI);
Expand Down Expand Up @@ -3677,6 +3746,36 @@ SPIRVType *SPIRVInstructionSelector::widenTypeToVec4(const SPIRVType *Type,
return GR.getOrCreateSPIRVVectorType(ScalarType, 4, MIRBuilder);
}

bool SPIRVInstructionSelector::loadHandleBeforePosition(
Register &HandleReg, const SPIRVType *ResType, GIntrinsic &HandleDef,
MachineInstr &Pos) const {

assert(HandleDef.getIntrinsicID() ==
Intrinsic::spv_resource_handlefrombinding);
uint32_t Set = foldImm(HandleDef.getOperand(2), MRI);
uint32_t Binding = foldImm(HandleDef.getOperand(3), MRI);
uint32_t ArraySize = foldImm(HandleDef.getOperand(4), MRI);
Register IndexReg = HandleDef.getOperand(5).getReg();
bool IsNonUniform = ArraySize > 1 && foldImm(HandleDef.getOperand(6), MRI);

MachineIRBuilder MIRBuilder(HandleDef);
Register VarReg = buildPointerToResource(ResType, Set, Binding, ArraySize,
IndexReg, IsNonUniform, MIRBuilder);

if (IsNonUniform)
buildOpDecorate(HandleReg, HandleDef, TII, SPIRV::Decoration::NonUniformEXT,
{});

// TODO: For now we assume the resource is an image, which needs to be
// loaded to get the handle. That will not be true for storage buffers.
return BuildMI(*Pos.getParent(), Pos, HandleDef.getDebugLoc(),
TII.get(SPIRV::OpLoad))
.addDef(HandleReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(VarReg)
.constrainAllUses(TII, TRI, RBI);
}

namespace llvm {
InstructionSelector *
createSPIRVInstructionSelector(const SPIRVTargetMachine &TM,
Expand Down
6 changes: 4 additions & 2 deletions llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1696,14 +1696,16 @@ void addInstrRequirements(const MachineInstr &MI,
break;
case SPIRV::OpImageRead: {
Register ImageReg = MI.getOperand(2).getReg();
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(
ImageReg, const_cast<MachineFunction *>(MI.getMF()));
if (isImageTypeWithUnknownFormat(TypeDef))
Reqs.addCapability(SPIRV::Capability::StorageImageReadWithoutFormat);
break;
}
case SPIRV::OpImageWrite: {
Register ImageReg = MI.getOperand(0).getReg();
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(
ImageReg, const_cast<MachineFunction *>(MI.getMF()));
if (isImageTypeWithUnknownFormat(TypeDef))
Reqs.addCapability(SPIRV::Capability::StorageImageWriteWithoutFormat);
break;
Expand Down
Loading
Loading