[Spirv] Clamp frag depth using push constant values

Add a way to use pre-created push constant layout generated by
`PreparePushConstants` to find the min and max values used for
frag depth clamping.

Bug: 366291600

Change-Id: I8393c912e48804614fd9fce8c1c1e9228f5b4594
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/221014
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Shaobo Yan <shaoboyan@microsoft.com>
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.bazel b/src/tint/lang/spirv/writer/raise/BUILD.bazel
index efd4f60..6b0ee8c 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/raise/BUILD.bazel
@@ -118,6 +118,7 @@
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
     "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/ir/transform",
     "//src/tint/lang/core/ir/transform:test",
     "//src/tint/lang/core/type",
     "//src/tint/utils",
@@ -135,6 +136,7 @@
     "//src/utils",
   ] + select({
     ":tint_build_spv_writer": [
+      "//src/tint/lang/spirv/writer/common",
       "//src/tint/lang/spirv/writer/raise",
     ],
     "//conditions:default": [],
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.cmake b/src/tint/lang/spirv/writer/raise/BUILD.cmake
index 57e528c..51a7d8a 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/raise/BUILD.cmake
@@ -127,6 +127,7 @@
   tint_lang_core_constant
   tint_lang_core_intrinsic
   tint_lang_core_ir
+  tint_lang_core_ir_transform
   tint_lang_core_ir_transform_test
   tint_lang_core_type
   tint_utils
@@ -149,6 +150,7 @@
 
 if(TINT_BUILD_SPV_WRITER)
   tint_target_add_dependencies(tint_lang_spirv_writer_raise_test test
+    tint_lang_spirv_writer_common
     tint_lang_spirv_writer_raise
   )
 endif(TINT_BUILD_SPV_WRITER)
diff --git a/src/tint/lang/spirv/writer/raise/BUILD.gn b/src/tint/lang/spirv/writer/raise/BUILD.gn
index ae4dde5..e042ee7 100644
--- a/src/tint/lang/spirv/writer/raise/BUILD.gn
+++ b/src/tint/lang/spirv/writer/raise/BUILD.gn
@@ -121,6 +121,7 @@
         "${tint_src_dir}/lang/core/constant",
         "${tint_src_dir}/lang/core/intrinsic",
         "${tint_src_dir}/lang/core/ir",
+        "${tint_src_dir}/lang/core/ir/transform",
         "${tint_src_dir}/lang/core/ir/transform:unittests",
         "${tint_src_dir}/lang/core/type",
         "${tint_src_dir}/utils",
@@ -137,7 +138,10 @@
       ]
 
       if (tint_build_spv_writer) {
-        deps += [ "${tint_src_dir}/lang/spirv/writer/raise" ]
+        deps += [
+          "${tint_src_dir}/lang/spirv/writer/common",
+          "${tint_src_dir}/lang/spirv/writer/raise",
+        ]
       }
     }
   }
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 4e28323..5cd98e5 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -170,9 +170,11 @@
     RUN_TRANSFORM(raise::HandleMatrixArithmetic, module);
     RUN_TRANSFORM(raise::MergeReturn, module);
     RUN_TRANSFORM(raise::RemoveUnreachableInLoopContinuing, module);
