diff --git a/llvm/docs/SPIRVUsage.rst b/llvm/docs/SPIRVUsage.rst index 0fcaa366c8a3e..277b9c15292c5 100644 --- a/llvm/docs/SPIRVUsage.rst +++ b/llvm/docs/SPIRVUsage.rst @@ -392,6 +392,14 @@ SPIR-V backend, along with their descriptions and argument details. If `arraySize > 1`, then the binding represents an array of resources\ of the given size, and the handle for the resource at the given index is returned.\ If the index is possibly non-uniform, then `isUniformIndex` must get set to true. + * - `int_spv_typeBufferLoad` + - Scalar or vector + - `[spirv.Image ImageBuffer, 32-bit Integer coordinate]` + - Loads a value from a Vulkan image buffer at the given coordinate. The \ + image buffer data is assumed to be stored as a 4-element vector. If the \ + return type is a scalar, then the first element of the vector is \ + returned. If the return type is an n-element vector, then the first \ + n-elements of the 4-element vector are returned. .. _spirv-builtin-functions: diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td index 962b25560d751..f29eb7ee22b2d 100644 --- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td +++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td @@ -106,4 +106,10 @@ let TargetPrefix = "spv" in { [IntrNoMem]>; def int_spv_firstbituhigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>; def int_spv_firstbitshigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>; + + // Read a value from the image buffer. It does not translate directly to a + // single OpImageRead because the result type is not necessarily a 4 element + // vector. + def int_spv_typedBufferLoad + : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_i32_ty]>; } diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp index f66506beaa6ed..6f222883ee07d 100644 --- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp @@ -1056,6 +1056,11 @@ 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::getOrCreateSPIRVType( const Type *Ty, MachineIRBuilder &MIRBuilder, SPIRV::AccessQualifier::AccessQualifier AccessQual, bool EmitIR) { @@ -1126,6 +1131,24 @@ SPIRVGlobalRegistry::getScalarOrVectorComponentCount(SPIRVType *Type) const { : 1; } +SPIRVType * +SPIRVGlobalRegistry::getScalarOrVectorComponentType(Register VReg) const { + return getScalarOrVectorComponentType(getSPIRVTypeForVReg(VReg)); +} + +SPIRVType * +SPIRVGlobalRegistry::getScalarOrVectorComponentType(SPIRVType *Type) const { + if (!Type) + return nullptr; + Register ScalarReg = Type->getOpcode() == SPIRV::OpTypeVector + ? Type->getOperand(1).getReg() + : Type->getOperand(0).getReg(); + SPIRVType *ScalarType = getSPIRVTypeForVReg(ScalarReg); + assert(isScalarOrVectorOfType(Type->getOperand(0).getReg(), + ScalarType->getOpcode())); + return ScalarType; +} + unsigned SPIRVGlobalRegistry::getScalarOrVectorBitWidth(const SPIRVType *Type) const { assert(Type && "Invalid Type pointer"); diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h index a95b488960c4c..7dfe8ef6366f9 100644 --- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h +++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h @@ -353,6 +353,9 @@ class SPIRVGlobalRegistry { SPIRVType *getSPIRVTypeForVReg(Register VReg, const MachineFunction *MF = nullptr) const; + // Return the result type of the instruction defining the register. + SPIRVType *getResultType(Register VReg); + // Whether the given VReg has a SPIR-V type mapped to it yet. bool hasSPIRVTypeForVReg(Register VReg) const { return getSPIRVTypeForVReg(VReg) != nullptr; @@ -388,6 +391,12 @@ class SPIRVGlobalRegistry { unsigned getScalarOrVectorComponentCount(Register VReg) const; unsigned getScalarOrVectorComponentCount(SPIRVType *Type) const; + // Return the component type in a vector if the argument is associated with + // a vector type. Returns the argument itself for other types, and nullptr + // for a missing type. + SPIRVType *getScalarOrVectorComponentType(Register VReg) const; + SPIRVType *getScalarOrVectorComponentType(SPIRVType *Type) const; + // For vectors or scalars of booleans, integers and floats, return the scalar // type's bitwidth. Otherwise calls llvm_unreachable(). unsigned getScalarOrVectorBitWidth(const SPIRVType *Type) const; diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp index a968b65c17306..7da4cadfd8807 100644 --- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp @@ -22,6 +22,7 @@ #include "SPIRVTargetMachine.h" #include "SPIRVUtils.h" #include "llvm/ADT/APFloat.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h" #include "llvm/CodeGen/GlobalISel/GenericMachineInstrs.h" #include "llvm/CodeGen/GlobalISel/InstructionSelector.h" @@ -267,6 +268,9 @@ class SPIRVInstructionSelector : public InstructionSelector { void selectHandleFromBinding(Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const; + void selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType, + MachineInstr &I) const; + // Utilities Register buildI32Constant(uint32_t Val, MachineInstr &I, const SPIRVType *ResType = nullptr) const; @@ -291,6 +295,9 @@ class SPIRVInstructionSelector : public InstructionSelector { uint32_t Binding, uint32_t ArraySize, Register IndexReg, bool IsNonUniform, MachineIRBuilder MIRBuilder) const; + SPIRVType *widenTypeToVec4(const SPIRVType *Type, MachineInstr &I) const; + void extractSubvector(Register &ResVReg, const SPIRVType *ResType, + Register &ReadReg, MachineInstr &InsertionPoint) const; }; } // end anonymous namespace @@ -2809,6 +2816,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg, selectHandleFromBinding(ResVReg, ResType, I); return true; } + case Intrinsic::spv_typedBufferLoad: { + selectReadImageIntrinsic(ResVReg, ResType, I); + return true; + } default: { std::string DiagMsg; raw_string_ostream OS(DiagMsg); @@ -2845,6 +2856,83 @@ void SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg, .addUse(VarReg); } +void SPIRVInstructionSelector::selectReadImageIntrinsic( + Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const { + + // If the load of the image is in a different basic block, then + // this will generate invalid code. A proper solution is to move + // the OpLoad from selectHandleFromBinding here. However, to do + // that we will need to change the return type of the intrinsic. + // 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."); + + uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType); + if (ResultSize == 4) { + BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageRead)) + .addDef(ResVReg) + .addUse(GR.getSPIRVTypeID(ResType)) + .addUse(ImageReg) + .addUse(I.getOperand(3).getReg()); + return; + } + + SPIRVType *ReadType = widenTypeToVec4(ResType, I); + Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType)); + BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageRead)) + .addDef(ReadReg) + .addUse(GR.getSPIRVTypeID(ReadType)) + .addUse(ImageReg) + .addUse(I.getOperand(3).getReg()); + + if (ResultSize == 1) { + BuildMI(*I.getParent(), I, I.getDebugLoc(), + TII.get(SPIRV::OpCompositeExtract)) + .addDef(ResVReg) + .addUse(GR.getSPIRVTypeID(ResType)) + .addUse(ReadReg) + .addImm(0); + return; + } + extractSubvector(ResVReg, ResType, ReadReg, I); +} + +void SPIRVInstructionSelector::extractSubvector( + Register &ResVReg, const SPIRVType *ResType, Register &ReadReg, + MachineInstr &InsertionPoint) const { + SPIRVType *InputType = GR.getResultType(ReadReg); + uint64_t InputSize = GR.getScalarOrVectorComponentCount(InputType); + uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType); + assert(InputSize > 1 && "The input must be a vector."); + assert(ResultSize > 1 && "The result must be a vector."); + assert(ResultSize < InputSize && + "Cannot extract more element than there are in the input."); + SmallVector ComponentRegisters; + SPIRVType *ScalarType = GR.getScalarOrVectorComponentType(ResType); + const TargetRegisterClass *ScalarRegClass = GR.getRegClass(ScalarType); + for (uint64_t I = 0; I < ResultSize; I++) { + Register ComponentReg = MRI->createVirtualRegister(ScalarRegClass); + BuildMI(*InsertionPoint.getParent(), InsertionPoint, + InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract)) + .addDef(ComponentReg) + .addUse(ScalarType->getOperand(0).getReg()) + .addUse(ReadReg) + .addImm(I); + ComponentRegisters.emplace_back(ComponentReg); + } + + MachineInstrBuilder MIB = BuildMI(*InsertionPoint.getParent(), InsertionPoint, + InsertionPoint.getDebugLoc(), + TII.get(SPIRV::OpCompositeConstruct)) + .addDef(ResVReg) + .addUse(GR.getSPIRVTypeID(ResType)); + + for (Register ComponentReg : ComponentRegisters) + MIB.addUse(ComponentReg); +} + Register SPIRVInstructionSelector::buildPointerToResource( const SPIRVType *ResType, uint32_t Set, uint32_t Binding, uint32_t ArraySize, Register IndexReg, bool IsNonUniform, @@ -3347,6 +3435,21 @@ bool SPIRVInstructionSelector::selectSpvThreadId(Register ResVReg, return MIB.constrainAllUses(TII, TRI, RBI); } +SPIRVType *SPIRVInstructionSelector::widenTypeToVec4(const SPIRVType *Type, + MachineInstr &I) const { + MachineIRBuilder MIRBuilder(I); + if (Type->getOpcode() != SPIRV::OpTypeVector) + return GR.getOrCreateSPIRVVectorType(Type, 4, MIRBuilder); + + uint64_t VectorSize = Type->getOperand(2).getImm(); + if (VectorSize == 4) + return Type; + + Register ScalarTypeReg = Type->getOperand(1).getReg(); + const SPIRVType *ScalarType = GR.getSPIRVTypeForVReg(ScalarTypeReg); + return GR.getOrCreateSPIRVVectorType(ScalarType, 4, MIRBuilder); +} + namespace llvm { InstructionSelector * createSPIRVInstructionSelector(const SPIRVTargetMachine &TM, diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp index e8641b3a105de..0308c56093064 100644 --- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp @@ -660,7 +660,8 @@ void RequirementHandler::initAvailableCapabilitiesForOpenCL( // Add the min requirements for different OpenCL and SPIR-V versions. addAvailableCaps({Capability::Addresses, Capability::Float16Buffer, Capability::Kernel, Capability::Vector16, - Capability::Groups, Capability::GenericPointer}); + Capability::Groups, Capability::GenericPointer, + Capability::StorageImageReadWithoutFormat}); if (ST.hasOpenCLFullProfile()) addAvailableCaps({Capability::Int64, Capability::Int64Atomics}); if (ST.hasOpenCLImageSupport()) { @@ -719,6 +720,10 @@ void RequirementHandler::initAvailableCapabilitiesForVulkan( Capability::UniformTexelBufferArrayNonUniformIndexingEXT, Capability::StorageTexelBufferArrayNonUniformIndexingEXT}); } + + // Became core in Vulkan 1.3 + if (ST.isAtLeastSPIRVVer(VersionTuple(1, 6))) + addAvailableCaps({Capability::StorageImageReadWithoutFormat}); } } // namespace SPIRV @@ -1005,6 +1010,13 @@ void addOpAccessChainReqs(const MachineInstr &Instr, } } +static bool isImageTypeWithUnknownFormat(SPIRVType *TypeInst) { + if (TypeInst->getOpcode() != SPIRV::OpTypeImage) + return false; + assert(TypeInst->getOperand(7).isImm() && "The image format must be an imm."); + return TypeInst->getOperand(7).getImm() == 0; +} + static void AddDotProductRequirements(const MachineInstr &MI, SPIRV::RequirementHandler &Reqs, const SPIRVSubtarget &ST) { @@ -1411,6 +1423,14 @@ void addInstrRequirements(const MachineInstr &MI, case SPIRV::OpUDot: AddDotProductRequirements(MI, Reqs, ST); break; + case SPIRV::OpImageRead: { + Register ImageReg = MI.getOperand(2).getReg(); + SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg); + if (isImageTypeWithUnknownFormat(TypeDef)) + Reqs.addCapability(SPIRV::Capability::StorageImageReadWithoutFormat); + break; + } + default: break; } diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll new file mode 100644 index 0000000000000..c2749d13c214d --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll @@ -0,0 +1,66 @@ +; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-vulkan-library %s -o - | FileCheck %s +; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-library %s -o - -filetype=obj | spirv-val %} + +; CHECK-NOT: OpCapability StorageImageReadWithoutFormat + +; CHECK-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16 +; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7 + +; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0 +; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0 +; CHECK-DAG: [[v4_int:%[0-9]+]] = OpTypeVector [[int]] 4 +; CHECK-DAG: [[v2_int:%[0-9]+]] = OpTypeVector [[int]] 2 +; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 R32i {{$}} +; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]] +; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant + +; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}} +; CHECK-NEXT: OpLabel +define void @RWBufferLoad_Vec4_I32() #0 { +; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]] + %buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) + @llvm.spv.handle.fromBinding.tspirv.Image_i32_5_2_0_0_2_24( + i32 16, i32 7, i32 1, i32 0, i1 false) + +; CHECK: OpImageRead [[v4_int]] [[buffer]] [[zero]] + %data0 = call <4 x i32> @llvm.spv.typedBufferLoad( + target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0) + + ret void +} + +; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}} +; CHECK-NEXT: OpLabel +define void @RWBufferLoad_I32() #0 { +; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]] + %buffer1 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) + @llvm.spv.handle.fromBinding.tspirv.Image_i32_5_2_0_0_2_24( + i32 16, i32 7, i32 1, i32 0, i1 false) + +; CHECK: [[V:%[0-9]+]] = OpImageRead [[v4_int]] [[buffer]] [[zero]] +; CHECK: OpCompositeExtract [[int]] [[V]] 0 + %data1 = call i32 @llvm.spv.typedBufferLoad( + target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer1, i32 0) + + ret void +} + +; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}} +; CHECK-NEXT: OpLabel +define void @RWBufferLoad_Vec2_I32() #0 { +; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]] + %buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) + @llvm.spv.handle.fromBinding.tspirv.Image_i32_5_2_0_0_2_24( + i32 16, i32 7, i32 1, i32 0, i1 false) + +; CHECK: [[V:%[0-9]+]] = OpImageRead [[v4_int]] [[buffer]] [[zero]] +; CHECK: [[e0:%[0-9]+]] = OpCompositeExtract [[int]] [[V]] 0 +; CHECK: [[e1:%[0-9]+]] = OpCompositeExtract [[int]] [[V]] 1 +; CHECK: OpCompositeConstruct [[v2_int]] [[e0]] [[e1]] + %data0 = call <2 x i32> @llvm.spv.typedBufferLoad( + target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0) + + ret void +} + +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" } diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HlslBufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/ScalarResourceType.ll similarity index 100% rename from llvm/test/CodeGen/SPIRV/hlsl-resources/HlslBufferLoad.ll rename to llvm/test/CodeGen/SPIRV/hlsl-resources/ScalarResourceType.ll diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll new file mode 100644 index 0000000000000..7f9c6f7da2859 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll @@ -0,0 +1,30 @@ +; 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 %} + +; CHECK: OpCapability StorageImageReadWithoutFormat +; CHECK-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16 +; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7 + +; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0 +; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0 +; CHECK-DAG: [[v4_int:%[0-9]+]] = OpTypeVector [[int]] 4 +; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 Unknown {{$}} +; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]] +; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant + +; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}} +; CHECK-NEXT: OpLabel +define void @RWBufferLoad_Vec4_I32() #0 { +; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]] + %buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) + @llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_0( + i32 16, i32 7, i32 1, i32 0, i1 false) + +; CHECK: OpImageRead [[v4_int]] [[buffer]] [[zero]] + %data0 = call <4 x i32> @llvm.spv.typedBufferLoad( + target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) %buffer0, i32 0) + + ret void +} + +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" } diff --git a/llvm/test/CodeGen/SPIRV/read_image.ll b/llvm/test/CodeGen/SPIRV/read_image.ll index ede5994279d2f..20063aa6b8b75 100644 --- a/llvm/test/CodeGen/SPIRV/read_image.ll +++ b/llvm/test/CodeGen/SPIRV/read_image.ll @@ -1,5 +1,7 @@ ; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV +; CHECK-SPIRV: OpCapability StorageImageReadWithoutFormat + ; CHECK-SPIRV: %[[#IntTy:]] = OpTypeInt ; CHECK-SPIRV: %[[#IVecTy:]] = OpTypeVector %[[#IntTy]] ; CHECK-SPIRV: %[[#FloatTy:]] = OpTypeFloat diff --git a/llvm/test/CodeGen/SPIRV/transcoding/OpImageReadMS.ll b/llvm/test/CodeGen/SPIRV/transcoding/OpImageReadMS.ll index f4e34c325e601..4de79a24e2136 100644 --- a/llvm/test/CodeGen/SPIRV/transcoding/OpImageReadMS.ll +++ b/llvm/test/CodeGen/SPIRV/transcoding/OpImageReadMS.ll @@ -1,5 +1,6 @@ ; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV +; CHECK-SPIRV: OpCapability StorageImageReadWithoutFormat ; CHECK-SPIRV: %[[#]] = OpImageRead %[[#]] %[[#]] %[[#]] Sample %[[#]] define spir_kernel void @sample_test(target("spirv.Image", void, 1, 0, 0, 1, 0, 0, 0) %source, i32 %sampler, <4 x float> addrspace(1)* nocapture %results) {