[spirv-reader][ir] Add image query levels support.

Add support for `OpImageQueryLevels`.

Bug: 407375749
Change-Id: Ic24b5de0819418de8db81b945d7801cf36477586
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/242535
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/reader/lower/builtins.cc b/src/tint/lang/spirv/reader/lower/builtins.cc
index c12f1ea..ad9d2fd 100644
--- a/src/tint/lang/spirv/reader/lower/builtins.cc
+++ b/src/tint/lang/spirv/reader/lower/builtins.cc
@@ -237,6 +237,7 @@
                     break;
                 case spirv::BuiltinFn::kSampledImage:
                 case spirv::BuiltinFn::kImageGather:
+                case spirv::BuiltinFn::kImageQueryLevels:
                 case spirv::BuiltinFn::kImageQuerySize:
                 case spirv::BuiltinFn::kImageQuerySizeLod:
                 case spirv::BuiltinFn::kImageSampleImplicitLod:
diff --git a/src/tint/lang/spirv/reader/lower/texture.cc b/src/tint/lang/spirv/reader/lower/texture.cc
index ddb9c2b..f1660a7 100644
--- a/src/tint/lang/spirv/reader/lower/texture.cc
+++ b/src/tint/lang/spirv/reader/lower/texture.cc
@@ -104,6 +104,7 @@
                 switch (builtin->Func()) {
                     case spirv::BuiltinFn::kSampledImage:
                     case spirv::BuiltinFn::kImageGather:
+                    case spirv::BuiltinFn::kImageQueryLevels:
                     case spirv::BuiltinFn::kImageQuerySize:
                     case spirv::BuiltinFn::kImageQuerySizeLod:
                     case spirv::BuiltinFn::kImageSampleImplicitLod:
@@ -124,6 +125,9 @@
                 case spirv::BuiltinFn::kImageGather:
                     ImageGather(builtin);
                     break;
+                case spirv::BuiltinFn::kImageQueryLevels:
+                    ImageQueryLevels(builtin);
+                    break;
                 case spirv::BuiltinFn::kImageQuerySize:
                 case spirv::BuiltinFn::kImageQuerySizeLod:
                     ImageQuerySize(builtin);
@@ -292,6 +296,25 @@
         call->Destroy();
     }
 
+    void ImageQueryLevels(spirv::ir::BuiltinCall* call) {
+        auto* image = call->Args()[0];
+
+        b.InsertBefore(call, [&] {
+            auto* type = call->Result()->Type();
+
+            // WGSL requires a `u32` result component where SPIR-V allows `i32` or `u32`
+            core::ir::Value* res = b.Call(ty.MatchWidth(ty.u32(), type),
+                                          core::BuiltinFn::kTextureNumLevels, Vector{image})
+                                       ->Result();
+            if (type->IsSignedIntegerScalarOrVector()) {
+                res = b.Convert(type, res)->Result();
+            }
+
+            call->Result()->ReplaceAllUsesWith(res);
+        });
+        call->Destroy();
+    }
+
     void ImageQuerySize(spirv::ir::BuiltinCall* call) {
         auto* image = call->Args()[0];
 
@@ -301,8 +324,7 @@
         b.InsertBefore(call, [&] {
             auto* type = call->Result()->Type();
 
-            // WGSL requires a `u32` result component where SPIR-V allows
-            // `i32` or `u32`
+            // WGSL requires a `u32` result component where SPIR-V allows `i32` or `u32`
             auto* wgsl_type = ty.MatchWidth(ty.u32(), type);
 
             // A SPIR-V OpImageQuery will return the array `element` entry with
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index e843c4a..47d7beb 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -1396,8 +1396,11 @@
                 case spv::Op::OpImageGather:
                     EmitImageGather(inst);
                     break;
+                case spv::Op::OpImageQueryLevels:
+                    EmitImageQuery(inst, spirv::BuiltinFn::kImageQueryLevels);
+                    break;
                 case spv::Op::OpImageQuerySize:
-                    EmitImageQuerySize(inst);
+                    EmitImageQuery(inst, spirv::BuiltinFn::kImageQuerySize);
                     break;
                 case spv::Op::OpImageQuerySizeLod:
                     EmitImageQuerySizeLod(inst);
@@ -1534,12 +1537,11 @@
              inst.result_id());
     }
 
-    void EmitImageQuerySize(const spvtools::opt::Instruction& inst) {
+    void EmitImageQuery(const spvtools::opt::Instruction& inst, spirv::BuiltinFn fn) {
         auto* image = Value(inst.GetSingleWordInOperand(0));
 
         auto* ty = Type(inst.type_id());
-        Emit(b_.CallExplicit<spirv::ir::BuiltinCall>(ty, spirv::BuiltinFn::kImageQuerySize,
-                                                     Vector{ty->DeepestElement()}, image),
+        Emit(b_.CallExplicit<spirv::ir::BuiltinCall>(ty, fn, Vector{ty->DeepestElement()}, image),
              inst.result_id());
     }
 
diff --git a/src/tint/lang/spirv/reader/texture_test.cc b/src/tint/lang/spirv/reader/texture_test.cc
index 362aa36..7f48799 100644
--- a/src/tint/lang/spirv/reader/texture_test.cc
+++ b/src/tint/lang/spirv/reader/texture_test.cc
@@ -407,6 +407,51 @@
 )");
 }
 
+TEST_F(SpirvReaderTest, ImageQueryLevels) {
+    EXPECT_IR(R"(
+           OpCapability Shader
+           OpCapability Sampled1D
+           OpCapability ImageQuery
+           OpMemoryModel Logical Simple
+           OpEntryPoint Fragment %main "main"
+           OpExecutionMode %main OriginUpperLeft
+           OpName %wg "wg"
+           OpDecorate %wg DescriptorSet 2
+           OpDecorate %wg Binding 0
+    %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+    %tex = OpTypeImage %float 1D 0 0 0 1 R32f
+%ptr_tex = OpTypePointer UniformConstant %tex
+   %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+
+     %wg = OpVariable %ptr_tex UniformConstant
+
+   %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+     %im = OpLoad %tex %wg
+ %result = OpImageQueryLevels %int %im
+     %r2 = OpIAdd %int %result %result
+           OpReturn
+           OpFunctionEnd
+        )",
+              R"(
+$B1: {  # root
+  %wg:ptr<handle, texture_1d<f32>, read> = var undef @binding_point(2, 0)
+}
+
+%main = @fragment func():void {
+  $B2: {
+    %3:texture_1d<f32> = load %wg
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4
+    %6:i32 = add %5, %5
+    ret
+  }
+}
+)");
+}
+
 TEST_F(SpirvReaderTest, ImageQuerySizeLod) {
     EXPECT_IR(R"(
            OpCapability Shader
@@ -2110,88 +2155,107 @@
                          ::testing::Values(ImgData{
                              .name = "1D",
                              .spirv_type = "%float 1D 0 0 0 1 Unknown",
-                             .spirv_fn = "%99 = OpImageQuerySizeLod %uint %im %int_1\n",
+                             .spirv_fn = "%99 = OpImageQuerySizeLod %uint %im %int_1",
                              .wgsl_type = "texture_1d<f32>",
                              .wgsl_fn = R"(
     %4:u32 = textureDimensions %3, 1i)",
                          }));
 
