[llvm] bf67e4d - [HLSL][SPIR-V] Add resource load level intrinsics and SPIR-V backend support (#185707)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Mar 11 09:59:12 PDT 2026
Author: Steven Perron
Date: 2026-03-11T16:59:07Z
New Revision: bf67e4d4ad644764b52302675b205426483b3910
URL: https://github.com/llvm/llvm-project/commit/bf67e4d4ad644764b52302675b205426483b3910
DIFF: https://github.com/llvm/llvm-project/commit/bf67e4d4ad644764b52302675b205426483b3910.diff
LOG: [HLSL][SPIR-V] Add resource load level intrinsics and SPIR-V backend support (#185707)
Adds the intrinsics resource_load_level intrinic for DXIL and SPIR-V. It
will be used to load a value from an specific location in the image at
the given mip level. It will be used to implement the Texture Load and
mips[][] methods.
Assisted-by: Gemini
Added:
llvm/test/CodeGen/SPIRV/hlsl-resources/LoadLevel.ll
Modified:
llvm/include/llvm/IR/IntrinsicsDirectX.td
llvm/include/llvm/IR/IntrinsicsSPIRV.td
llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 29d0f4d7e46cf..52a305888f699 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -120,6 +120,12 @@ def int_dx_resource_samplelevel
llvm_float_ty, llvm_any_ty],
[IntrReadMem]>;
+def int_dx_resource_load_level
+ : DefaultAttrsIntrinsic<[llvm_any_ty],
+ [llvm_any_ty, llvm_any_ty, llvm_any_ty,
+ llvm_any_ty],
+ [IntrReadMem]>;
+
def int_dx_resource_samplecmp
: DefaultAttrsIntrinsic<[llvm_any_ty],
[llvm_any_ty, llvm_any_ty, llvm_any_ty,
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index ead6d18124381..5bc93ec87ef66 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -238,6 +238,12 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
llvm_float_ty, llvm_any_ty],
[IntrReadMem]>;
+ def int_spv_resource_load_level
+ : DefaultAttrsIntrinsic<[llvm_any_ty],
+ [llvm_any_ty, llvm_any_ty, llvm_any_ty,
+ llvm_any_ty],
+ [IntrReadMem]>;
+
def int_spv_resource_samplecmp
: DefaultAttrsIntrinsic<[llvm_any_ty],
[llvm_any_ty, llvm_any_ty, llvm_any_ty,
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index fcae23432017d..9d4d2236929c6 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -376,6 +376,8 @@ class SPIRVInstructionSelector : public InstructionSelector {
MachineInstr &I) const;
bool selectSampleLevelIntrinsic(Register &ResVReg, SPIRVTypeInst ResType,
MachineInstr &I) const;
+ bool selectLoadLevelIntrinsic(Register &ResVReg, SPIRVTypeInst ResType,
+ MachineInstr &I) const;
bool selectSampleCmpIntrinsic(Register &ResVReg, SPIRVTypeInst ResType,
MachineInstr &I) const;
bool selectSampleCmpLevelZeroIntrinsic(Register &ResVReg,
@@ -434,7 +436,8 @@ class SPIRVInstructionSelector : public InstructionSelector {
Register &ReadReg, MachineInstr &InsertionPoint) const;
bool generateImageReadOrFetch(Register &ResVReg, SPIRVTypeInst ResType,
Register ImageReg, Register IdxReg,
- DebugLoc Loc, MachineInstr &Pos) const;
+ DebugLoc Loc, MachineInstr &Pos,
+ const ImageOperands *ImOps = nullptr) const;
bool generateSampleImage(Register ResVReg, SPIRVTypeInst ResType,
Register ImageReg, Register SamplerReg,
Register CoordinateReg, const ImageOperands &ImOps,
@@ -4374,6 +4377,9 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
case Intrinsic::spv_resource_load_typedbuffer: {
return selectReadImageIntrinsic(ResVReg, ResType, I);
}
+ case Intrinsic::spv_resource_load_level: {
+ return selectLoadLevelIntrinsic(ResVReg, ResType, I);
+ }
case Intrinsic::spv_resource_sample:
case Intrinsic::spv_resource_sample_clamp:
return selectSampleBasicIntrinsic(ResVReg, ResType, I);
@@ -4759,6 +4765,32 @@ bool SPIRVInstructionSelector::selectSampleCmpIntrinsic(Register &ResVReg,
CoordinateReg, ImOps, I.getDebugLoc(), I);
}
+bool SPIRVInstructionSelector::selectLoadLevelIntrinsic(Register &ResVReg,
+ SPIRVTypeInst ResType,
+ MachineInstr &I) const {
+ Register ImageReg = I.getOperand(2).getReg();
+ Register CoordinateReg = I.getOperand(3).getReg();
+ Register LodReg = I.getOperand(4).getReg();
+
+ ImageOperands ImOps;
+ ImOps.Lod = LodReg;
+ if (I.getNumOperands() > 5)
+ ImOps.Offset = I.getOperand(5).getReg();
+
+ auto *ImageDef = dyn_cast<GIntrinsic>(getVRegDef(*MRI, ImageReg));
+ if (!ImageDef)
+ return false;
+
+ Register NewImageReg = MRI->createVirtualRegister(MRI->getRegClass(ImageReg));
+ if (!loadHandleBeforePosition(NewImageReg, GR.getSPIRVTypeForVReg(ImageReg),
+ *ImageDef, I)) {
+ return false;
+ }
+
+ return generateImageReadOrFetch(ResVReg, ResType, NewImageReg, CoordinateReg,
+ I.getDebugLoc(), I, &ImOps);
+}
+
bool SPIRVInstructionSelector::selectSampleCmpLevelZeroIntrinsic(
Register &ResVReg, SPIRVTypeInst ResType, MachineInstr &I) const {
Register ImageReg = I.getOperand(2).getReg();
@@ -4863,7 +4895,8 @@ bool SPIRVInstructionSelector::selectGatherIntrinsic(Register &ResVReg,
bool SPIRVInstructionSelector::generateImageReadOrFetch(
Register &ResVReg, SPIRVTypeInst ResType, Register ImageReg,
- Register IdxReg, DebugLoc Loc, MachineInstr &Pos) const {
+ Register IdxReg, DebugLoc Loc, MachineInstr &Pos,
+ const ImageOperands *ImOps) const {
SPIRVTypeInst ImageType = GR.getSPIRVTypeForVReg(ImageReg);
assert(ImageType && ImageType->getOpcode() == SPIRV::OpTypeImage &&
"ImageReg is not an image type.");
@@ -4875,6 +4908,35 @@ bool SPIRVInstructionSelector::generateImageReadOrFetch(
auto SampledOp = ImageType->getOperand(6);
bool IsFetch = (SampledOp.getImm() == 1);
+ auto AddOperands = [&](MachineInstrBuilder &MIB) {
+ uint32_t ImageOperandsMask = 0;
+ if (IsSignedInteger)
+ ImageOperandsMask |= 0x1000; // SignExtend
+
+ if (IsFetch && ImOps) {
+ if (ImOps->Lod)
+ ImageOperandsMask |= SPIRV::ImageOperand::Lod;
+ if (ImOps->Offset && !isScalarOrVectorIntConstantZero(*ImOps->Offset)) {
+ if (isConstReg(MRI, *ImOps->Offset))
+ ImageOperandsMask |= SPIRV::ImageOperand::ConstOffset;
+ else
+ ImageOperandsMask |= SPIRV::ImageOperand::Offset;
+ }
+ }
+
+ if (ImageOperandsMask != 0) {
+ MIB.addImm(ImageOperandsMask);
+ if (IsFetch && ImOps) {
+ if (ImOps->Lod)
+ MIB.addUse(*ImOps->Lod);
+ if (ImOps->Offset &&
+ (ImageOperandsMask &
+ (SPIRV::ImageOperand::Offset | SPIRV::ImageOperand::ConstOffset)))
+ MIB.addUse(*ImOps->Offset);
+ }
+ }
+ };
+
uint64_t ResultSize = GR.getScalarOrVectorComponentCount(ResType);
if (ResultSize == 4) {
auto BMI =
@@ -4885,8 +4947,7 @@ bool SPIRVInstructionSelector::generateImageReadOrFetch(
.addUse(ImageReg)
.addUse(IdxReg);
- if (IsSignedInteger)
- BMI.addImm(0x1000); // SignExtend
+ AddOperands(BMI);
BMI.constrainAllUses(TII, TRI, RBI);
return true;
}
@@ -4900,8 +4961,7 @@ bool SPIRVInstructionSelector::generateImageReadOrFetch(
.addUse(GR.getSPIRVTypeID(ReadType))
.addUse(ImageReg)
.addUse(IdxReg);
- if (IsSignedInteger)
- BMI.addImm(0x1000); // SignExtend
+ AddOperands(BMI);
BMI.constrainAllUses(TII, TRI, RBI);
if (ResultSize == 1) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index b93a759ff4983..259eaa8933d4f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -2172,6 +2172,10 @@ void addInstrRequirements(const MachineInstr &MI,
Reqs.addCapability(SPIRV::Capability::Shader);
addImageOperandReqs(MI, Reqs, ST, 5);
break;
+ case SPIRV::OpImageFetch:
+ Reqs.addCapability(SPIRV::Capability::Shader);
+ addImageOperandReqs(MI, Reqs, ST, 4);
+ break;
case SPIRV::OpImageDrefGather:
case SPIRV::OpImageGather:
Reqs.addCapability(SPIRV::Capability::Shader);
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/LoadLevel.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/LoadLevel.ll
new file mode 100644
index 0000000000000..65035ce74ecdf
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/LoadLevel.ll
@@ -0,0 +1,98 @@
+; RUN: llc -O0 -mtriple=spirv1.6-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-compute %s -o - -filetype=obj | spirv-val --allow-offset-texture-operand --target-env vulkan1.3 %}
+
+; CHECK-DAG: OpCapability Shader
+; CHECK-DAG: OpCapability ImageGatherExtended
+
+; CHECK-DAG: %[[float:[0-9]+]] = OpTypeFloat 32
+; CHECK-DAG: %[[v4float:[0-9]+]] = OpTypeVector %[[float]] 4
+; CHECK-DAG: %[[uint:[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: %[[v2uint:[0-9]+]] = OpTypeVector %[[uint]] 2
+; CHECK-DAG: %[[v3uint:[0-9]+]] = OpTypeVector %[[uint]] 3
+; CHECK-DAG: %[[image_1d:[0-9]+]] = OpTypeImage %[[float]] 1D 2 0 0 1 Unknown
+; CHECK-DAG: %[[image_2d:[0-9]+]] = OpTypeImage %[[float]] 2D 2 0 0 1 Unknown
+; CHECK-DAG: %[[image_3d:[0-9]+]] = OpTypeImage %[[float]] 3D 2 0 0 1 Unknown
+
+; CHECK-DAG: %[[lod0:[0-9]+]] = OpConstant %[[uint]] 0
+; CHECK-DAG: %[[uint_1:[0-9]+]] = OpConstant %[[uint]] 1
+; CHECK-DAG: %[[coord_2d:[0-9]+]] = OpConstantNull %[[v2uint]]
+; CHECK-DAG: %[[coord_3d:[0-9]+]] = OpConstantNull %[[v3uint]]
+; CHECK-DAG: %[[offset_2d:[0-9]+]] = OpConstantComposite %[[v2uint]] %[[uint_1]] %[[uint_1]]
+; CHECK-DAG: %[[offset_3d:[0-9]+]] = OpConstantComposite %[[v3uint]] %[[uint_1]] %[[uint_1]] %[[uint_1]]
+
+ at .str = private unnamed_addr constant [6 x i8] c"img1d\00", align 1
+ at .str.1 = private unnamed_addr constant [6 x i8] c"img2d\00", align 1
+ at .str.2 = private unnamed_addr constant [6 x i8] c"img3d\00", align 1
+
+define void @test_load_1d() #0 {
+entry:
+ %img = tail call target("spirv.Image", float, 0, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_0_2_0_0_1_0t(i32 0, i32 0, i32 1, i32 0, ptr @.str)
+; CHECK: %[[img_val_1d:[0-9]+]] = OpLoad %[[image_1d]]
+; CHECK: %[[res_1d:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_1d]] %[[uint_1]] Lod %[[lod0]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_0_2_0_0_1_0t.i32.i32.i32(target("spirv.Image", float, 0, 2, 0, 0, 1, 0) %img, i32 1, i32 0, i32 0)
+ ret void
+}
+
+define void @test_load_1d_offset() #0 {
+entry:
+ %img = tail call target("spirv.Image", float, 0, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_0_2_0_0_1_0t(i32 0, i32 0, i32 1, i32 0, ptr @.str)
+; CHECK: %[[img_val_1d_off:[0-9]+]] = OpLoad %[[image_1d]]
+; CHECK: %[[res_1d_off:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_1d_off]] %[[uint_1]] Lod|ConstOffset %[[lod0]] %[[uint_1]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_0_2_0_0_1_0t.i32.i32.i32(target("spirv.Image", float, 0, 2, 0, 0, 1, 0) %img, i32 1, i32 0, i32 1)
+ ret void
+}
+
+define void @test_load_2d() #0 {
+entry:
+ %img = tail call target("spirv.Image", float, 1, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_1_2_0_0_1_0t(i32 0, i32 1, i32 1, i32 0, ptr @.str.1)
+; CHECK: %[[img_val_2d:[0-9]+]] = OpLoad %[[image_2d]]
+; CHECK: %[[res_2d:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_2d]] %[[coord_2d]] Lod %[[lod0]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.v2i32.i32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0) %img, <2 x i32> zeroinitializer, i32 0, <2 x i32> zeroinitializer)
+ ret void
+}
+
+define void @test_load_2d_offset() #0 {
+entry:
+ %img = tail call target("spirv.Image", float, 1, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_1_2_0_0_1_0t(i32 0, i32 1, i32 1, i32 0, ptr @.str.1)
+; CHECK: %[[img_val_2d_off:[0-9]+]] = OpLoad %[[image_2d]]
+; CHECK: %[[res_2d_off:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_2d_off]] %[[coord_2d]] Lod|ConstOffset %[[lod0]] %[[offset_2d]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.v2i32.i32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0) %img, <2 x i32> zeroinitializer, i32 0, <2 x i32> <i32 1, i32 1>)
+ ret void
+}
+
+define internal void @test_load_2d_non_const_offset(<2 x i32> %offset) {
+entry:
+; CHECK: %[[offset:[0-9]+]] = OpFunctionParameter %[[v2uint]]
+ %img = tail call target("spirv.Image", float, 1, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_1_2_0_0_1_0t(i32 0, i32 1, i32 1, i32 0, ptr @.str.1)
+; CHECK: %[[img_val_2d_non_off:[0-9]+]] = OpLoad %[[image_2d]]
+; CHECK: %[[res_2d_non_off:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_2d_non_off]] %[[coord_2d]] Lod|Offset %[[lod0]] %[[offset]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.v2i32.i32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0) %img, <2 x i32> zeroinitializer, i32 0, <2 x i32> %offset)
+ ret void
+}
+
+define void @test_load_3d() #0 {
+entry:
+ %img = tail call target("spirv.Image", float, 2, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_2_2_0_0_1_0t(i32 0, i32 2, i32 1, i32 0, ptr @.str.2)
+; CHECK: %[[img_val_3d:[0-9]+]] = OpLoad %[[image_3d]]
+; CHECK: %[[res_3d:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_3d]] %[[coord_3d]] Lod %[[lod0]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_2_2_0_0_1_0t.v3i32.i32.v3i32(target("spirv.Image", float, 2, 2, 0, 0, 1, 0) %img, <3 x i32> zeroinitializer, i32 0, <3 x i32> zeroinitializer)
+ ret void
+}
+
+define void @test_load_3d_offset() #0 {
+entry:
+ %img = tail call target("spirv.Image", float, 2, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_2_2_0_0_1_0t(i32 0, i32 2, i32 1, i32 0, ptr @.str.2)
+; CHECK: %[[img_val_3d_off:[0-9]+]] = OpLoad %[[image_3d]]
+; CHECK: %[[res_3d_off:[0-9]+]] = OpImageFetch %[[v4float]] %[[img_val_3d_off]] %[[coord_3d]] Lod|ConstOffset %[[lod0]] %[[offset_3d]]
+ %res = call <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_2_2_0_0_1_0t.v3i32.i32.v3i32(target("spirv.Image", float, 2, 2, 0, 0, 1, 0) %img, <3 x i32> zeroinitializer, i32 0, <3 x i32> <i32 1, i32 1, i32 1>)
+ ret void
+}
+
+declare target("spirv.Image", float, 0, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_0_2_0_0_1_0t(i32, i32, i32, i32, ptr)
+declare <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_0_2_0_0_1_0t.i32.i32.i32(target("spirv.Image", float, 0, 2, 0, 0, 1, 0), i32, i32, i32)
+declare target("spirv.Image", float, 1, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_1_2_0_0_1_0t(i32, i32, i32, i32, ptr)
+declare <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.v2i32.i32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), <2 x i32>, i32, <2 x i32>)
+declare target("spirv.Image", float, 2, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_2_2_0_0_1_0t(i32, i32, i32, i32, ptr)
+declare <4 x float> @llvm.spv.resource.load.level.v4f32.tspirv.Image_f32_2_2_0_0_1_0t.v3i32.i32.v3i32(target("spirv.Image", float, 2, 2, 0, 0, 1, 0), <3 x i32>, i32, <3 x i32>)
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
More information about the llvm-commits
mailing list