[llvm] [HLSL][SPIR-V] Add resource load level intrinsics and SPIR-V backend support (PR #185707)

via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 10 10:53:57 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Steven Perron (s-perron)

<details>
<summary>Changes</summary>

<!-- branch-stack-start -->

-------------------------
- main
  - users/s-perron/texture-2d-load-level-spirv :point_left:

<sup>[Stack](https://www.git-town.com/how-to/proposal-breadcrumb.html) generated by [Git Town](https://github.com/git-town/git-town)</sup>

<!-- branch-stack-end -->


---
Full diff: https://github.com/llvm/llvm-project/pull/185707.diff


5 Files Affected:

- (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+6) 
- (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+6) 
- (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+66-6) 
- (modified) llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp (+4) 
- (added) llvm/test/CodeGen/SPIRV/hlsl-resources/LoadLevel.ll (+96) 


``````````diff
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index cb92fe9597b2f..ae0ade19211ad 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 75bf44801e63d..96997aa8c8609 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -229,6 +229,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 65f4856aeee68..64e9f58f24bb5 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -363,6 +363,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,
@@ -421,7 +423,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,
@@ -4287,6 +4290,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);
@@ -4662,6 +4668,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();
@@ -4766,7 +4798,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.");
@@ -4778,6 +4811,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 =
@@ -4788,8 +4850,7 @@ bool SPIRVInstructionSelector::generateImageReadOrFetch(
             .addUse(ImageReg)
             .addUse(IdxReg);
 
-    if (IsSignedInteger)
-      BMI.addImm(0x1000); // SignExtend
+    AddOperands(BMI);
     BMI.constrainAllUses(TII, TRI, RBI);
     return true;
   }
@@ -4803,8 +4864,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 923f59917cb10..7bed9c58f7f2d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -2163,6 +2163,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..82e94489b9a06
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/LoadLevel.ll
@@ -0,0 +1,96 @@
+; RUN: llc -O0 -mtriple=spirv-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; 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() {
+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() {
+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() {
+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() {
+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 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() {
+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() {
+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>)

``````````

</details>


https://github.com/llvm/llvm-project/pull/185707


More information about the llvm-commits mailing list