[llvm] [SPIRV] Emit HLSL structured buffers (PR #132034)
Steven Perron via llvm-commits
llvm-commits at lists.llvm.org
Wed Mar 19 07:01:30 PDT 2025
https://github.com/s-perron created https://github.com/llvm/llvm-project/pull/132034
Adds code to expand the `llvm.spv.resource.handlefrombinding` and
`llvm.spv.resource.getpointer` when the resource type is
`spirv.VulkanBuffer`.
It gets expanded as a storage buffer or uniform buffer denpending on the
storage class used.
>From 1cf452a7eba7793065554c193016ae937520fd5a Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Fri, 24 Jan 2025 15:27:46 -0500
Subject: [PATCH] [SPIRV] Emit HLSL structured buffers
Adds code to expand the `llvm.spv.resource.handlefrombinding` and
`llvm.spv.resource.getpointer` when the resource type is
`spirv.VulkanBuffer`.
It gets expanded as a storage buffer or uniform buffer denpending on the
storage class used.
---
llvm/docs/SPIRVUsage.rst | 30 ++--
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 98 ++++++++----
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 23 ++-
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp | 74 ++++++---
.../Target/SPIRV/SPIRVInstructionSelector.cpp | 145 +++++++++++-------
llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp | 16 +-
llvm/lib/Target/SPIRV/SPIRVUtils.cpp | 26 ++++
llvm/lib/Target/SPIRV/SPIRVUtils.h | 13 ++
.../SPIRV/hlsl-resources/StructuredBuffer.ll | 70 +++++++++
9 files changed, 359 insertions(+), 136 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/StructuredBuffer.ll
diff --git a/llvm/docs/SPIRVUsage.rst b/llvm/docs/SPIRVUsage.rst
index 3e19ff881dffc..03ed93cdc9fc3 100644
--- a/llvm/docs/SPIRVUsage.rst
+++ b/llvm/docs/SPIRVUsage.rst
@@ -243,19 +243,20 @@ using target extension types and are represented as follows:
.. table:: SPIR-V Opaque Types
- ================== ====================== ===========================================================================================
- SPIR-V Type LLVM type name LLVM type arguments
- ================== ====================== ===========================================================================================
- OpTypeImage ``spirv.Image`` sampled type, dimensionality, depth, arrayed, MS, sampled, image format, [access qualifier]
- OpTypeSampler ``spirv.Sampler`` (none)
- OpTypeSampledImage ``spirv.SampledImage`` sampled type, dimensionality, depth, arrayed, MS, sampled, image format, [access qualifier]
- OpTypeEvent ``spirv.Event`` (none)
- OpTypeDeviceEvent ``spirv.DeviceEvent`` (none)
- OpTypeReserveId ``spirv.ReserveId`` (none)
- OpTypeQueue ``spirv.Queue`` (none)
- OpTypePipe ``spirv.Pipe`` access qualifier
- OpTypePipeStorage ``spirv.PipeStorage`` (none)
- ================== ====================== ===========================================================================================
+ ================== ======================= ===========================================================================================
+ SPIR-V Type LLVM type name LLVM type arguments
+ ================== ======================= ===========================================================================================
+ OpTypeImage ``spirv.Image`` sampled type, dimensionality, depth, arrayed, MS, sampled, image format, [access qualifier]
+ OpTypeSampler ``spirv.Sampler`` (none)
+ OpTypeSampledImage ``spirv.SampledImage`` sampled type, dimensionality, depth, arrayed, MS, sampled, image format, [access qualifier]
+ OpTypeEvent ``spirv.Event`` (none)
+ OpTypeDeviceEvent ``spirv.DeviceEvent`` (none)
+ OpTypeReserveId ``spirv.ReserveId`` (none)
+ OpTypeQueue ``spirv.Queue`` (none)
+ OpTypePipe ``spirv.Pipe`` access qualifier
+ OpTypePipeStorage ``spirv.PipeStorage`` (none)
+ NA ``spirv.VulkanBuffer`` ElementType, StorageClass, IsWriteable, IsROV
+ ================== ======================= ===========================================================================================
All integer arguments take the same value as they do in their `corresponding
SPIR-V instruction <https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_type_declaration_instructions>`_.
@@ -266,6 +267,9 @@ parameters of its underlying image type, so that a sampled image for the
previous type has the representation
``target("spirv.SampledImage, void, 1, 1, 0, 0, 0, 0, 0)``.
+See `wg-hlsl proposal 0018 <https://github.com/llvm/wg-hlsl/blob/main/proposals/0018-spirv-resource-representation.md>`_
+for details on ``spirv.VulkanBuffer``.
+
.. _spirv-intrinsics:
Target Intrinsics
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index 579e37f68d5d8..c4e681c887191 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -3041,6 +3041,34 @@ static SPIRVType *getSampledImageType(const TargetExtType *OpaqueType,
return GR->getOrCreateOpTypeSampledImage(OpaqueImageType, MIRBuilder);
}
+static SPIRVType *getVulkanBufferType(const TargetExtType *ExtensionType,
+ MachineIRBuilder &MIRBuilder,
+ SPIRVGlobalRegistry *GR) {
+ assert(ExtensionType->getNumTypeParameters() == 1 &&
+ "Vulkan buffers have exactly one type for the type of the buffer.");
+ assert(ExtensionType->getNumIntParameters() == 3 &&
+ "Vulkan buffer have 3 integer parameters: storage class, is writable, "
+ "and is rov");
+
+ auto *T = StructType::create(ExtensionType->getTypeParameter(0));
+ auto *BlockType = GR->getOrCreateSPIRVType(
+ T, MIRBuilder, SPIRV::AccessQualifier::None, false);
+ buildOpDecorate(BlockType->defs().begin()->getReg(), MIRBuilder,
+ SPIRV::Decoration::Block, {});
+ buildOpMemberDecorate(BlockType->defs().begin()->getReg(), MIRBuilder,
+ SPIRV::Decoration::Offset, 0, {0});
+
+ bool IsWritable = ExtensionType->getIntParameter(1);
+ if (!IsWritable) {
+ buildOpMemberDecorate(BlockType->defs().begin()->getReg(), MIRBuilder,
+ SPIRV::Decoration::NonWritable, 0, {});
+ }
+
+ auto SC = static_cast<SPIRV::StorageClass::StorageClass>(
+ ExtensionType->getIntParameter(0));
+ return GR->getOrCreateSPIRVPointerType(BlockType, MIRBuilder, SC);
+}
+
namespace SPIRV {
TargetExtType *parseBuiltinTypeNameToTargetExtType(std::string TypeName,
LLVMContext &Context) {
@@ -3113,39 +3141,45 @@ SPIRVType *lowerBuiltinType(const Type *OpaqueType,
const StringRef Name = BuiltinType->getName();
LLVM_DEBUG(dbgs() << "Lowering builtin type: " << Name << "\n");
- // Lookup the demangled builtin type in the TableGen records.
- const SPIRV::BuiltinType *TypeRecord = SPIRV::lookupBuiltinType(Name);
- if (!TypeRecord)
- report_fatal_error("Missing TableGen record for builtin type: " + Name);
-
- // "Lower" the BuiltinType into TargetType. The following get<...>Type methods
- // use the implementation details from TableGen records or TargetExtType
- // parameters to either create a new OpType<...> machine instruction or get an
- // existing equivalent SPIRVType from GlobalRegistry.
SPIRVType *TargetType;
- switch (TypeRecord->Opcode) {
- case SPIRV::OpTypeImage:
- TargetType = getImageType(BuiltinType, AccessQual, MIRBuilder, GR);
- break;
- case SPIRV::OpTypePipe:
- TargetType = getPipeType(BuiltinType, MIRBuilder, GR);
- break;
- case SPIRV::OpTypeDeviceEvent:
- TargetType = GR->getOrCreateOpTypeDeviceEvent(MIRBuilder);
- break;
- case SPIRV::OpTypeSampler:
- TargetType = getSamplerType(MIRBuilder, GR);
- break;
- case SPIRV::OpTypeSampledImage:
- TargetType = getSampledImageType(BuiltinType, MIRBuilder, GR);
- break;
- case SPIRV::OpTypeCooperativeMatrixKHR:
- TargetType = getCoopMatrType(BuiltinType, MIRBuilder, GR);
- break;
- default:
- TargetType =
- getNonParameterizedType(BuiltinType, TypeRecord, MIRBuilder, GR);
- break;
+ if (Name == "spirv.VulkanBuffer") {
+ TargetType = getVulkanBufferType(BuiltinType, MIRBuilder, GR);
+ } else {
+ // Lookup the demangled builtin type in the TableGen records.
+ const SPIRV::BuiltinType *TypeRecord = SPIRV::lookupBuiltinType(Name);
+ if (!TypeRecord)
+ report_fatal_error("Missing TableGen record for builtin type: " + Name);
+
+ // "Lower" the BuiltinType into TargetType. The following get<...>Type
+ // methods use the implementation details from TableGen records or
+ // TargetExtType parameters to either create a new OpType<...> machine
+ // instruction or get an existing equivalent SPIRVType from
+ // GlobalRegistry.
+
+ switch (TypeRecord->Opcode) {
+ case SPIRV::OpTypeImage:
+ TargetType = getImageType(BuiltinType, AccessQual, MIRBuilder, GR);
+ break;
+ case SPIRV::OpTypePipe:
+ TargetType = getPipeType(BuiltinType, MIRBuilder, GR);
+ break;
+ case SPIRV::OpTypeDeviceEvent:
+ TargetType = GR->getOrCreateOpTypeDeviceEvent(MIRBuilder);
+ break;
+ case SPIRV::OpTypeSampler:
+ TargetType = getSamplerType(MIRBuilder, GR);
+ break;
+ case SPIRV::OpTypeSampledImage:
+ TargetType = getSampledImageType(BuiltinType, MIRBuilder, GR);
+ break;
+ case SPIRV::OpTypeCooperativeMatrixKHR:
+ TargetType = getCoopMatrType(BuiltinType, MIRBuilder, GR);
+ break;
+ default:
+ TargetType =
+ getNonParameterizedType(BuiltinType, TypeRecord, MIRBuilder, GR);
+ break;
+ }
}
// Emit OpName instruction if a new OpType<...> instruction was added
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 68651f4ee4d2f..ed96602e46ad0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -667,13 +667,22 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
auto *II = dyn_cast<IntrinsicInst>(I);
if (II && II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
- auto *ImageType = cast<TargetExtType>(II->getOperand(0)->getType());
- assert(ImageType->getTargetExtName() == "spirv.Image");
- (void)ImageType;
- if (II->hasOneUse()) {
- auto *U = *II->users().begin();
- Ty = cast<Instruction>(U)->getAccessType();
- assert(Ty && "Unable to get type for resource pointer.");
+ auto *HandleType = cast<TargetExtType>(II->getOperand(0)->getType());
+ if (HandleType->getTargetExtName() == "spirv.Image") {
+ if (II->hasOneUse()) {
+ auto *U = *II->users().begin();
+ Ty = cast<Instruction>(U)->getAccessType();
+ assert(Ty && "Unable to get type for resource pointer.");
+ }
+ } else if (HandleType->getTargetExtName() == "spirv.VulkanBuffer") {
+ // This call is supposed to index into an array
+ Ty = HandleType->getTypeParameter(0);
+ assert(Ty->isArrayTy() &&
+ "spv_resource_getpointer indexes into an array, so the type of "
+ "the buffer should be an array.");
+ Ty = Ty->getArrayElementType();
+ } else {
+ llvm_unreachable("Unknown handle type for spv_resource_getpointer.");
}
} else if (Function *CalledF = CI->getCalledFunction()) {
std::string DemangledName =
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
index cbec1c95eadc3..b3aa477bfab94 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
@@ -799,16 +799,18 @@ Register SPIRVGlobalRegistry::buildGlobalVariable(
static std::string GetSpirvImageTypeName(const SPIRVType *Type,
MachineIRBuilder &MIRBuilder,
- const std::string &Prefix);
+ const std::string &Prefix,
+ SPIRVGlobalRegistry &GR);
static std::string buildSpirvTypeName(const SPIRVType *Type,
- MachineIRBuilder &MIRBuilder) {
+ MachineIRBuilder &MIRBuilder,
+ SPIRVGlobalRegistry &GR) {
switch (Type->getOpcode()) {
case SPIRV::OpTypeSampledImage: {
- return GetSpirvImageTypeName(Type, MIRBuilder, "sampled_image_");
+ return GetSpirvImageTypeName(Type, MIRBuilder, "sampled_image_", GR);
}
case SPIRV::OpTypeImage: {
- return GetSpirvImageTypeName(Type, MIRBuilder, "image_");
+ return GetSpirvImageTypeName(Type, MIRBuilder, "image_", GR);
}
case SPIRV::OpTypeArray: {
MachineRegisterInfo *MRI = MIRBuilder.getMRI();
@@ -819,7 +821,7 @@ static std::string buildSpirvTypeName(const SPIRVType *Type,
MachineInstr *ImmInst = MRI->getVRegDef(TypeInst->getOperand(1).getReg());
assert(ImmInst->getOpcode() == TargetOpcode::G_CONSTANT);
uint32_t ArraySize = ImmInst->getOperand(1).getCImm()->getZExtValue();
- return (buildSpirvTypeName(ElementType, MIRBuilder) + Twine("[") +
+ return (buildSpirvTypeName(ElementType, MIRBuilder, GR) + Twine("[") +
Twine(ArraySize) + Twine("]"))
.str();
}
@@ -831,6 +833,22 @@ static std::string buildSpirvTypeName(const SPIRVType *Type,
if (Type->getOperand(2).getImm())
return ("i" + Twine(Type->getOperand(1).getImm())).str();
return ("u" + Twine(Type->getOperand(1).getImm())).str();
+ case SPIRV::OpTypePointer: {
+ uint32_t StorageClass = GR.getPointerStorageClass(Type);
+ SPIRVType *PointeeType = GR.getPointeeType(Type);
+ return ("p_" + Twine(StorageClass) + Twine("_") +
+ buildSpirvTypeName(PointeeType, MIRBuilder, GR))
+ .str();
+ }
+ case SPIRV::OpTypeStruct: {
+ std::string TypeName = "{";
+ for (uint32_t I = 2; I < Type->getNumOperands(); ++I) {
+ SPIRVType *MemberType =
+ GR.getSPIRVTypeForVReg(Type->getOperand(I).getReg());
+ TypeName = '_' + buildSpirvTypeName(MemberType, MIRBuilder, GR);
+ }
+ return TypeName + "}";
+ }
default:
llvm_unreachable("Trying to the the name of an unknown type.");
}
@@ -838,10 +856,12 @@ static std::string buildSpirvTypeName(const SPIRVType *Type,
static std::string GetSpirvImageTypeName(const SPIRVType *Type,
MachineIRBuilder &MIRBuilder,
- const std::string &Prefix) {
+ const std::string &Prefix,
+ SPIRVGlobalRegistry &GR) {
Register SampledTypeReg = Type->getOperand(1).getReg();
auto *SampledType = MIRBuilder.getMRI()->getUniqueVRegDef(SampledTypeReg);
- std::string TypeName = Prefix + buildSpirvTypeName(SampledType, MIRBuilder);
+ std::string TypeName =
+ Prefix + buildSpirvTypeName(SampledType, MIRBuilder, GR);
for (uint32_t I = 2; I < Type->getNumOperands(); ++I) {
TypeName = (TypeName + '_' + Twine(Type->getOperand(I).getImm())).str();
}
@@ -851,20 +871,19 @@ static std::string GetSpirvImageTypeName(const SPIRVType *Type,
Register SPIRVGlobalRegistry::getOrCreateGlobalVariableWithBinding(
const SPIRVType *VarType, uint32_t Set, uint32_t Binding,
MachineIRBuilder &MIRBuilder) {
- SPIRVType *VarPointerTypeReg = getOrCreateSPIRVPointerType(
- VarType, MIRBuilder, SPIRV::StorageClass::UniformConstant);
Register VarReg =
MIRBuilder.getMRI()->createVirtualRegister(&SPIRV::iIDRegClass);
// TODO: The name should come from the llvm-ir, but how that name will be
// passed from the HLSL to the backend has not been decided. Using this place
// holder for now.
- std::string Name = ("__resource_" + buildSpirvTypeName(VarType, MIRBuilder) +
- "_" + Twine(Set) + "_" + Twine(Binding))
- .str();
- buildGlobalVariable(VarReg, VarPointerTypeReg, Name, nullptr,
- SPIRV::StorageClass::UniformConstant, nullptr, false,
- false, SPIRV::LinkageType::Import, MIRBuilder, false);
+ std::string Name =
+ ("__resource_" + buildSpirvTypeName(VarType, MIRBuilder, *this) + "_" +
+ Twine(Set) + "_" + Twine(Binding))
+ .str();
+ buildGlobalVariable(VarReg, VarType, Name, nullptr,
+ getPointerStorageClass(VarType), nullptr, false, false,
+ SPIRV::LinkageType::Import, MIRBuilder, false);
buildOpDecorate(VarReg, MIRBuilder, SPIRV::Decoration::DescriptorSet, {Set});
buildOpDecorate(VarReg, MIRBuilder, SPIRV::Decoration::Binding, {Binding});
@@ -878,14 +897,23 @@ SPIRVType *SPIRVGlobalRegistry::getOpTypeArray(uint32_t NumElems,
assert((ElemType->getOpcode() != SPIRV::OpTypeVoid) &&
"Invalid array element type");
SPIRVType *SpvTypeInt32 = getOrCreateSPIRVIntegerType(32, MIRBuilder);
- Register NumElementsVReg =
- buildConstantInt(NumElems, MIRBuilder, SpvTypeInt32, EmitIR);
- return createOpType(MIRBuilder, [&](MachineIRBuilder &MIRBuilder) {
- return MIRBuilder.buildInstr(SPIRV::OpTypeArray)
- .addDef(createTypeVReg(MIRBuilder))
- .addUse(getSPIRVTypeID(ElemType))
- .addUse(NumElementsVReg);
- });
+
+ if (NumElems != 0) {
+ Register NumElementsVReg =
+ buildConstantInt(NumElems, MIRBuilder, SpvTypeInt32, EmitIR);
+ return createOpType(MIRBuilder, [&](MachineIRBuilder &MIRBuilder) {
+ return MIRBuilder.buildInstr(SPIRV::OpTypeArray)
+ .addDef(createTypeVReg(MIRBuilder))
+ .addUse(getSPIRVTypeID(ElemType))
+ .addUse(NumElementsVReg);
+ });
+ } else {
+ return createOpType(MIRBuilder, [&](MachineIRBuilder &MIRBuilder) {
+ return MIRBuilder.buildInstr(SPIRV::OpTypeRuntimeArray)
+ .addDef(createTypeVReg(MIRBuilder))
+ .addUse(getSPIRVTypeID(ElemType));
+ });
+ }
}
SPIRVType *SPIRVGlobalRegistry::getOpTypeOpaque(const StructType *Ty,
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 6e0349a463aad..43bc3fbe4a872 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -327,9 +327,11 @@ class SPIRVInstructionSelector : public InstructionSelector {
uint32_t Opcode) const;
MachineInstrBuilder buildConstGenericPtr(MachineInstr &I, Register SrcPtr,
SPIRVType *SrcPtrTy) const;
- Register buildPointerToResource(const SPIRVType *ResType, uint32_t Set,
- uint32_t Binding, uint32_t ArraySize,
- Register IndexReg, bool IsNonUniform,
+ Register buildPointerToResource(const SPIRVType *ResType,
+ SPIRV::StorageClass::StorageClass SC,
+ uint32_t Set, uint32_t Binding,
+ uint32_t ArraySize, Register IndexReg,
+ bool IsNonUniform,
MachineIRBuilder MIRBuilder) const;
SPIRVType *widenTypeToVec4(const SPIRVType *Type, MachineInstr &I) const;
bool extractSubvector(Register &ResVReg, const SPIRVType *ResType,
@@ -1088,18 +1090,20 @@ bool SPIRVInstructionSelector::selectLoad(Register ResVReg,
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 HandleReg = IntPtrDef->getOperand(2).getReg();
+ SPIRVType *HandleType = GR.getSPIRVTypeForVReg(HandleReg);
+ if (HandleType->getOpcode() == SPIRV::OpTypeImage) {
+ Register NewHandleReg =
+ MRI->createVirtualRegister(MRI->getRegClass(HandleReg));
+ auto *HandleDef = cast<GIntrinsic>(getVRegDef(*MRI, HandleReg));
+ if (!loadHandleBeforePosition(NewHandleReg, HandleType, *HandleDef, I)) {
+ return false;
+ }
- Register IdxReg = IntPtrDef->getOperand(3).getReg();
- return generateImageRead(ResVReg, ResType, NewImageReg, IdxReg,
- I.getDebugLoc(), I);
+ Register IdxReg = IntPtrDef->getOperand(3).getReg();
+ return generateImageRead(ResVReg, ResType, NewHandleReg, IdxReg,
+ I.getDebugLoc(), I);
+ }
}
auto MIB = BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpLoad))
@@ -1127,22 +1131,24 @@ bool SPIRVInstructionSelector::selectStore(MachineInstr &I) const {
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)) {
+ Register HandleReg = IntPtrDef->getOperand(2).getReg();
+ Register NewHandleReg =
+ MRI->createVirtualRegister(MRI->getRegClass(HandleReg));
+ auto *HandleDef = cast<GIntrinsic>(getVRegDef(*MRI, HandleReg));
+ SPIRVType *HandleType = GR.getSPIRVTypeForVReg(HandleReg);
+ if (!loadHandleBeforePosition(NewHandleReg, HandleType, *HandleDef, 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);
+ if (HandleType->getOpcode() == SPIRV::OpTypeImage) {
+ return BuildMI(*I.getParent(), I, I.getDebugLoc(),
+ TII.get(SPIRV::OpImageWrite))
+ .addUse(NewHandleReg)
+ .addUse(IdxReg)
+ .addUse(StoreVal)
+ .constrainAllUses(TII, TRI, RBI);
+ }
}
MachineBasicBlock &BB = *I.getParent();
@@ -3212,7 +3218,13 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
bool SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
- return true;
+ // The images need to be loaded in the same basic block as their use. We defer
+ // loading the image to the intrinsic that uses it.
+ if (ResType->getOpcode() == SPIRV::OpTypeImage)
+ return true;
+
+ return loadHandleBeforePosition(ResVReg, GR.getSPIRVTypeForVReg(ResVReg),
+ *cast<GIntrinsic>(&I), I);
}
bool SPIRVInstructionSelector::selectReadImageIntrinsic(
@@ -3280,20 +3292,30 @@ bool SPIRVInstructionSelector::generateImageRead(Register &ResVReg,
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;
+ SPIRVType *RegType = GR.getSPIRVTypeForVReg(ResourcePtr, I.getMF());
+ if (RegType->getOpcode() == SPIRV::OpTypeImage) {
+ // 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;
+ }
+
+ assert(ResType->getOpcode() == SPIRV::OpTypePointer);
+ MachineIRBuilder MIRBuilder(I);
+
+ Register IndexReg = I.getOperand(3).getReg();
+ Register ZeroReg =
+ buildZerosVal(GR.getOrCreateSPIRVIntegerType(32, I, TII), I);
+ return BuildMI(*I.getParent(), I, I.getDebugLoc(),
+ TII.get(SPIRV::OpAccessChain))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(ResourcePtr)
+ .addUse(ZeroReg)
+ .addUse(IndexReg)
+ .constrainAllUses(TII, TRI, RBI);
}
bool SPIRVInstructionSelector::extractSubvector(
@@ -3365,22 +3387,27 @@ bool SPIRVInstructionSelector::selectImageWriteIntrinsic(
}
Register SPIRVInstructionSelector::buildPointerToResource(
- const SPIRVType *ResType, uint32_t Set, uint32_t Binding,
- uint32_t ArraySize, Register IndexReg, bool IsNonUniform,
- MachineIRBuilder MIRBuilder) const {
- if (ArraySize == 1)
- return GR.getOrCreateGlobalVariableWithBinding(ResType, Set, Binding,
+ const SPIRVType *ResType, SPIRV::StorageClass::StorageClass SC,
+ uint32_t Set, uint32_t Binding, uint32_t ArraySize, Register IndexReg,
+ bool IsNonUniform, MachineIRBuilder MIRBuilder) const {
+ if (ArraySize == 1) {
+ SPIRVType *PtrType =
+ GR.getOrCreateSPIRVPointerType(ResType, MIRBuilder, SC);
+ return GR.getOrCreateGlobalVariableWithBinding(PtrType, Set, Binding,
MIRBuilder);
+ }
const SPIRVType *VarType = GR.getOrCreateSPIRVArrayType(
ResType, ArraySize, *MIRBuilder.getInsertPt(), TII);
+ SPIRVType *VarPointerType =
+ GR.getOrCreateSPIRVPointerType(VarType, MIRBuilder, SC);
Register VarReg = GR.getOrCreateGlobalVariableWithBinding(
- VarType, Set, Binding, MIRBuilder);
+ VarPointerType, Set, Binding, MIRBuilder);
- SPIRVType *ResPointerType = GR.getOrCreateSPIRVPointerType(
- ResType, MIRBuilder, SPIRV::StorageClass::UniformConstant);
+ SPIRVType *ResPointerType =
+ GR.getOrCreateSPIRVPointerType(ResType, MIRBuilder, SC);
- Register AcReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
+ Register AcReg = MRI->createVirtualRegister(GR.getRegClass(ResPointerType));
if (IsNonUniform) {
// It is unclear which value needs to be marked an non-uniform, so both
// the index and the access changed are decorated as non-uniform.
@@ -4037,19 +4064,29 @@ bool SPIRVInstructionSelector::loadHandleBeforePosition(
uint32_t ArraySize = foldImm(HandleDef.getOperand(4), MRI);
Register IndexReg = HandleDef.getOperand(5).getReg();
bool IsNonUniform = ArraySize > 1 && foldImm(HandleDef.getOperand(6), MRI);
-
+ bool IsStructuredBuffer = ResType->getOpcode() == SPIRV::OpTypePointer;
MachineIRBuilder MIRBuilder(HandleDef);
- Register VarReg = buildPointerToResource(ResType, Set, Binding, ArraySize,
+ SPIRVType *VarType = ResType;
+ SPIRV::StorageClass::StorageClass SC = SPIRV::StorageClass::UniformConstant;
+
+ if (IsStructuredBuffer) {
+ VarType = GR.getPointeeType(ResType);
+ SC = GR.getPointerStorageClass(ResType);
+ }
+
+ Register VarReg = buildPointerToResource(VarType, SC, 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.
+ // The handle for the buffer is the pointer to the resource. For an image, the
+ // handle is the image object. So images get an extra load.
+ uint32_t LoadOpcode =
+ IsStructuredBuffer ? SPIRV::OpCopyObject : SPIRV::OpLoad;
return BuildMI(*Pos.getParent(), Pos, HandleDef.getDebugLoc(),
- TII.get(SPIRV::OpLoad))
+ TII.get(LoadOpcode))
.addDef(HandleReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(VarReg)
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index daa8ea52ffe03..a0d7b756eb710 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -122,13 +122,15 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
const LLT p7 = LLT::pointer(7, PSize); // Input
const LLT p8 = LLT::pointer(8, PSize); // Output
const LLT p10 = LLT::pointer(10, PSize); // Private
+ const LLT p11 = LLT::pointer(11, PSize); // StorageBuffer
// TODO: remove copy-pasting here by using concatenation in some way.
auto allPtrsScalarsAndVectors = {
- p0, p1, p2, p3, p4, p5, p6, p7, p8, p10,
- s1, s8, s16, s32, s64, v2s1, v2s8, v2s16, v2s32, v2s64,
- v3s1, v3s8, v3s16, v3s32, v3s64, v4s1, v4s8, v4s16, v4s32, v4s64,
- v8s1, v8s8, v8s16, v8s32, v8s64, v16s1, v16s8, v16s16, v16s32, v16s64};
+ p0, p1, p2, p3, p4, p5, p6, p7, p8,
+ p10, p11, s1, s8, s16, s32, s64, v2s1, v2s8,
+ v2s16, v2s32, v2s64, v3s1, v3s8, v3s16, v3s32, v3s64, v4s1,
+ v4s8, v4s16, v4s32, v4s64, v8s1, v8s8, v8s16, v8s32, v8s64,
+ v16s1, v16s8, v16s16, v16s32, v16s64};
auto allVectors = {v2s1, v2s8, v2s16, v2s32, v2s64, v3s1, v3s8,
v3s16, v3s32, v3s64, v4s1, v4s8, v4s16, v4s32,
@@ -155,10 +157,10 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
s16, s32, s64, v2s16, v2s32, v2s64, v3s16, v3s32, v3s64,
v4s16, v4s32, v4s64, v8s16, v8s32, v8s64, v16s16, v16s32, v16s64};
- auto allFloatAndIntScalarsAndPtrs = {s8, s16, s32, s64, p0, p1, p2,
- p3, p4, p5, p6, p7, p8, p10};
+ auto allFloatAndIntScalarsAndPtrs = {s8, s16, s32, s64, p0, p1, p2, p3,
+ p4, p5, p6, p7, p8, p10, p11};
- auto allPtrs = {p0, p1, p2, p3, p4, p5, p6, p7, p8, p10};
+ auto allPtrs = {p0, p1, p2, p3, p4, p5, p6, p7, p8, p10, p11};
bool IsExtendedInts =
ST.canUseExtension(
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 6edc757a88d37..f1b4f1e3b8205 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -146,6 +146,30 @@ void buildOpDecorate(Register Reg, MachineInstr &I, const SPIRVInstrInfo &TII,
finishBuildOpDecorate(MIB, DecArgs, StrImm);
}
+void buildOpMemberDecorate(Register Reg, MachineIRBuilder &MIRBuilder,
+ SPIRV::Decoration::Decoration Dec, uint32_t Member,
+ const std::vector<uint32_t> &DecArgs,
+ StringRef StrImm) {
+ auto MIB = MIRBuilder.buildInstr(SPIRV::OpMemberDecorate)
+ .addUse(Reg)
+ .addImm(Member)
+ .addImm(static_cast<uint32_t>(Dec));
+ finishBuildOpDecorate(MIB, DecArgs, StrImm);
+}
+
+void buildOpMemberDecorate(Register Reg, MachineInstr &I,
+ const SPIRVInstrInfo &TII,
+ SPIRV::Decoration::Decoration Dec, uint32_t Member,
+ const std::vector<uint32_t> &DecArgs,
+ StringRef StrImm) {
+ MachineBasicBlock &MBB = *I.getParent();
+ auto MIB = BuildMI(MBB, I, I.getDebugLoc(), TII.get(SPIRV::OpMemberDecorate))
+ .addUse(Reg)
+ .addImm(Member)
+ .addImm(static_cast<uint32_t>(Dec));
+ finishBuildOpDecorate(MIB, DecArgs, StrImm);
+}
+
void buildOpSpirvDecorations(Register Reg, MachineIRBuilder &MIRBuilder,
const MDNode *GVarMD) {
for (unsigned I = 0, E = GVarMD->getNumOperands(); I != E; ++I) {
@@ -236,6 +260,8 @@ addressSpaceToStorageClass(unsigned AddrSpace, const SPIRVSubtarget &STI) {
return SPIRV::StorageClass::CodeSectionINTEL;
case 10:
return SPIRV::StorageClass::Private;
+ case 11:
+ return SPIRV::StorageClass::StorageBuffer;
default:
report_fatal_error("Unknown address space");
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 9100e6fbf39cc..998ed3c72ad2d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -144,6 +144,17 @@ void buildOpDecorate(Register Reg, MachineInstr &I, const SPIRVInstrInfo &TII,
const std::vector<uint32_t> &DecArgs,
StringRef StrImm = "");
+// Add an OpDecorate instruction for the given Reg.
+void buildOpMemberDecorate(Register Reg, MachineIRBuilder &MIRBuilder,
+ SPIRV::Decoration::Decoration Dec, uint32_t Member,
+ const std::vector<uint32_t> &DecArgs,
+ StringRef StrImm = "");
+void buildOpMemberDecorate(Register Reg, MachineInstr &I,
+ const SPIRVInstrInfo &TII,
+ SPIRV::Decoration::Decoration Dec, uint32_t Member,
+ const std::vector<uint32_t> &DecArgs,
+ StringRef StrImm = "");
+
// Add an OpDecorate instruction by "spirv.Decorations" metadata node.
void buildOpSpirvDecorations(Register Reg, MachineIRBuilder &MIRBuilder,
const MDNode *GVarMD);
@@ -184,6 +195,8 @@ storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) {
return 9;
case SPIRV::StorageClass::Private:
return 10;
+ case SPIRV::StorageClass::StorageBuffer:
+ return 11;
default:
report_fatal_error("Unable to get address space id");
}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/StructuredBuffer.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/StructuredBuffer.ll
new file mode 100644
index 0000000000000..ff37642955874
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/StructuredBuffer.ll
@@ -0,0 +1,70 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+
+; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none)
+declare target("spirv.VulkanBuffer", [0 x i32], 12, 0, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_0_0t(i32, i32, i32, i32, i1) #0
+
+; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none)
+declare target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_i32_5_2_0_0_2_0t(i32, i32, i32, i32, i1) #0
+
+; CHECK-DAG: OpDecorate [[BufferVar:%.+]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[BufferVar]] Binding 0
+; CHECK-DAG: OpDecorate [[BufferType:%.+]] Block
+; STVEN-CHECK-DAG: OpMemberDecorate [[BufferType]] 0 Offset 0 // The decoration is not output correctly in the assembly output
+; CHECK-DAG: OpMemberDecorate [[BufferType]] 0 NonWritable
+; CHECK-DAG: OpDecorate [[ImageVar:%.+]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[ImageVar]] Binding 1
+
+
+; CHECK-DAG: [[ArrayType:%.+]] = OpTypeRuntimeArray
+; CHECK-DAG: [[BufferType]] = OpTypeStruct [[ArrayType]]
+; CHECK-DAG: [[BufferPtrType:%.+]] = OpTypePointer StorageBuffer [[BufferType]]
+; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0
+; CHECK-DAG: [[one:%[0-9]+]] = OpConstant [[int]] 1
+; CHECK-DAG: [[BufferVar]] = OpVariable [[BufferPtrType]] StorageBuffer
+; CHECK-DAG: [[ImageVar]] = OpVariable {{.*}} UniformConstant
+
+; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none)
+define void @main() local_unnamed_addr #1 {
+entry:
+; CHECK: [[BufferHandle:%.+]] = OpCopyObject [[BufferPtrType]] [[BufferVar]]
+ %i_h.i.i = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 0, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_0_0t(i32 0, i32 0, i32 1, i32 0, i1 false)
+
+ %o_h.i.i = tail call target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_i32_5_2_0_0_2_0t(i32 0, i32 1, i32 1, i32 0, i1 false)
+
+; CHECK: [[AC:%.+]] = OpAccessChain {{.*}} [[BufferHandle]] [[zero]] [[one]]
+ %0 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_0_0t(target("spirv.VulkanBuffer", [0 x i32], 12, 0, 0) %i_h.i.i, i32 1)
+
+; CHECK: [[LD:%.+]] = OpLoad [[int]] [[AC]] Aligned 4
+ %1 = load i32, ptr addrspace(11) %0, align 4, !tbaa !3
+
+; CHECK: [[ImageHandle:%.+]] = OpLoad {{.*}} [[ImageVar]]
+ %2 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_i32_5_2_0_0_2_0t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) %o_h.i.i, i32 0)
+
+; CHECK: OpImageWrite [[ImageHandle]] [[zero]] [[LD]]
+ store i32 %1, ptr addrspace(11) %2, align 4, !tbaa !3
+ ret void
+}
+
+; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none)
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_i32_5_2_0_0_2_0t(target("spirv.Image", i32, 5, 2, 0, 0, 2, 0), i32) #0
+
+; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none)
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_0_0t(target("spirv.VulkanBuffer", [0 x i32], 12, 0, 0), i32) #0
+
+attributes #0 = { mustprogress nocallback nofree nosync nounwind willreturn memory(none) }
+attributes #1 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) "approx-func-fp-math"="false" "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 7, !"frame-pointer", i32 2}
+!2 = !{!"clang version 21.0.0git (git at github.com:s-perron/llvm-project.git 6e86add06c03e328dbb4b83f99406cc832a22f86)"}
+!3 = !{!4, !4, i64 0}
+!4 = !{!"int", !5, i64 0}
+!5 = !{!"omnipotent char", !6, i64 0}
+!6 = !{!"Simple C++ TBAA"}
More information about the llvm-commits
mailing list