-    RUN_TRANSFORM(raise::ShaderIO, module,
-                  raise::ShaderIOConfig{options.clamp_frag_depth, options.emit_vertex_point_size,
-                                        !options.use_storage_input_output_16});
+    RUN_TRANSFORM(
+        raise::ShaderIO, module,
+        raise::ShaderIOConfig{push_constant_layout.Get(), options.clamp_frag_depth,
+                              options.emit_vertex_point_size, !options.use_storage_input_output_16,
+                              options.depth_range_offsets});
     RUN_TRANSFORM(core::ir::transform::Std140, module);
     RUN_TRANSFORM(raise::VarForDynamicIndex, module);
 
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 1477478..b3feb0e 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -190,10 +190,24 @@
     /// @param frag_depth the incoming frag_depth value
     /// @returns the clamped value
     core::ir::Value* ClampFragDepth(core::ir::Builder& builder, core::ir::Value* frag_depth) {
-        if (!config.clamp_frag_depth) {
+        if (!config.clamp_frag_depth && !config.depth_range_offsets) {
             return frag_depth;
         }
 
+        // Use pre-created push constant block for clamping frag depth if possible.
+        if (config.depth_range_offsets) {
+            auto* push_constants = config.push_constant_layout.var;
+            auto min_idx =
+                u32(config.push_constant_layout.IndexOf(config.depth_range_offsets->min));
+            auto max_idx =
+                u32(config.push_constant_layout.IndexOf(config.depth_range_offsets->max));
+            auto* min =
+                builder.Load(builder.Access<ptr<push_constant, f32>>(push_constants, min_idx));
+            auto* max =
+                builder.Load(builder.Access<ptr<push_constant, f32>>(push_constants, max_idx));
+            return builder.Call<f32>(core::BuiltinFn::kClamp, frag_depth, min, max)->Result(0);
+        }
+
         // Create the clamp args struct and variable.
         if (!module_state.frag_depth_clamp_args) {
             // Check that there are no push constants in the module already.
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.h b/src/tint/lang/spirv/writer/raise/shader_io.h
index 38cd9ad..5d76988 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.h
+++ b/src/tint/lang/spirv/writer/raise/shader_io.h
@@ -30,6 +30,8 @@
 
 #include <string>
 
+#include "src/tint/lang/core/ir/transform/prepare_push_constants.h"
+#include "src/tint/lang/spirv/writer/common/options.h"
 #include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/result/result.h"
 
@@ -42,12 +44,17 @@
 
 /// ShaderIOConfig describes the set of configuration options for the ShaderIO transform.
 struct ShaderIOConfig {
+    /// push constant layout information
+    const core::ir::transform::PushConstantLayout& push_constant_layout;
+
     /// true if frag_depth builtin outputs should be clamped
     bool clamp_frag_depth = false;
     /// true if a vertex point size builtin output should be added
     bool emit_vertex_point_size = false;
     /// true if f16 IO types should be replaced with f32 types and converted
     bool polyfill_f16_io = false;
+    /// offsets for clamping frag depth
+    std::optional<Options::RangeOffsets> depth_range_offsets{};
 };
 
 /// ShaderIO is a transform that moves each entry point function's parameters and return value to
diff --git a/src/tint/lang/spirv/writer/raise/shader_io_test.cc b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
index 417b406..888ce74 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
@@ -57,7 +57,8 @@
 
     auto* expect = src;
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -138,7 +139,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -290,7 +292,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -412,7 +415,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -459,7 +463,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -505,7 +510,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -615,7 +621,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -702,7 +709,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -843,7 +851,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -940,7 +949,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -1036,7 +1046,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -1187,7 +1198,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = false;
     Run(ShaderIO, config);
 
@@ -1284,7 +1296,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = true;
     Run(ShaderIO, config);
 
@@ -1441,13 +1454,285 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.clamp_frag_depth = true;
     Run(ShaderIO, config);
 
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(SpirvWriter_ShaderIOTest, ClampFragDepth_With_Prepared_Push_Constants) {
+    auto* str_ty = ty.Struct(mod.symbols.New("Outputs"),
+                             {
+                                 {
+                                     mod.symbols.New("color"),
+                                     ty.f32(),
+                                     core::IOAttributes{
+                                         /* location */ 0u,
+                                         /* blend_src */ std::nullopt,
+                                         /* color */ std::nullopt,
+                                         /* builtin */ std::nullopt,
+                                         /* interpolation */ std::nullopt,
+                                         /* invariant */ false,
+                                     },
+                                 },
+                                 {
+                                     mod.symbols.New("depth"),
+                                     ty.f32(),
+                                     core::IOAttributes{
+                                         /* location */ std::nullopt,
+                                         /* blend_src */ std::nullopt,
+                                         /* color */ std::nullopt,
+                                         /* builtin */ core::BuiltinValue::kFragDepth,
+                                         /* interpolation */ std::nullopt,
+                                         /* invariant */ false,
+                                     },
+                                 },
+                             });
+
+    auto* ep = b.Function("foo", str_ty);
+    ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Construct(str_ty, 0.5_f, 2_f));
+    });
+
+    auto* src = R"(
+Outputs = struct @align(4) {
+  color:f32 @offset(0), @location(0)
+  depth:f32 @offset(4), @builtin(frag_depth)
+}
+
+%foo = @fragment func():Outputs {
+  $B1: {
+    %2:Outputs = construct 0.5f, 2.0f
+    ret %2
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+Outputs = struct @align(4) {
+  color:f32 @offset(0)
+  depth:f32 @offset(4)
+}
+
+tint_push_constant_struct = struct @align(4), @block {
+  depth_min:f32 @offset(4)
+  depth_max:f32 @offset(8)
+}
+
+$B1: {  # root
+  %tint_push_constants:ptr<push_constant, tint_push_constant_struct, read> = var
+  %foo_loc0_Output:ptr<__out, f32, write> = var @location(0)
+  %foo_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+}
+
+%foo_inner = func():Outputs {
+  $B2: {
+    %5:Outputs = construct 0.5f, 2.0f
+    ret %5
+  }
+}
+%foo = @fragment func():void {
+  $B3: {
+    %7:Outputs = call %foo_inner
+    %8:f32 = access %7, 0u
+    store %foo_loc0_Output, %8
+    %9:f32 = access %7, 1u
+    %10:ptr<push_constant, f32, read> = access %tint_push_constants, 0u
+    %11:f32 = load %10
+    %12:ptr<push_constant, f32, read> = access %tint_push_constants, 1u
+    %13:f32 = load %12
+    %14:f32 = clamp %9, %11, %13
+    store %foo_frag_depth_Output, %14
+    ret
+  }
+}
+)";
+
+    core::ir::transform::PreparePushConstantsConfig push_constants_config;
+    push_constants_config.AddInternalConstant(4, mod.symbols.New("depth_min"), ty.f32());
+    push_constants_config.AddInternalConstant(8, mod.symbols.New("depth_max"), ty.f32());
+    auto push_constants = PreparePushConstants(mod, push_constants_config);
+    EXPECT_EQ(push_constants, Success);
+
+    ShaderIOConfig config{push_constants.Get()};
+    config.depth_range_offsets = {4, 8};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvWriter_ShaderIOTest,
+       ClampFragDepth_With_Prepared_Push_Constants_MultipleFragmentShaders) {
+    auto* str_ty = ty.Struct(mod.symbols.New("Outputs"),
+                             {
+                                 {
+                                     mod.symbols.New("color"),
+                                     ty.f32(),
+                                     core::IOAttributes{
+                                         /* location */ 0u,
+                                         /* blend_src */ std::nullopt,
+                                         /* color */ std::nullopt,
+                                         /* builtin */ std::nullopt,
+                                         /* interpolation */ std::nullopt,
+                                         /* invariant */ false,
+                                     },
+                                 },
+                                 {
+                                     mod.symbols.New("depth"),
+                                     ty.f32(),
+                                     core::IOAttributes{
+                                         /* location */ std::nullopt,
+                                         /* blend_src */ std::nullopt,
+                                         /* color */ std::nullopt,
+                                         /* builtin */ core::BuiltinValue::kFragDepth,
+                                         /* interpolation */ std::nullopt,
+                                         /* invariant */ false,
+                                     },
+                                 },
+                             });
+
+    auto make_entry_point = [&](std::string_view name) {
+        auto* ep = b.Function(name, str_ty);
+        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+        b.Append(ep->Block(), [&] {  //
+            b.Return(ep, b.Construct(str_ty, 0.5_f, 2_f));
+        });
+    };
+    make_entry_point("ep1");
+    make_entry_point("ep2");
+    make_entry_point("ep3");
+
+    auto* src = R"(
+Outputs = struct @align(4) {
+  color:f32 @offset(0), @location(0)
+  depth:f32 @offset(4), @builtin(frag_depth)
+}
+
+%ep1 = @fragment func():Outputs {
+  $B1: {
+    %2:Outputs = construct 0.5f, 2.0f
+    ret %2
+  }
+}
+%ep2 = @fragment func():Outputs {
+  $B2: {
+    %4:Outputs = construct 0.5f, 2.0f
+    ret %4
+  }
+}
+%ep3 = @fragment func():Outputs {
+  $B3: {
+    %6:Outputs = construct 0.5f, 2.0f
+    ret %6
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+Outputs = struct @align(4) {
+  color:f32 @offset(0)
+  depth:f32 @offset(4)
+}
+
+tint_push_constant_struct = struct @align(4), @block {
+  depth_min:f32 @offset(4)
+  depth_max:f32 @offset(8)
+}
+
+$B1: {  # root
+  %tint_push_constants:ptr<push_constant, tint_push_constant_struct, read> = var
+  %ep1_loc0_Output:ptr<__out, f32, write> = var @location(0)
+  %ep1_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+  %ep2_loc0_Output:ptr<__out, f32, write> = var @location(0)
+  %ep2_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+  %ep3_loc0_Output:ptr<__out, f32, write> = var @location(0)
+  %ep3_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+}
+
+%ep1_inner = func():Outputs {
+  $B2: {
+    %9:Outputs = construct 0.5f, 2.0f
+    ret %9
+  }
+}
+%ep2_inner = func():Outputs {
+  $B3: {
+    %11:Outputs = construct 0.5f, 2.0f
+    ret %11
+  }
+}
+%ep3_inner = func():Outputs {
+  $B4: {
+    %13:Outputs = construct 0.5f, 2.0f
+    ret %13
+  }
+}
+%ep1 = @fragment func():void {
+  $B5: {
+    %15:Outputs = call %ep1_inner
+    %16:f32 = access %15, 0u
+    store %ep1_loc0_Output, %16
+    %17:f32 = access %15, 1u
+    %18:ptr<push_constant, f32, read> = access %tint_push_constants, 0u
+    %19:f32 = load %18
+    %20:ptr<push_constant, f32, read> = access %tint_push_constants, 1u
+    %21:f32 = load %20
+    %22:f32 = clamp %17, %19, %21
+    store %ep1_frag_depth_Output, %22
+    ret
+  }
+}
+%ep2 = @fragment func():void {
+  $B6: {
+    %24:Outputs = call %ep2_inner
+    %25:f32 = access %24, 0u
+    store %ep2_loc0_Output, %25
+    %26:f32 = access %24, 1u
+    %27:ptr<push_constant, f32, read> = access %tint_push_constants, 0u
+    %28:f32 = load %27
+    %29:ptr<push_constant, f32, read> = access %tint_push_constants, 1u
+    %30:f32 = load %29
+    %31:f32 = clamp %26, %28, %30
+    store %ep2_frag_depth_Output, %31
+    ret
+  }
+}
+%ep3 = @fragment func():void {
+  $B7: {
+    %33:Outputs = call %ep3_inner
+    %34:f32 = access %33, 0u
+    store %ep3_loc0_Output, %34
+    %35:f32 = access %33, 1u
+    %36:ptr<push_constant, f32, read> = access %tint_push_constants, 0u
+    %37:f32 = load %36
+    %38:ptr<push_constant, f32, read> = access %tint_push_constants, 1u
+    %39:f32 = load %38
+    %40:f32 = clamp %35, %37, %39
+    store %ep3_frag_depth_Output, %40
+    ret
+  }
+}
+)";
+
+    core::ir::transform::PreparePushConstantsConfig push_constants_config;
+    push_constants_config.AddInternalConstant(4, mod.symbols.New("depth_min"), ty.f32());
+    push_constants_config.AddInternalConstant(8, mod.symbols.New("depth_max"), ty.f32());
+    auto push_constants = PreparePushConstants(mod, push_constants_config);
+    EXPECT_EQ(push_constants, Success);
+
+    ShaderIOConfig config{push_constants.Get()};
+    config.depth_range_offsets = {4, 8};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
 TEST_F(SpirvWriter_ShaderIOTest, EmitVertexPointSize) {
     auto* ep = b.Function("foo", ty.vec4<f32>());
     ep->SetStage(core::ir::Function::PipelineStage::kVertex);
@@ -1489,7 +1774,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.emit_vertex_point_size = true;
     Run(ShaderIO, config);
 
@@ -1583,7 +1869,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.polyfill_f16_io = false;
     Run(ShaderIO, config);
 
@@ -1681,7 +1968,8 @@
 }
 )";
 
-    ShaderIOConfig config;
+    core::ir::transform::PushConstantLayout push_constants;
+    ShaderIOConfig config{push_constants};
     config.polyfill_f16_io = true;
     Run(ShaderIO, config);
 
diff --git a/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.spvasm b/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.spvasm
index 0ac710c..0601e90 100644
--- a/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.spvasm
+++ b/test/tint/types/functions/shader_io/fragment_output_builtins.wgsl.expected.spvasm
@@ -4,9 +4,10 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 1
-; Bound: 16
+; Bound: 26
 ; Schema: 0
                OpCapability Shader
+         %25 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %main1 "main1" %main1_frag_depth_Output
                OpExecutionMode %main1 OriginUpperLeft
@@ -32,6 +33,10 @@
     %float_1 = OpConstant %float 1
        %void = OpTypeVoid
          %13 = OpTypeFunction %void
+%_ptr_PushConstant_float = OpTypePointer PushConstant %float
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
 %main1_inner = OpFunction %float None %8
           %9 = OpLabel
                OpReturnValue %float_1
@@ -39,7 +44,12 @@
       %main1 = OpFunction %void None %13
          %14 = OpLabel
          %15 = OpFunctionCall %float %main1_inner
-               OpStore %main1_frag_depth_Output %15 None
+         %16 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_0
+         %20 = OpLoad %float %16 None
+         %21 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_1
+         %23 = OpLoad %float %21 None
+         %24 = OpExtInst %float %25 NClamp %15 %20 %23
+               OpStore %main1_frag_depth_Output %24 None
                OpReturn
                OpFunctionEnd
 ;
diff --git a/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm b/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm
index d171bcd..c8a45a0 100644
--- a/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm
+++ b/test/tint/types/functions/shader_io/fragment_output_builtins_struct.wgsl.expected.spvasm
@@ -1,9 +1,10 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 1
-; Bound: 28
+; Bound: 35
 ; Schema: 0
                OpCapability Shader
+         %31 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %main "main" %main_frag_depth_Output %main_sample_mask_Output
                OpExecutionMode %main OriginUpperLeft
@@ -44,8 +45,9 @@
          %16 = OpConstantComposite %FragmentOutputs %float_1 %uint_1
        %void = OpTypeVoid
          %20 = OpTypeFunction %void
-%_ptr_Output_uint = OpTypePointer Output %uint
+%_ptr_PushConstant_float = OpTypePointer PushConstant %float
      %uint_0 = OpConstant %uint 0
+%_ptr_Output_uint = OpTypePointer Output %uint
  %main_inner = OpFunction %FragmentOutputs None %14
          %15 = OpLabel
                OpReturnValue %16
@@ -54,9 +56,14 @@
          %21 = OpLabel
          %22 = OpFunctionCall %FragmentOutputs %main_inner
          %23 = OpCompositeExtract %float %22 0
-               OpStore %main_frag_depth_Output %23 None
-         %24 = OpCompositeExtract %uint %22 1
-         %25 = OpAccessChain %_ptr_Output_uint %main_sample_mask_Output %uint_0
-               OpStore %25 %24 None
+         %24 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_0
+         %27 = OpLoad %float %24 None
+         %28 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_1
+         %29 = OpLoad %float %28 None
+         %30 = OpExtInst %float %31 NClamp %23 %27 %29
+               OpStore %main_frag_depth_Output %30 None
+         %32 = OpCompositeExtract %uint %22 1
+         %33 = OpAccessChain %_ptr_Output_uint %main_sample_mask_Output %uint_0
+               OpStore %33 %32 None
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.spvasm b/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.spvasm
index f6a498f..65f1ac6 100644
--- a/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.spvasm
+++ b/test/tint/types/functions/shader_io/fragment_output_mixed.wgsl.expected.spvasm
@@ -1,9 +1,10 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 1
-; Bound: 46
+; Bound: 53
 ; Schema: 0
                OpCapability Shader
+         %47 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %main "main" %main_loc0_Output %main_frag_depth_Output %main_loc1_Output %main_loc2_Output %main_sample_mask_Output %main_loc3_Output
                OpExecutionMode %main OriginUpperLeft
@@ -75,6 +76,7 @@
          %25 = OpConstantComposite %FragmentOutputs %int_1 %float_2 %uint_1 %float_1 %uint_2 %30
        %void = OpTypeVoid
          %35 = OpTypeFunction %void
+%_ptr_PushConstant_float = OpTypePointer PushConstant %float
      %uint_0 = OpConstant %uint 0
  %main_inner = OpFunction %FragmentOutputs None %23
          %24 = OpLabel
@@ -86,15 +88,20 @@
          %38 = OpCompositeExtract %int %37 0
                OpStore %main_loc0_Output %38 None
          %39 = OpCompositeExtract %float %37 1
-               OpStore %main_frag_depth_Output %39 None
-         %40 = OpCompositeExtract %uint %37 2
-               OpStore %main_loc1_Output %40 None
-         %41 = OpCompositeExtract %float %37 3
-               OpStore %main_loc2_Output %41 None
-         %42 = OpCompositeExtract %uint %37 4
-         %43 = OpAccessChain %_ptr_Output_uint %main_sample_mask_Output %uint_0
-               OpStore %43 %42 None
-         %45 = OpCompositeExtract %v4float %37 5
-               OpStore %main_loc3_Output %45 None
+         %40 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_0
+         %43 = OpLoad %float %40 None
+         %44 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_1
+         %45 = OpLoad %float %44 None
+         %46 = OpExtInst %float %47 NClamp %39 %43 %45
+               OpStore %main_frag_depth_Output %46 None
+         %48 = OpCompositeExtract %uint %37 2
+               OpStore %main_loc1_Output %48 None
+         %49 = OpCompositeExtract %float %37 3
+               OpStore %main_loc2_Output %49 None
+         %50 = OpCompositeExtract %uint %37 4
+         %51 = OpAccessChain %_ptr_Output_uint %main_sample_mask_Output %uint_0
+               OpStore %51 %50 None
+         %52 = OpCompositeExtract %v4float %37 5
+               OpStore %main_loc3_Output %52 None
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm b/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm
index aa28337..ee4eabb 100644
--- a/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm
+++ b/test/tint/types/functions/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm
@@ -1,13 +1,14 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 1
-; Bound: 59
+; Bound: 66
 ; Schema: 0
                OpCapability Shader
                OpCapability Float16
                OpCapability UniformAndStorageBuffer16BitAccess
                OpCapability StorageBuffer16BitAccess
                OpCapability StorageInputOutput16
+         %58 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %main "main" %main_loc0_Output %main_frag_depth_Output %main_loc1_Output %main_loc2_Output %main_sample_mask_Output %main_loc3_Output %main_loc4_Output %main_loc5_Output
                OpExecutionMode %main OriginUpperLeft
@@ -98,6 +99,7 @@
          %31 = OpConstantComposite %FragmentOutputs %int_1 %float_2 %uint_1 %float_1 %uint_2 %36 %half_0x1_2p_1 %40
        %void = OpTypeVoid
          %46 = OpTypeFunction %void
+%_ptr_PushConstant_float = OpTypePointer PushConstant %float
      %uint_0 = OpConstant %uint 0
  %main_inner = OpFunction %FragmentOutputs None %29
          %30 = OpLabel
@@ -109,19 +111,24 @@
          %49 = OpCompositeExtract %int %48 0
                OpStore %main_loc0_Output %49 None
          %50 = OpCompositeExtract %float %48 1
-               OpStore %main_frag_depth_Output %50 None
-         %51 = OpCompositeExtract %uint %48 2
-               OpStore %main_loc1_Output %51 None
-         %52 = OpCompositeExtract %float %48 3
-               OpStore %main_loc2_Output %52 None
-         %53 = OpCompositeExtract %uint %48 4
-         %54 = OpAccessChain %_ptr_Output_uint %main_sample_mask_Output %uint_0
-               OpStore %54 %53 None
-         %56 = OpCompositeExtract %v4float %48 5
-               OpStore %main_loc3_Output %56 None
-         %57 = OpCompositeExtract %half %48 6
-               OpStore %main_loc4_Output %57 None
-         %58 = OpCompositeExtract %v3half %48 7
-               OpStore %main_loc5_Output %58 None
+         %51 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_0
+         %54 = OpLoad %float %51 None
+         %55 = OpAccessChain %_ptr_PushConstant_float %tint_push_constants %uint_1
+         %56 = OpLoad %float %55 None
+         %57 = OpExtInst %float %58 NClamp %50 %54 %56
+               OpStore %main_frag_depth_Output %57 None
+         %59 = OpCompositeExtract %uint %48 2
+               OpStore %main_loc1_Output %59 None
+         %60 = OpCompositeExtract %float %48 3
+               OpStore %main_loc2_Output %60 None
+         %61 = OpCompositeExtract %uint %48 4
+         %62 = OpAccessChain %_ptr_Output_uint %main_sample_mask_Output %uint_0
+               OpStore %62 %61 None
+         %63 = OpCompositeExtract %v4float %48 5
+               OpStore %main_loc3_Output %63 None
+         %64 = OpCompositeExtract %half %48 6
+               OpStore %main_loc4_Output %64 None
+         %65 = OpCompositeExtract %v3half %48 7
+               OpStore %main_loc5_Output %65 None
                OpReturn
                OpFunctionEnd