-INSTANTIATE_TEST_SUITE_P(DISABLED_SpirvReaderTest_ImageQueryLevels_SignedResult,
+INSTANTIATE_TEST_SUITE_P(SpirvReaderTest_ImageQueryLevels_SignedResult,
                          SampledImageAccessTest,
                          ::testing::Values(
                              ImgData{
                                  .name = "2D",
                                  .spirv_type = "%float 2D 0 0 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_2d<f32>",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "2D array",
                                  .spirv_type = "%float 2D 0 1 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_2d_array<f32>",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "3D",
                                  .spirv_type = "%float 3D 0 0 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_3d<f32>",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "Cube",
                                  .spirv_type = "%float Cube 0 0 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_cube<f32>",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "Cube array",
                                  .spirv_type = "%float Cube 0 1 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_cube_array<f32>",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "depth 2d",
                                  .spirv_type = "%float 2D 1 0 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_depth_2d",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "depth 2d array",
                                  .spirv_type = "%float 2D 1 1 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_depth_2d_array",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "depth cube",
                                  .spirv_type = "%float Cube 1 0 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_depth_cube",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              },
                              ImgData{
                                  .name = "depth cube array",
                                  .spirv_type = "%float Cube 1 1 0 1 Unknown",
-                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im\n",
+                                 .spirv_fn = "%99 = OpImageQueryLevels %int %im",
                                  .wgsl_type = "texture_depth_cube_array",
-                                 .wgsl_fn = "let x_99 = i32(textureNumLevels(x_20))",
+                                 .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3
+    %5:i32 = convert %4)",
                              }));
 
 // Spot check that a value conversion is inserted when SPIR-V asks for an unsigned int result.
-INSTANTIATE_TEST_SUITE_P(DISABLED_SpirvReaderTest_ImageQueryLevels_UnsignedResult,
+INSTANTIATE_TEST_SUITE_P(SpirvReaderTest_ImageQueryLevels_UnsignedResult,
                          SampledImageAccessTest,
                          ::testing::Values(ImgData{
                              .name = "2D",
                              .spirv_type = "%float 2D 0 0 0 1 Unknown",
                              .spirv_fn = "%99 = OpImageQueryLevels %uint %im\n",
                              .wgsl_type = "texture_2d<f32>",
-                             .wgsl_fn = "let x_99 = textureNumLevels(x_20)",
+                             .wgsl_fn = R"(
+    %4:u32 = textureNumLevels %3)",
                          }));
 
 using MultiSampledImageAccessTest = SpirvReaderTestWithParam<ImgData>;