[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