[ir] Add capability for multiple entry points

Most transforms and printers assume there is only one entry point, as
we require that SingleEntryPoint has run before entering the
backends. Capture this in a capability so that the fuzzers respect
this precondition.

Change-Id: I8b20ab1ae9cacfe3129c20197c1bebbdf2e367b5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/244157
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/fuzz/ir/fuzz.cc b/src/tint/cmd/fuzz/ir/fuzz.cc
index 948c6ef..56fa4f3 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.cc
+++ b/src/tint/cmd/fuzz/ir/fuzz.cc
@@ -101,7 +101,11 @@
                 }
             }
 
-            if (auto val = core::ir::Validate(ir.Get()); val != Success) {
+            if (auto val = core::ir::Validate(ir.Get(),
+                                              core::ir::Capabilities{
+                                                  core::ir::Capability::kAllowMultipleEntryPoints,
+                                              });
+                val != Success) {
                 TINT_ICE() << val.Failure();
             }
             // Copy relevant options from wgsl::Context to ir::Context
diff --git a/src/tint/lang/core/ir/transform/single_entry_point.cc b/src/tint/lang/core/ir/transform/single_entry_point.cc
index 4ec9c04..040f2a0 100644
--- a/src/tint/lang/core/ir/transform/single_entry_point.cc
+++ b/src/tint/lang/core/ir/transform/single_entry_point.cc
@@ -95,8 +95,11 @@
 }  // namespace
 
 Result<SuccessType> SingleEntryPoint(Module& ir, std::string_view entry_point_name) {
-    auto result = ValidateAndDumpIfNeeded(
-        ir, "core.SingleEntryPoint", core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    auto result = ValidateAndDumpIfNeeded(ir, "core.SingleEntryPoint",
+                                          Capabilities{
+                                              Capability::kAllowMultipleEntryPoints,
+                                              Capability::kAllowOverrides,
+                                          });
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
index d232617..eaa1a27 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
@@ -1805,121 +1805,5 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(IR_ZeroInitWorkgroupMemoryTest, MultipleEntryPoints_SameVarViaHelper) {
-    auto* var = MakeVar("wgvar", ty.bool_());
-
-    auto* foo = b.Function("foo", ty.void_());
-    b.Append(foo->Block(), [&] {  //
-        auto* loop = b.Loop();
-        b.Append(loop->Body(), [&] {  //
-            b.Continue(loop);
-            b.Append(loop->Continuing(), [&] {  //
-                auto* load = b.Load(var);
-                b.BreakIf(loop, load);
-            });
-        });
-        b.Return(foo);
-    });
-
-    auto* ep1 = MakeEntryPoint("ep1", 1, 1, 1);
-    b.Append(ep1->Block(), [&] {  //
-        b.Call(ty.void_(), foo);
-        b.Return(ep1);
-    });
-
-    auto* ep2 = MakeEntryPoint("ep2", 1, 1, 1);
-    b.Append(ep2->Block(), [&] {  //
-        b.Call(ty.void_(), foo);
-        b.Return(ep2);
-    });
-
-    auto* src = R"(
-$B1: {  # root
-  %wgvar:ptr<workgroup, bool, read_write> = var undef
-}
-
-%foo = func():void {
-  $B2: {
-    loop [b: $B3, c: $B4] {  # loop_1
-      $B3: {  # body
-        continue  # -> $B4
-      }
-      $B4: {  # continuing
-        %3:bool = load %wgvar
-        break_if %3  # -> [t: exit_loop loop_1, f: $B3]
-      }
-    }
-    ret
-  }
-}
-%ep1 = @compute @workgroup_size(1u, 1u, 1u) func():void {
-  $B5: {
-    %5:void = call %foo
-    ret
-  }
-}
-%ep2 = @compute @workgroup_size(1u, 1u, 1u) func():void {
-  $B6: {
-    %7:void = call %foo
-    ret
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-$B1: {  # root
-  %wgvar:ptr<workgroup, bool, read_write> = var undef
-}
-
-%foo = func():void {
-  $B2: {
-    loop [b: $B3, c: $B4] {  # loop_1
-      $B3: {  # body
-        continue  # -> $B4
-      }
-      $B4: {  # continuing
-        %3:bool = load %wgvar
-        break_if %3  # -> [t: exit_loop loop_1, f: $B3]
-      }
-    }
-    ret
-  }
-}
-%ep1 = @compute @workgroup_size(1u, 1u, 1u) func(%tint_local_index:u32 [@local_invocation_index]):void {
-  $B5: {
-    %6:bool = lt %tint_local_index, 1u
-    if %6 [t: $B6] {  # if_1
-      $B6: {  # true
-        store %wgvar, false
-        exit_if  # if_1
-      }
-    }
-    %7:void = workgroupBarrier
-    %8:void = call %foo
-    ret
-  }
-}
-%ep2 = @compute @workgroup_size(1u, 1u, 1u) func(%tint_local_index_1:u32 [@local_invocation_index]):void {  # %tint_local_index_1: 'tint_local_index'
-  $B7: {
-    %11:bool = lt %tint_local_index_1, 1u
-    if %11 [t: $B8] {  # if_2
-      $B8: {  # true
-        store %wgvar, false
-        exit_if  # if_2
-      }
-    }
-    %12:void = workgroupBarrier
-    %13:void = call %foo
-    ret
-  }
-}
-)";
-
-    Run(ZeroInitWorkgroupMemory);
-
-    EXPECT_EQ(expect, str());
-}
-
 }  // namespace
 }  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 24fc0db..4b09228 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -2072,8 +2072,17 @@
     scope_stack_.Push();
     TINT_DEFER(scope_stack_.Pop());
 
-    // Checking the name early, so its usage can be recorded, even if the function is malformed.
     if (func->IsEntryPoint()) {
+        // Check that there is at most one entry point unless we allow multiple entry points.
+        if (!capabilities_.Contains(Capability::kAllowMultipleEntryPoints)) {
+            if (!entry_point_names_.IsEmpty()) {
+                AddError(func) << "a module with multiple entry points requires the "
+                                  "AllowMultipleEntryPoints capability";
+                return;
+            }
+        }
+
+        // Checking the name early, so its usage can be recorded, even if the function is malformed.
         const auto name = mod_.NameOf(func).Name();
         if (!entry_point_names_.Add(name)) {
             AddError(func) << "entry point name " << style::Function(name) << " is not unique";
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h
index b7efa1d..444537c 100644
--- a/src/tint/lang/core/ir/validator.h
+++ b/src/tint/lang/core/ir/validator.h
@@ -50,6 +50,8 @@
     kAllowHandleVarsWithoutBindings,
     /// Allows module scoped lets
     kAllowModuleScopeLets,
+    /// Allows multiple entry points in the module.
+    kAllowMultipleEntryPoints,
     /// Allow overrides
     kAllowOverrides,
     /// Allows pointers and handle addressspace variables inside structures.
diff --git a/src/tint/lang/core/ir/validator_function_test.cc b/src/tint/lang/core/ir/validator_function_test.cc
index 0eafdec..796a42d 100644
--- a/src/tint/lang/core/ir/validator_function_test.cc
+++ b/src/tint/lang/core/ir/validator_function_test.cc
@@ -95,6 +95,37 @@
 )")) << res.Failure();
 }
 
+TEST_F(IR_ValidatorTest, Function_MultipleEntryPoints_WithCapability) {
+    auto* ep1 = ComputeEntryPoint("ep1");
+    ep1->Block()->Append(b.Return(ep1));
+
+    auto* ep2 = ComputeEntryPoint("ep2");
+    ep2->Block()->Append(b.Return(ep2));
+
+    auto res = ir::Validate(mod, Capabilities{
+                                     Capability::kAllowMultipleEntryPoints,
+                                 });
+    ASSERT_EQ(res, Success) << res.Failure();
+}
+
+TEST_F(IR_ValidatorTest, Function_MultipleEntryPoints_WithoutCapability) {
+    auto* ep1 = ComputeEntryPoint("ep1");
+    ep1->Block()->Append(b.Return(ep1));
+
+    auto* ep2 = ComputeEntryPoint("ep2");
+    ep2->Block()->Append(b.Return(ep2));
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(
+        res.Failure().reason,
+        testing::HasSubstr(
+            R"(:6:1 error: a module with multiple entry points requires the AllowMultipleEntryPoints capability
+%ep2 = @compute @workgroup_size(1u, 1u, 1u) func():void {
+^^^^
+)")) << res.Failure();
+}
+
 TEST_F(IR_ValidatorTest, Function_DuplicateEntryPointNames) {
     auto* c = ComputeEntryPoint("dup");
     c->Block()->Append(b.Return(c));
@@ -102,7 +133,9 @@
     auto* f = FragmentEntryPoint("dup");
     f->Block()->Append(b.Return(f));
 
-    auto res = ir::Validate(mod);
+    auto res = ir::Validate(mod, Capabilities{
+                                     Capability::kAllowMultipleEntryPoints,
+                                 });
     ASSERT_NE(res, Success);
     EXPECT_THAT(res.Failure().reason,
                 testing::HasSubstr(R"(:6:1 error: entry point name 'dup' is not unique
diff --git a/src/tint/lang/glsl/writer/raise/shader_io_test.cc b/src/tint/lang/glsl/writer/raise/shader_io_test.cc
index ad4134f..db661c6 100644
--- a/src/tint/lang/glsl/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/glsl/writer/raise/shader_io_test.cc
@@ -679,147 +679,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(GlslWriter_ShaderIOTest, Struct_SharedByVertexAndFragment) {
-    auto* vec4f = ty.vec4<f32>();
-    auto* str_ty = ty.Struct(mod.symbols.New("Interface"),
-                             {
-                                 {
-                                     mod.symbols.New("position"),
-                                     vec4f,
-                                     core::IOAttributes{
-                                         .builtin = core::BuiltinValue::kPosition,
-                                     },
-                                 },
-                                 {
-                                     mod.symbols.New("color"),
-                                     vec4f,
-                                     core::IOAttributes{
-                                         .location = 0u,
-                                     },
-                                 },
-                             });
-
-    // Vertex shader.
-    {
-        auto* ep = b.Function("vert", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kVertex);
-
-        b.Append(ep->Block(), [&] {  //
-            auto* position = b.Construct(vec4f, 0_f);
-            auto* color = b.Construct(vec4f, 1_f);
-            b.Return(ep, b.Construct(str_ty, position, color));
-        });
-    }
-
-    // Fragment shader.
-    {
-        auto* ep = b.Function("frag", vec4f);
-        auto* inputs = b.FunctionParam("inputs", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-        ep->SetParams({inputs});
-        ep->SetReturnLocation(0u);
-
-        b.Append(ep->Block(), [&] {  //
-            auto* position = b.Access(vec4f, inputs, 0_u);
-            auto* color = b.Access(vec4f, inputs, 1_u);
-            b.Return(ep, b.Add(vec4f, position, color));
-        });
-    }
-
-    auto* src = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0), @builtin(position)
-  color:vec4<f32> @offset(16), @location(0)
-}
-
-%vert = @vertex func():Interface {
-  $B1: {
-    %2:vec4<f32> = construct 0.0f
-    %3:vec4<f32> = construct 1.0f
-    %4:Interface = construct %2, %3
-    ret %4
-  }
-}
-%frag = @fragment func(%inputs:Interface):vec4<f32> [@location(0)] {
-  $B2: {
-    %7:vec4<f32> = access %inputs, 0u
-    %8:vec4<f32> = access %inputs, 1u
-    %9:vec4<f32> = add %7, %8
-    ret %9
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0)
-  color:vec4<f32> @offset(16)
-}
-
-$B1: {  # root
-  %vert_position:ptr<__out, vec4<f32>, write> = var undef @builtin(position)
-  %vert_loc0_Output:ptr<__out, vec4<f32>, write> = var undef @location(0)
-  %vert___point_size:ptr<__out, f32, write> = var undef @builtin(__point_size)
-  %frag_position:ptr<__in, vec4<f32>, read> = var undef @builtin(position)
-  %frag_loc0_Input:ptr<__in, vec4<f32>, read> = var undef @location(0)
-  %frag_loc0_Output:ptr<__out, vec4<f32>, write> = var undef @location(0)
-}
-
-%vert_inner = func():Interface {
-  $B2: {
-    %8:vec4<f32> = construct 0.0f
-    %9:vec4<f32> = construct 1.0f
-    %10:Interface = construct %8, %9
-    ret %10
-  }
-}
-%frag_inner = func(%inputs:Interface):vec4<f32> {
-  $B3: {
-    %13:vec4<f32> = access %inputs, 0u
-    %14:vec4<f32> = access %inputs, 1u
-    %15:vec4<f32> = add %13, %14
-    ret %15
-  }
-}
-%vert = @vertex func():void {
-  $B4: {
-    %17:Interface = call %vert_inner
-    %18:vec4<f32> = access %17, 0u
-    %19:f32 = swizzle %18, x
-    %20:f32 = swizzle %18, y
-    %21:f32 = negation %20
-    %22:f32 = swizzle %18, z
-    %23:f32 = swizzle %18, w
-    %24:f32 = mul 2.0f, %22
-    %25:f32 = sub %24, %23
-    %26:vec4<f32> = construct %19, %21, %25, %23
-    store %vert_position, %26
-    %27:vec4<f32> = access %17, 1u
-    store %vert_loc0_Output, %27
-    store %vert___point_size, 1.0f
-    ret
-  }
-}
-%frag = @fragment func():void {
-  $B5: {
-    %29:vec4<f32> = load %frag_position
-    %30:vec4<f32> = load %frag_loc0_Input
-    %31:Interface = construct %29, %30
-    %32:vec4<f32> = call %frag_inner, %31
-    store %frag_loc0_Output, %32
-    ret
-  }
-}
-)";
-
-    core::ir::transform::ImmediateDataLayout immediate_data;
-    ShaderIOConfig config{immediate_data};
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(GlslWriter_ShaderIOTest, Struct_SharedWithBuffer) {
     auto* vec4f = ty.vec4<f32>();
     auto* str_ty =
@@ -1005,8 +864,8 @@
     EXPECT_EQ(expect, str());
 }
 
-// Test that interpolation attributes are stripped from vertex inputs and fragment outputs.
-TEST_F(GlslWriter_ShaderIOTest, InterpolationOnVertexInputOrFragmentOutput) {
+// Test that interpolation attributes are stripped from vertex inputs.
+TEST_F(GlslWriter_ShaderIOTest, InterpolationOnVertexInput) {
     auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"),
                              {
                                  {
@@ -1023,45 +882,20 @@
                                  },
                              });
 
-    // Vertex shader.
-    {
-        auto* ep = b.Function("vert", ty.vec4<f32>());
-        ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
-        ep->SetReturnInvariant(true);
-        ep->SetStage(core::ir::Function::PipelineStage::kVertex);
+    auto* ep = b.Function("vert", ty.vec4<f32>());
+    ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
+    ep->SetReturnInvariant(true);
+    ep->SetStage(core::ir::Function::PipelineStage::kVertex);
 
-        auto* str_param = b.FunctionParam("input", str_ty);
-        auto* ival = b.FunctionParam("ival", ty.i32());
-        ival->SetLocation(1);
-        ival->SetInterpolation(core::Interpolation{core::InterpolationType::kFlat});
-        ep->SetParams({str_param, ival});
+    auto* str_param = b.FunctionParam("input", str_ty);
+    auto* ival = b.FunctionParam("ival", ty.i32());
+    ival->SetLocation(1);
+    ival->SetInterpolation(core::Interpolation{core::InterpolationType::kFlat});
+    ep->SetParams({str_param, ival});
 
-        b.Append(ep->Block(), [&] {  //
-            b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f));
-        });
-    }
-
-    // Fragment shader with struct output.
-    {
-        auto* ep = b.Function("frag1", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-
-        b.Append(ep->Block(), [&] {  //
-            b.Return(ep, b.Construct(str_ty, 0.5_f));
-        });
-    }
-
-    // Fragment shader with non-struct output.
-    {
-        auto* ep = b.Function("frag2", ty.i32());
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-        ep->SetReturnLocation(0);
-        ep->SetReturnInterpolation(core::Interpolation{core::InterpolationType::kFlat});
-
-        b.Append(ep->Block(), [&] {  //
-            b.Return(ep, b.Constant(42_i));
-        });
-    }
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f));
+    });
 
     auto* src = R"(
 MyStruct = struct @align(4) {
@@ -1074,17 +908,6 @@
     ret %4
   }
 }
-%frag1 = @fragment func():MyStruct {
-  $B2: {
-    %6:MyStruct = construct 0.5f
-    ret %6
-  }
-}
-%frag2 = @fragment func():i32 [@location(0), @interpolate(flat)] {
-  $B3: {
-    ret 42i
-  }
-}
 )";
     EXPECT_EQ(src, str());
 
@@ -1098,58 +921,147 @@
   %vert_loc1_Input_1:ptr<__in, i32, read> = var undef @location(1)  # %vert_loc1_Input_1: 'vert_loc1_Input'
   %vert_position:ptr<__out, vec4<f32>, write> = var undef @invariant @builtin(position)
   %vert___point_size:ptr<__out, f32, write> = var undef @builtin(__point_size)
-  %frag1_loc1_Output:ptr<__out, f32, write> = var undef @location(1)
-  %frag2_loc0_Output:ptr<__out, i32, write> = var undef @location(0)
 }
 
 %vert_inner = func(%input:MyStruct, %ival:i32):vec4<f32> {
   $B2: {
-    %10:vec4<f32> = construct 0.5f
-    ret %10
-  }
-}
-%frag1_inner = func():MyStruct {
-  $B3: {
-    %12:MyStruct = construct 0.5f
-    ret %12
-  }
-}
-%frag2_inner = func():i32 {
-  $B4: {
-    ret 42i
+    %8:vec4<f32> = construct 0.5f
+    ret %8
   }
 }
 %vert = @vertex func():void {
-  $B5: {
-    %15:f32 = load %vert_loc1_Input
-    %16:MyStruct = construct %15
-    %17:i32 = load %vert_loc1_Input_1
-    %18:vec4<f32> = call %vert_inner, %16, %17
-    %19:f32 = swizzle %18, x
-    %20:f32 = swizzle %18, y
-    %21:f32 = negation %20
-    %22:f32 = swizzle %18, z
-    %23:f32 = swizzle %18, w
-    %24:f32 = mul 2.0f, %22
-    %25:f32 = sub %24, %23
-    %26:vec4<f32> = construct %19, %21, %25, %23
-    store %vert_position, %26
+  $B3: {
+    %10:f32 = load %vert_loc1_Input
+    %11:MyStruct = construct %10
+    %12:i32 = load %vert_loc1_Input_1
+    %13:vec4<f32> = call %vert_inner, %11, %12
+    %14:f32 = swizzle %13, x
+    %15:f32 = swizzle %13, y
+    %16:f32 = negation %15
+    %17:f32 = swizzle %13, z
+    %18:f32 = swizzle %13, w
+    %19:f32 = mul 2.0f, %17
+    %20:f32 = sub %19, %18
+    %21:vec4<f32> = construct %14, %16, %20, %18
+    store %vert_position, %21
     store %vert___point_size, 1.0f
     ret
   }
 }
+)";
+
+    core::ir::transform::ImmediateDataLayout immediate_data;
+    ShaderIOConfig config{immediate_data};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Test that interpolation attributes are stripped from fragment struct outputs
+TEST_F(GlslWriter_ShaderIOTest, InterpolationOnFragmentOutput_Struct) {
+    auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"),
+                             {
+                                 {
+                                     mod.symbols.New("color"),
+                                     ty.f32(),
+                                     core::IOAttributes{
+                                         .location = 1u,
+                                         .interpolation =
+                                             core::Interpolation{
+                                                 core::InterpolationType::kLinear,
+                                                 core::InterpolationSampling::kSample,
+                                             },
+                                     },
+                                 },
+                             });
+
+    auto* ep = b.Function("frag1", str_ty);
+    ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Construct(str_ty, 0.5_f));
+    });
+
+    auto* src = R"(
+MyStruct = struct @align(4) {
+  color:f32 @offset(0), @location(1), @interpolate(linear, sample)
+}
+
+%frag1 = @fragment func():MyStruct {
+  $B1: {
+    %2:MyStruct = construct 0.5f
+    ret %2
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+MyStruct = struct @align(4) {
+  color:f32 @offset(0)
+}
+
+$B1: {  # root
+  %frag1_loc1_Output:ptr<__out, f32, write> = var undef @location(1)
+}
+
+%frag1_inner = func():MyStruct {
+  $B2: {
+    %3:MyStruct = construct 0.5f
+    ret %3
+  }
+}
 %frag1 = @fragment func():void {
-  $B6: {
-    %28:MyStruct = call %frag1_inner
-    %29:f32 = access %28, 0u
-    store %frag1_loc1_Output, %29
+  $B3: {
+    %5:MyStruct = call %frag1_inner
+    %6:f32 = access %5, 0u
+    store %frag1_loc1_Output, %6
     ret
   }
 }
+)";
+
+    core::ir::transform::ImmediateDataLayout immediate_data;
+    ShaderIOConfig config{immediate_data};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Test that interpolation attributes are stripped from fragment struct outputs
+TEST_F(GlslWriter_ShaderIOTest, InterpolationOnFragmentOutput_NonStruct) {
+    auto* ep = b.Function("frag2", ty.i32());
+    ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+    ep->SetReturnLocation(0);
+    ep->SetReturnInterpolation(core::Interpolation{core::InterpolationType::kFlat});
+
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Constant(42_i));
+    });
+
+    auto* src = R"(
+%frag2 = @fragment func():i32 [@location(0), @interpolate(flat)] {
+  $B1: {
+    ret 42i
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %frag2_loc0_Output:ptr<__out, i32, write> = var undef @location(0)
+}
+
+%frag2_inner = func():i32 {
+  $B2: {
+    ret 42i
+  }
+}
 %frag2 = @fragment func():void {
-  $B7: {
-    %31:i32 = call %frag2_inner
-    store %frag2_loc0_Output, %31
+  $B3: {
+    %4:i32 = call %frag2_inner
+    store %frag2_loc0_Output, %4
     ret
   }
 }
@@ -1255,161 +1167,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(GlslWriter_ShaderIOTest, ClampFragDepth_MultipleFragmentShaders) {
-    auto* str_ty =
-        ty.Struct(mod.symbols.New("Outputs"), {
-                                                  {
-                                                      mod.symbols.New("color"),
-                                                      ty.f32(),
-                                                      core::IOAttributes{
-                                                          .location = 0u,
-                                                      },
-                                                  },
-                                                  {
-                                                      mod.symbols.New("depth"),
-                                                      ty.f32(),
-                                                      core::IOAttributes{
-                                                          .builtin = core::BuiltinValue::kFragDepth,
-                                                      },
-                                                  },
-                                              });
-
-    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_immediate_data_struct = struct @align(4), @block {
-  depth_min:f32 @offset(4)
-  depth_max:f32 @offset(8)
-}
-
-$B1: {  # root
-  %tint_immediate_data:ptr<immediate, tint_immediate_data_struct, read> = var undef
-  %ep1_loc0_Output:ptr<__out, f32, write> = var undef @location(0)
-  %ep1_frag_depth:ptr<__out, f32, write> = var undef @builtin(frag_depth)
-  %ep2_loc0_Output:ptr<__out, f32, write> = var undef @location(0)
-  %ep2_frag_depth:ptr<__out, f32, write> = var undef @builtin(frag_depth)
-  %ep3_loc0_Output:ptr<__out, f32, write> = var undef @location(0)
-  %ep3_frag_depth:ptr<__out, f32, write> = var undef @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<immediate, f32, read> = access %tint_immediate_data, 0u
-    %19:f32 = load %18
-    %20:ptr<immediate, f32, read> = access %tint_immediate_data, 1u
-    %21:f32 = load %20
-    %22:f32 = clamp %17, %19, %21
-    store %ep1_frag_depth, %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<immediate, f32, read> = access %tint_immediate_data, 0u
-    %28:f32 = load %27
-    %29:ptr<immediate, f32, read> = access %tint_immediate_data, 1u
-    %30:f32 = load %29
-    %31:f32 = clamp %26, %28, %30
-    store %ep2_frag_depth, %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<immediate, f32, read> = access %tint_immediate_data, 0u
-    %37:f32 = load %36
-    %38:ptr<immediate, f32, read> = access %tint_immediate_data, 1u
-    %39:f32 = load %38
-    %40:f32 = clamp %35, %37, %39
-    store %ep3_frag_depth, %40
-    ret
-  }
-}
-)";
-
-    core::ir::transform::PrepareImmediateDataConfig immediate_data_config;
-    immediate_data_config.AddInternalImmediateData(4, mod.symbols.New("depth_min"), ty.f32());
-    immediate_data_config.AddInternalImmediateData(8, mod.symbols.New("depth_max"), ty.f32());
-    auto immediate_data = PrepareImmediateData(mod, immediate_data_config);
-    EXPECT_EQ(immediate_data, Success);
-    ShaderIOConfig config{immediate_data.Get()};
-    config.depth_range_offsets = {4, 8};
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(GlslWriter_ShaderIOTest, BGRASwizzleSingleValue) {
     auto* ep = b.Function("vert", ty.vec4<f32>());
     ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
diff --git a/src/tint/lang/hlsl/writer/function_test.cc b/src/tint/lang/hlsl/writer/function_test.cc
index ad8ead0..e200c46 100644
--- a/src/tint/lang/hlsl/writer/function_test.cc
+++ b/src/tint/lang/hlsl/writer/function_test.cc
@@ -227,196 +227,6 @@
 )");
 }
 
-TEST_F(HlslWriterTest, FunctionEntryPointSharedStructDifferentStages) {
-    // struct Interface {
-    //   @builtin(position) pos : vec4<f32>;
-    //   @location(1) col1 : f32;
-    //   @location(2) col2 : f32;
-    // };
-    //
-    // fn vert_main() -> Interface {
-    //   return Interface(vec4<f32>(), 0.4, 0.6);
-    // }
-    //
-    // fn frag_main(inputs : Interface) {
-    //   const r = inputs.col1;
-    //   const g = inputs.col2;
-    //   const p = inputs.pos;
-    // }
-
-    core::IOAttributes pos_attrs{};
-    pos_attrs.builtin = core::BuiltinValue::kPosition;
-    core::IOAttributes col1_attrs{};
-    col1_attrs.location = 1;
-    core::IOAttributes col2_attrs{};
-    col2_attrs.location = 2;
-
-    Vector members{
-        ty.Get<core::type::StructMember>(b.ir.symbols.New("pos"), ty.vec4<f32>(), 0u, 0u, 16u, 16u,
-                                         pos_attrs),
-        ty.Get<core::type::StructMember>(b.ir.symbols.New("col1"), ty.f32(), 1u, 16u, 4u, 4u,
-                                         col1_attrs),
-        ty.Get<core::type::StructMember>(b.ir.symbols.New("col2"), ty.f32(), 2u, 16u, 4u, 4u,
-                                         col2_attrs),
-    };
-    auto* strct = ty.Struct(b.ir.symbols.New("Interface"), std::move(members));
-
-    auto* vert_func = b.Function("vert_main", strct, core::ir::Function::PipelineStage::kVertex);
-    b.Append(vert_func->Block(), [&] {  //
-        b.Return(vert_func, b.Construct(strct, b.Zero(ty.vec4<f32>()), 0.5_f, 0.25_f));
-    });
-
-    auto* frag_param = b.FunctionParam("inputs", strct);
-    auto* frag_func =
-        b.Function("frag_main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    frag_func->SetParams({frag_param});
-    b.Append(frag_func->Block(), [&] {
-        auto* r = b.Access(ty.f32(), frag_param, 1_u);
-        auto* g = b.Access(ty.f32(), frag_param, 2_u);
-        auto* p = b.Access(ty.vec4<f32>(), frag_param, 0_u);
-
-        b.Let("r", r);
-        b.Let("g", g);
-        b.Let("p", p);
-        b.Return(frag_func);
-    });
-
-    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(struct Interface {
-  float4 pos;
-  float col1;
-  float col2;
-};
-
-struct vert_main_outputs {
-  float Interface_col1 : TEXCOORD1;
-  float Interface_col2 : TEXCOORD2;
-  float4 Interface_pos : SV_Position;
-};
-
-struct frag_main_inputs {
-  float Interface_col1 : TEXCOORD1;
-  float Interface_col2 : TEXCOORD2;
-  float4 Interface_pos : SV_Position;
-};
-
-
-Interface vert_main_inner() {
-  Interface v = {(0.0f).xxxx, 0.5f, 0.25f};
-  return v;
-}
-
-void frag_main_inner(Interface inputs) {
-  float r = inputs.col1;
-  float g = inputs.col2;
-  float4 p = inputs.pos;
-}
-
-vert_main_outputs vert_main() {
-  Interface v_1 = vert_main_inner();
-  vert_main_outputs v_2 = {v_1.col1, v_1.col2, v_1.pos};
-  return v_2;
-}
-
-void frag_main(frag_main_inputs inputs) {
-  Interface v_3 = {float4(inputs.Interface_pos.xyz, (1.0f / inputs.Interface_pos.w)), inputs.Interface_col1, inputs.Interface_col2};
-  frag_main_inner(v_3);
-}
-
-)");
-    EXPECT_EQ(0u, output_.workgroup_info.x);
-    EXPECT_EQ(0u, output_.workgroup_info.y);
-    EXPECT_EQ(0u, output_.workgroup_info.z);
-}
-
-TEST_F(HlslWriterTest, FunctionEntryPointSharedStructHelperFunction) {
-    // struct VertexOutput {
-    //   @builtin(position) pos : vec4<f32>;
-    // };
-    // fn foo(x : f32) -> VertexOutput {
-    //   return VertexOutput(vec4<f32>(x, x, x, 1.0));
-    // }
-    // fn vert_main1() -> VertexOutput {
-    //   return foo(0.5);
-    // }
-    // fn vert_main2() -> VertexOutput {
-    //   return foo(0.25);
-    // }
-
-    core::IOAttributes pos_attrs{};
-    pos_attrs.builtin = core::BuiltinValue::kPosition;
-
-    Vector members{ty.Get<core::type::StructMember>(b.ir.symbols.New("pos"), ty.vec4<f32>(), 0u, 0u,
-                                                    16u, 16u, pos_attrs)};
-    auto* strct = ty.Struct(b.ir.symbols.New("VertexOutput"), std::move(members));
-
-    auto* x = b.FunctionParam("x", ty.f32());
-    auto* foo_func = b.Function("foo", strct);
-    foo_func->SetParams({x});
-    b.Append(foo_func->Block(), [&] {  //
-        b.Return(foo_func, b.Construct(strct, b.Construct(ty.vec4<f32>(), x, x, x, 1_f)));
-    });
-
-    {
-        auto* vert1_func =
-            b.Function("vert1_main1", strct, core::ir::Function::PipelineStage::kVertex);
-        b.Append(vert1_func->Block(), [&] {  //
-            b.Return(vert1_func, b.Call(foo_func, 0.5_f));
-        });
-    }
-
-    {
-        auto* vert2_func =
-            b.Function("vert2_main1", strct, core::ir::Function::PipelineStage::kVertex);
-        b.Append(vert2_func->Block(), [&] {  //
-            b.Return(vert2_func, b.Call(foo_func, 0.25_f));
-        });
-    }
-
-    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(struct VertexOutput {
-  float4 pos;
-};
-
-struct vert1_main1_outputs {
-  float4 VertexOutput_pos : SV_Position;
-};
-
-struct vert2_main1_outputs {
-  float4 VertexOutput_pos : SV_Position;
-};
-
-
-VertexOutput foo(float x) {
-  VertexOutput v = {float4(x, x, x, 1.0f)};
-  return v;
-}
-
-VertexOutput vert1_main1_inner() {
-  VertexOutput v_1 = foo(0.5f);
-  return v_1;
-}
-
-VertexOutput vert2_main1_inner() {
-  VertexOutput v_2 = foo(0.25f);
-  return v_2;
-}
-
-vert1_main1_outputs vert1_main1() {
-  VertexOutput v_3 = vert1_main1_inner();
-  vert1_main1_outputs v_4 = {v_3.pos};
-  return v_4;
-}
-
-vert2_main1_outputs vert2_main1() {
-  VertexOutput v_5 = vert2_main1_inner();
-  vert2_main1_outputs v_6 = {v_5.pos};
-  return v_6;
-}
-
-)");
-}
-
 TEST_F(HlslWriterTest, FunctionEntryPointWithUniform) {
     // struct Uniforms {
     //   coord: vec4f,
@@ -1003,67 +813,6 @@
 )");
 }
 
-// https://crbug.com/tint/297
-TEST_F(HlslWriterTest, FunctionMultipleEntryPointWithSameModuleVar) {
-    // struct Data {
-    //   d : f32;
-    // };
-    // @binding(0) @group(0) var<storage, read_write> data : Data;
-    //
-    // @compute @workgroup_size(1)
-    // fn a() {
-    //   var v = data.d;
-    //   return;
-    // }
-    //
-    // @compute @workgroup_size(1)
-    // fn b() {
-    //   var v = data.d;
-    //   return;
-    // }
-
-    Vector members{ty.Get<core::type::StructMember>(b.ir.symbols.New("d"), ty.f32(), 0u, 0u, 4u, 4u,
-                                                    core::IOAttributes{})};
-    auto* strct = ty.Struct(b.ir.symbols.New("Data"), std::move(members));
-
-    auto* data = b.Var("data", storage, strct, read_write);
-    data->SetBindingPoint(0, 0);
-    b.ir.root_block->Append(data);
-
-    {
-        auto* func = b.ComputeFunction("a");
-        b.Append(func->Block(), [&] {  //
-            auto* a = b.Access(ty.ptr<storage, f32>(), data, 0_u);
-            b.Var("v", b.Load(a));
-            b.Return(func);
-        });
-    }
-
-    {
-        auto* func = b.ComputeFunction("b");
-        b.Append(func->Block(), [&] {  //
-            auto* a = b.Access(ty.ptr<storage, f32>(), data, 0_u);
-            b.Var("v", b.Load(a));
-            b.Return(func);
-        });
-    }
-
-    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
-    EXPECT_EQ(output_.hlsl, R"(
-RWByteAddressBuffer data : register(u0);
-[numthreads(1, 1, 1)]
-void a() {
-  float v = asfloat(data.Load(0u));
-}
-
-[numthreads(1, 1, 1)]
-void b() {
-  float v = asfloat(data.Load(0u));
-}
-
-)");
-}
-
 TEST_F(HlslWriterTest, DuplicateConstant) {
     auto* ret_arr = b.Function("ret_arr", ty.array<vec4<i32>, 4>());
     b.Append(ret_arr->Block(), [&] { b.Return(ret_arr, b.Zero<array<vec4<i32>, 4>>()); });
diff --git a/src/tint/lang/hlsl/writer/raise/pixel_local_test.cc b/src/tint/lang/hlsl/writer/raise/pixel_local_test.cc
index aefddf0..a462370 100644
--- a/src/tint/lang/hlsl/writer/raise/pixel_local_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/pixel_local_test.cc
@@ -512,172 +512,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(HlslWriterPixelLocalTest, MultipleEntryPoints) {
-    auto* pixel_local_struct_ty =
-        ty.Struct(mod.symbols.New("PixelLocal"), {
-                                                     {mod.symbols.New("a"), ty.u32()},
-                                                 });
-    auto* pl = b.Var("pl", ty.ptr<pixel_local>(pixel_local_struct_ty));
-    mod.root_block->Append(pl);
-
-    Vector<core::type::Manager::StructMemberDesc, 3> members;
-    core::IOAttributes attrs;
-    attrs.builtin = core::BuiltinValue::kPosition;
-    members.Emplace(mod.symbols.New("pos"), ty.vec4<f32>(), attrs);
-    auto* param_struct_ty = ty.Struct(mod.symbols.New("params"), members);
-
-    for (size_t i = 0; i < 3; ++i) {
-        auto* func = b.Function("main" + std::to_string(i), ty.vec4<f32>(),
-                                core::ir::Function::PipelineStage::kFragment);
-        func->SetReturnLocation(0_u);
-        func->SetParams({b.FunctionParam(param_struct_ty)});
-
-        b.Append(func->Block(), [&] {
-            auto* access = b.Access(ty.ptr<pixel_local>(ty.u32()), pl, 0_u);
-            auto* add = b.Add<u32>(b.Load(access), 42_u);
-            b.Store(access, add);
-            b.Return(func, b.Construct<vec4<f32>>(1_f, 0_f, 0_f, 1_f));
-        });
-    }
-
-    auto* src = R"(
-PixelLocal = struct @align(4) {
-  a:u32 @offset(0)
-}
-
-params = struct @align(16) {
-  pos:vec4<f32> @offset(0), @builtin(position)
-}
-
-$B1: {  # root
-  %pl:ptr<pixel_local, PixelLocal, read_write> = var undef
-}
-
-%main0 = @fragment func(%3:params):vec4<f32> [@location(0)] {
-  $B2: {
-    %4:ptr<pixel_local, u32, read_write> = access %pl, 0u
-    %5:u32 = load %4
-    %6:u32 = add %5, 42u
-    store %4, %6
-    %7:vec4<f32> = construct 1.0f, 0.0f, 0.0f, 1.0f
-    ret %7
-  }
-}
-%main1 = @fragment func(%9:params):vec4<f32> [@location(0)] {
-  $B3: {
-    %10:ptr<pixel_local, u32, read_write> = access %pl, 0u
-    %11:u32 = load %10
-    %12:u32 = add %11, 42u
-    store %10, %12
-    %13:vec4<f32> = construct 1.0f, 0.0f, 0.0f, 1.0f
-    ret %13
-  }
-}
-%main2 = @fragment func(%15:params):vec4<f32> [@location(0)] {
-  $B4: {
-    %16:ptr<pixel_local, u32, read_write> = access %pl, 0u
-    %17:u32 = load %16
-    %18:u32 = add %17, 42u
-    store %16, %18
-    %19:vec4<f32> = construct 1.0f, 0.0f, 0.0f, 1.0f
-    ret %19
-  }
-}
-)";
-
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-PixelLocal = struct @align(4) {
-  a:u32 @offset(0)
-}
-
-params = struct @align(16) {
-  pos:vec4<f32> @offset(0), @builtin(position)
-}
-
-$B1: {  # root
-  %pl:ptr<private, PixelLocal, read_write> = var undef
-  %pixel_local_a:ptr<handle, hlsl.rasterizer_ordered_texture_2d<r32uint>, read> = var undef @binding_point(7, 10)
-}
-
-%main0 = @fragment func(%4:params):vec4<f32> [@location(0)] {
-  $B2: {
-    %5:vec4<f32> = access %4, 0u
-    %6:vec2<f32> = swizzle %5, xy
-    %7:vec2<u32> = convert %6
-    %8:hlsl.rasterizer_ordered_texture_2d<r32uint> = load %pixel_local_a
-    %9:vec4<u32> = %8.Load %7
-    %10:u32 = swizzle %9, x
-    %11:ptr<private, u32, read_write> = access %pl, 0u
-    store %11, %10
-    %12:ptr<private, u32, read_write> = access %pl, 0u
-    %13:u32 = load %12
-    %14:u32 = add %13, 42u
-    store %12, %14
-    %15:vec4<f32> = construct 1.0f, 0.0f, 0.0f, 1.0f
-    %16:ptr<private, u32, read_write> = access %pl, 0u
-    %17:u32 = load %16
-    %18:vec4<u32> = construct %17
-    %19:hlsl.rasterizer_ordered_texture_2d<r32uint> = load %pixel_local_a
-    %20:void = hlsl.textureStore %19, %7, %18
-    ret %15
-  }
-}
-%main1 = @fragment func(%22:params):vec4<f32> [@location(0)] {
-  $B3: {
-    %23:vec4<f32> = access %22, 0u
-    %24:vec2<f32> = swizzle %23, xy
-    %25:vec2<u32> = convert %24
-    %26:hlsl.rasterizer_ordered_texture_2d<r32uint> = load %pixel_local_a
-    %27:vec4<u32> = %26.Load %25
-    %28:u32 = swizzle %27, x
-    %29:ptr<private, u32, read_write> = access %pl, 0u
-    store %29, %28
-    %30:ptr<private, u32, read_write> = access %pl, 0u
-    %31:u32 = load %30
-    %32:u32 = add %31, 42u
-    store %30, %32
-    %33:vec4<f32> = construct 1.0f, 0.0f, 0.0f, 1.0f
-    %34:ptr<private, u32, read_write> = access %pl, 0u
-    %35:u32 = load %34
-    %36:vec4<u32> = construct %35
-    %37:hlsl.rasterizer_ordered_texture_2d<r32uint> = load %pixel_local_a
-    %38:void = hlsl.textureStore %37, %25, %36
-    ret %33
-  }
-}
-%main2 = @fragment func(%40:params):vec4<f32> [@location(0)] {
-  $B4: {
-    %41:vec4<f32> = access %40, 0u
-    %42:vec2<f32> = swizzle %41, xy
-    %43:vec2<u32> = convert %42
-    %44:hlsl.rasterizer_ordered_texture_2d<r32uint> = load %pixel_local_a
-    %45:vec4<u32> = %44.Load %43
-    %46:u32 = swizzle %45, x
-    %47:ptr<private, u32, read_write> = access %pl, 0u
-    store %47, %46
-    %48:ptr<private, u32, read_write> = access %pl, 0u
-    %49:u32 = load %48
-    %50:u32 = add %49, 42u
-    store %48, %50
-    %51:vec4<f32> = construct 1.0f, 0.0f, 0.0f, 1.0f
-    %52:ptr<private, u32, read_write> = access %pl, 0u
-    %53:u32 = load %52
-    %54:vec4<u32> = construct %53
-    %55:hlsl.rasterizer_ordered_texture_2d<r32uint> = load %pixel_local_a
-    %56:void = hlsl.textureStore %55, %43, %54
-    ret %51
-  }
-}
-)";
-
-    auto config = OneArgConfig();
-    Run(PixelLocal, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(HlslWriterPixelLocalTest, MultipleMembers) {
     auto r = ThreeArgFunc();
     b.Append(r.func->Block(), [&] {
diff --git a/src/tint/lang/hlsl/writer/raise/shader_io_test.cc b/src/tint/lang/hlsl/writer/raise/shader_io_test.cc
index 9f8e854..f265245 100644
--- a/src/tint/lang/hlsl/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/shader_io_test.cc
@@ -658,146 +658,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(HlslWriterTransformTest, ShaderIOStruct_SharedByVertexAndFragment) {
-    auto* str_ty = ty.Struct(mod.symbols.New("Interface"),
-                             {
-                                 {
-                                     mod.symbols.New("position"),
-                                     ty.vec4<f32>(),
-                                     core::IOAttributes{
-                                         .builtin = core::BuiltinValue::kPosition,
-                                     },
-                                 },
-                                 {
-                                     mod.symbols.New("color"),
-                                     ty.vec3<f32>(),
-                                     core::IOAttributes{
-                                         .location = 0u,
-                                     },
-                                 },
-                             });
-
-    // Vertex shader.
-    {
-        auto* ep = b.Function("vert", str_ty, core::ir::Function::PipelineStage::kVertex);
-
-        b.Append(ep->Block(), [&] {
-            auto* position = b.Construct(ty.vec4<f32>(), 0_f);
-            auto* color = b.Construct(ty.vec3<f32>(), 1_f);
-            b.Return(ep, b.Construct(str_ty, position, color));
-        });
-    }
-
-    // Fragment shader.
-    {
-        auto* inputs = b.FunctionParam("inputs", str_ty);
-
-        auto* ep = b.Function("frag", ty.vec4<f32>(), core::ir::Function::PipelineStage::kFragment);
-        ep->SetParams({inputs});
-        ep->SetReturnLocation(0u);
-
-        b.Append(ep->Block(), [&] {
-            auto* position = b.Access(ty.vec4<f32>(), inputs, 0_u);
-            auto* color = b.Access(ty.vec3<f32>(), inputs, 1_u);
-            b.Return(ep, b.Add(ty.vec4<f32>(), position, b.Construct(ty.vec4<f32>(), color, 1_f)));
-        });
-    }
-
-    auto* src = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0), @builtin(position)
-  color:vec3<f32> @offset(16), @location(0)
-}
-
-%vert = @vertex func():Interface {
-  $B1: {
-    %2:vec4<f32> = construct 0.0f
-    %3:vec3<f32> = construct 1.0f
-    %4:Interface = construct %2, %3
-    ret %4
-  }
-}
-%frag = @fragment func(%inputs:Interface):vec4<f32> [@location(0)] {
-  $B2: {
-    %7:vec4<f32> = access %inputs, 0u
-    %8:vec3<f32> = access %inputs, 1u
-    %9:vec4<f32> = construct %8, 1.0f
-    %10:vec4<f32> = add %7, %9
-    ret %10
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0)
-  color:vec3<f32> @offset(16)
-}
-
-vert_outputs = struct @align(16) {
-  Interface_color:vec3<f32> @offset(0), @location(0)
-  Interface_position:vec4<f32> @offset(16), @builtin(position)
-}
-
-frag_inputs = struct @align(16) {
-  Interface_color:vec3<f32> @offset(0), @location(0)
-  Interface_position:vec4<f32> @offset(16), @builtin(position)
-}
-
-frag_outputs = struct @align(16) {
-  tint_symbol:vec4<f32> @offset(0), @location(0)
-}
-
-%vert_inner = func():Interface {
-  $B1: {
-    %2:vec4<f32> = construct 0.0f
-    %3:vec3<f32> = construct 1.0f
-    %4:Interface = construct %2, %3
-    ret %4
-  }
-}
-%frag_inner = func(%inputs:Interface):vec4<f32> {
-  $B2: {
-    %7:vec4<f32> = access %inputs, 0u
-    %8:vec3<f32> = access %inputs, 1u
-    %9:vec4<f32> = construct %8, 1.0f
-    %10:vec4<f32> = add %7, %9
-    ret %10
-  }
-}
-%vert = @vertex func():vert_outputs {
-  $B3: {
-    %12:Interface = call %vert_inner
-    %13:vec4<f32> = access %12, 0u
-    %14:vec3<f32> = access %12, 1u
-    %15:vert_outputs = construct %14, %13
-    ret %15
-  }
-}
-%frag = @fragment func(%inputs_1:frag_inputs):frag_outputs {  # %inputs_1: 'inputs'
-  $B4: {
-    %18:vec4<f32> = access %inputs_1, 1u
-    %19:f32 = access %18, 3u
-    %20:f32 = div 1.0f, %19
-    %21:vec3<f32> = swizzle %18, xyz
-    %22:vec4<f32> = construct %21, %20
-    %23:vec3<f32> = access %inputs_1, 0u
-    %24:Interface = construct %22, %23
-    %25:vec4<f32> = call %frag_inner, %24
-    %26:frag_outputs = construct %25
-    ret %26
-  }
-}
-)";
-
-    core::ir::transform::ImmediateDataLayout immediate_data;
-    ShaderIOConfig config{immediate_data};
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(HlslWriterTransformTest, ShaderIOStruct_SharedWithBuffer) {
     auto* str_ty =
         ty.Struct(mod.symbols.New("Outputs"), {
@@ -3649,279 +3509,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(HlslWriterTransformTest,
-       ShaderIOParameters_TruncateInterstage_MultipleEntryPointsSharedStruct) {
-    core::IOAttributes pos_attr;
-    pos_attr.builtin = core::BuiltinValue::kPosition;
-
-    core::IOAttributes loc0_attr;
-    loc0_attr.location = 0;
-    core::IOAttributes loc1_attr;
-    loc1_attr.location = 1;
-    core::IOAttributes loc2_attr;
-    loc2_attr.location = 2;
-
-    auto* str_ty = ty.Struct(mod.symbols.New("Outputs"),
-                             {
-                                 {mod.symbols.New("position"), ty.vec4<f32>(), pos_attr},
-                                 {mod.symbols.New("loc0"), ty.f32(), loc0_attr},
-                                 {mod.symbols.New("loc1"), ty.i32(), loc1_attr},
-                                 {mod.symbols.New("loc2"), ty.vec3<i32>(), loc2_attr},
-                             });
-    auto* ep1 = b.Function("foo1", str_ty, core::ir::Function::PipelineStage::kVertex);
-    b.Append(ep1->Block(), [&] {
-        auto* pos = b.Construct(ty.vec4<f32>(), 0.5_f);
-        auto* loc2 = b.Construct(ty.vec3<i32>(), 3_i);
-        b.Return(ep1, b.Construct(str_ty, pos, 1_f, 2_i, loc2));
-    });
-    auto* ep2 = b.Function("foo2", str_ty, core::ir::Function::PipelineStage::kVertex);
-    b.Append(ep2->Block(), [&] {
-        auto* pos = b.Construct(ty.vec4<f32>(), 0.5_f);
-        auto* loc2 = b.Construct(ty.vec3<i32>(), 3_i);
-        b.Return(ep2, b.Construct(str_ty, pos, 1_f, 2_i, loc2));
-    });
-
-    auto* src = R"(
-Outputs = struct @align(16) {
-  position:vec4<f32> @offset(0), @builtin(position)
-  loc0:f32 @offset(16), @location(0)
-  loc1:i32 @offset(20), @location(1)
-  loc2:vec3<i32> @offset(32), @location(2)
-}
-
-%foo1 = @vertex func():Outputs {
-  $B1: {
-    %2:vec4<f32> = construct 0.5f
-    %3:vec3<i32> = construct 3i
-    %4:Outputs = construct %2, 1.0f, 2i, %3
-    ret %4
-  }
-}
-%foo2 = @vertex func():Outputs {
-  $B2: {
-    %6:vec4<f32> = construct 0.5f
-    %7:vec3<i32> = construct 3i
-    %8:Outputs = construct %6, 1.0f, 2i, %7
-    ret %8
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-Outputs = struct @align(16) {
-  position:vec4<f32> @offset(0)
-  loc0:f32 @offset(16)
-  loc1:i32 @offset(20)
-  loc2:vec3<i32> @offset(32)
-}
-
-foo1_outputs = struct @align(16) {
-  Outputs_loc0:f32 @offset(0), @location(0)
-  Outputs_loc2:vec3<i32> @offset(16), @location(2)
-  Outputs_position:vec4<f32> @offset(32), @builtin(position)
-}
-
-foo2_outputs = struct @align(16) {
-  Outputs_loc0:f32 @offset(0), @location(0)
-  Outputs_loc2:vec3<i32> @offset(16), @location(2)
-  Outputs_position:vec4<f32> @offset(32), @builtin(position)
-}
-
-%foo1_inner = func():Outputs {
-  $B1: {
-    %2:vec4<f32> = construct 0.5f
-    %3:vec3<i32> = construct 3i
-    %4:Outputs = construct %2, 1.0f, 2i, %3
-    ret %4
-  }
-}
-%foo2_inner = func():Outputs {
-  $B2: {
-    %6:vec4<f32> = construct 0.5f
-    %7:vec3<i32> = construct 3i
-    %8:Outputs = construct %6, 1.0f, 2i, %7
-    ret %8
-  }
-}
-%foo1 = @vertex func():foo1_outputs {
-  $B3: {
-    %10:Outputs = call %foo1_inner
-    %11:vec4<f32> = access %10, 0u
-    %12:f32 = access %10, 1u
-    %13:i32 = access %10, 2u
-    %14:vec3<i32> = access %10, 3u
-    %15:foo1_outputs = construct %12, %14, %11
-    ret %15
-  }
-}
-%foo2 = @vertex func():foo2_outputs {
-  $B4: {
-    %17:Outputs = call %foo2_inner
-    %18:vec4<f32> = access %17, 0u
-    %19:f32 = access %17, 1u
-    %20:i32 = access %17, 2u
-    %21:vec3<i32> = access %17, 3u
-    %22:foo2_outputs = construct %19, %21, %18
-    ret %22
-  }
-}
-)";
-
-    core::ir::transform::ImmediateDataLayout immediate_data;
-    ShaderIOConfig config{immediate_data};
-    config.truncate_interstage_variables = true;
-    config.interstage_locations[0] = true;
-    config.interstage_locations[2] = true;
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterTransformTest,
-       ShaderIOParameters_TruncateInterstage_MultipleEntryPointsSeparateStruct) {
-    core::IOAttributes pos_attr;
-    pos_attr.builtin = core::BuiltinValue::kPosition;
-
-    core::IOAttributes loc1_attr;
-    loc1_attr.location = 1;
-    core::IOAttributes loc2_attr;
-    loc2_attr.location = 2;
-    core::IOAttributes loc3_attr;
-    loc3_attr.location = 3;
-    core::IOAttributes loc5_attr;
-    loc5_attr.location = 5;
-
-    auto* str_ty1 = ty.Struct(mod.symbols.New("Outputs1"),
-                              {
-                                  {mod.symbols.New("position"), ty.vec4<f32>(), pos_attr},
-                                  {mod.symbols.New("loc1"), ty.f32(), loc1_attr},
-                                  {mod.symbols.New("loc3"), ty.i32(), loc3_attr},
-                                  {mod.symbols.New("loc5"), ty.vec3<i32>(), loc5_attr},
-                              });
-    auto* ep1 = b.Function("foo1", str_ty1, core::ir::Function::PipelineStage::kVertex);
-    b.Append(ep1->Block(), [&] {
-        auto* pos = b.Construct(ty.vec4<f32>(), 0.5_f);
-        auto* loc5 = b.Construct(ty.vec3<i32>(), 3_i);
-        b.Return(ep1, b.Construct(str_ty1, pos, 1_f, 2_i, loc5));
-    });
-
-    auto* str_ty2 = ty.Struct(mod.symbols.New("Outputs2"),
-                              {
-                                  {mod.symbols.New("position"), ty.vec4<f32>(), pos_attr},
-                                  {mod.symbols.New("loc2"), ty.vec3<i32>(), loc2_attr},
-                              });
-
-    auto* ep2 = b.Function("foo2", str_ty2, core::ir::Function::PipelineStage::kVertex);
-    b.Append(ep2->Block(), [&] {
-        auto* pos = b.Construct(ty.vec4<f32>(), 0.5_f);
-        auto* loc2 = b.Construct(ty.vec3<i32>(), 3_i);
-        b.Return(ep2, b.Construct(str_ty2, pos, loc2));
-    });
-
-    auto* src = R"(
-Outputs1 = struct @align(16) {
-  position:vec4<f32> @offset(0), @builtin(position)
-  loc1:f32 @offset(16), @location(1)
-  loc3:i32 @offset(20), @location(3)
-  loc5:vec3<i32> @offset(32), @location(5)
-}
-
-Outputs2 = struct @align(16) {
-  position_1:vec4<f32> @offset(0), @builtin(position)
-  loc2:vec3<i32> @offset(16), @location(2)
-}
-
-%foo1 = @vertex func():Outputs1 {
-  $B1: {
-    %2:vec4<f32> = construct 0.5f
-    %3:vec3<i32> = construct 3i
-    %4:Outputs1 = construct %2, 1.0f, 2i, %3
-    ret %4
-  }
-}
-%foo2 = @vertex func():Outputs2 {
-  $B2: {
-    %6:vec4<f32> = construct 0.5f
-    %7:vec3<i32> = construct 3i
-    %8:Outputs2 = construct %6, %7
-    ret %8
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-Outputs1 = struct @align(16) {
-  position:vec4<f32> @offset(0)
-  loc1:f32 @offset(16)
-  loc3:i32 @offset(20)
-  loc5:vec3<i32> @offset(32)
-}
-
-Outputs2 = struct @align(16) {
-  position_1:vec4<f32> @offset(0)
-  loc2:vec3<i32> @offset(16)
-}
-
-foo1_outputs = struct @align(16) {
-  Outputs1_loc3:i32 @offset(0), @location(3)
-  Outputs1_position:vec4<f32> @offset(16), @builtin(position)
-}
-
-foo2_outputs = struct @align(16) {
-  Outputs2_loc2:vec3<i32> @offset(0), @location(2)
-  Outputs2_position_1:vec4<f32> @offset(16), @builtin(position)
-}
-
-%foo1_inner = func():Outputs1 {
-  $B1: {
-    %2:vec4<f32> = construct 0.5f
-    %3:vec3<i32> = construct 3i
-    %4:Outputs1 = construct %2, 1.0f, 2i, %3
-    ret %4
-  }
-}
-%foo2_inner = func():Outputs2 {
-  $B2: {
-    %6:vec4<f32> = construct 0.5f
-    %7:vec3<i32> = construct 3i
-    %8:Outputs2 = construct %6, %7
-    ret %8
-  }
-}
-%foo1 = @vertex func():foo1_outputs {
-  $B3: {
-    %10:Outputs1 = call %foo1_inner
-    %11:vec4<f32> = access %10, 0u
-    %12:f32 = access %10, 1u
-    %13:i32 = access %10, 2u
-    %14:vec3<i32> = access %10, 3u
-    %15:foo1_outputs = construct %13, %11
-    ret %15
-  }
-}
-%foo2 = @vertex func():foo2_outputs {
-  $B4: {
-    %17:Outputs2 = call %foo2_inner
-    %18:vec4<f32> = access %17, 0u
-    %19:vec3<i32> = access %17, 1u
-    %20:foo2_outputs = construct %19, %18
-    ret %20
-  }
-}
-)";
-
-    core::ir::transform::ImmediateDataLayout immediate_data;
-    ShaderIOConfig config{immediate_data};
-    config.truncate_interstage_variables = true;
-    config.interstage_locations[2] = true;
-    config.interstage_locations[3] = true;
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(HlslWriterTransformTest, ShaderIOParameters_FirstIndexOffset_VertexIndex) {
     auto* vert_idx = b.FunctionParam("vert_idx", ty.u32());
     vert_idx->SetBuiltin(core::BuiltinValue::kVertexIndex);
diff --git a/src/tint/lang/msl/writer/raise/module_scope_vars.cc b/src/tint/lang/msl/writer/raise/module_scope_vars.cc
index 87fe418..e4d709f 100644
--- a/src/tint/lang/msl/writer/raise/module_scope_vars.cc
+++ b/src/tint/lang/msl/writer/raise/module_scope_vars.cc
@@ -77,8 +77,7 @@
         }
 
         // Create the structure to hold all module-scope variables.
-        // This includes all variables declared in the module, even those that are unused by one or
-        // more entry points.
+        // This includes all variables declared in the module, even those that are unused.
         CreateStruct();
 
         // Process functions in reverse-dependency order (i.e. root to leaves).
diff --git a/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc b/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc
index 37cc4f2..f0215bd1 100644
--- a/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc
+++ b/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc
@@ -1326,324 +1326,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(MslWriter_ModuleScopeVarsTest, MultipleEntryPoints) {
-    auto* var_a = b.Var("a", ty.ptr<uniform, i32, core::Access::kRead>());
-    auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
-    auto* var_c = b.Var("c", ty.ptr<private_, i32, core::Access::kReadWrite>());
-    var_a->SetBindingPoint(1, 2);
-    var_b->SetBindingPoint(3, 4);
-    mod.root_block->Append(var_a);
-    mod.root_block->Append(var_b);
-    mod.root_block->Append(var_c);
-
-    auto* main_a = b.Function("main_a", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(main_a->Block(), [&] {
-        auto* load_a = b.Load(var_a);
-        auto* load_b = b.Load(var_b);
-        auto* load_c = b.Load(var_c);
-        b.Store(var_b, b.Add<i32>(load_a, b.Add<i32>(load_b, load_c)));
-        b.Return(main_a);
-    });
-
-    auto* main_b = b.Function("main_b", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(main_b->Block(), [&] {
-        auto* load_a = b.Load(var_a);
-        auto* load_b = b.Load(var_b);
-        auto* load_c = b.Load(var_c);
-        b.Store(var_b, b.Multiply<i32>(load_a, b.Multiply<i32>(load_b, load_c)));
-        b.Return(main_b);
-    });
-
-    auto* src = R"(
-$B1: {  # root
-  %a:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
-  %b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
-  %c:ptr<private, i32, read_write> = var undef
-}
-
-%main_a = @fragment func():void {
-  $B2: {
-    %5:i32 = load %a
-    %6:i32 = load %b
-    %7:i32 = load %c
-    %8:i32 = add %6, %7
-    %9:i32 = add %5, %8
-    store %b, %9
-    ret
-  }
-}
-%main_b = @fragment func():void {
-  $B3: {
-    %11:i32 = load %a
-    %12:i32 = load %b
-    %13:i32 = load %c
-    %14:i32 = mul %12, %13
-    %15:i32 = mul %11, %14
-    store %b, %15
-    ret
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-tint_module_vars_struct = struct @align(1) {
-  a:ptr<uniform, i32, read> @offset(0)
-  b:ptr<storage, i32, read_write> @offset(0)
-  c:ptr<private, i32, read_write> @offset(0)
-}
-
-%main_a = @fragment func(%a:ptr<uniform, i32, read> [@binding_point(1, 2)], %b:ptr<storage, i32, read_write> [@binding_point(3, 4)]):void {
-  $B1: {
-    %c:ptr<private, i32, read_write> = var undef
-    %5:tint_module_vars_struct = construct %a, %b, %c
-    %tint_module_vars:tint_module_vars_struct = let %5
-    %7:ptr<uniform, i32, read> = access %tint_module_vars, 0u
-    %8:i32 = load %7
-    %9:ptr<storage, i32, read_write> = access %tint_module_vars, 1u
-    %10:i32 = load %9
-    %11:ptr<private, i32, read_write> = access %tint_module_vars, 2u
-    %12:i32 = load %11
-    %13:i32 = add %10, %12
-    %14:i32 = add %8, %13
-    %15:ptr<storage, i32, read_write> = access %tint_module_vars, 1u
-    store %15, %14
-    ret
-  }
-}
-%main_b = @fragment func(%a_1:ptr<uniform, i32, read> [@binding_point(1, 2)], %b_1:ptr<storage, i32, read_write> [@binding_point(3, 4)]):void {  # %a_1: 'a', %b_1: 'b'
-  $B2: {
-    %c_1:ptr<private, i32, read_write> = var undef  # %c_1: 'c'
-    %20:tint_module_vars_struct = construct %a_1, %b_1, %c_1
-    %tint_module_vars_1:tint_module_vars_struct = let %20  # %tint_module_vars_1: 'tint_module_vars'
-    %22:ptr<uniform, i32, read> = access %tint_module_vars_1, 0u
-    %23:i32 = load %22
-    %24:ptr<storage, i32, read_write> = access %tint_module_vars_1, 1u
-    %25:i32 = load %24
-    %26:ptr<private, i32, read_write> = access %tint_module_vars_1, 2u
-    %27:i32 = load %26
-    %28:i32 = mul %25, %27
-    %29:i32 = mul %23, %28
-    %30:ptr<storage, i32, read_write> = access %tint_module_vars_1, 1u
-    store %30, %29
-    ret
-  }
-}
-)";
-
-    Run(ModuleScopeVars);
-
-    EXPECT_EQ(expect, str());
-}
-
-TEST_F(MslWriter_ModuleScopeVarsTest, MultipleEntryPoints_DifferentUsageSets) {
-    auto* var_a = b.Var("a", ty.ptr<uniform, i32, core::Access::kRead>());
-    auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
-    auto* var_c = b.Var("c", ty.ptr<private_, i32, core::Access::kReadWrite>());
-    var_a->SetBindingPoint(1, 2);
-    var_b->SetBindingPoint(3, 4);
-    mod.root_block->Append(var_a);
-    mod.root_block->Append(var_b);
-    mod.root_block->Append(var_c);
-
-    auto* main_a = b.Function("main_a", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(main_a->Block(), [&] {
-        auto* load_a = b.Load(var_a);
-        auto* load_b = b.Load(var_b);
-        b.Store(var_b, b.Add<i32>(load_a, load_b));
-        b.Return(main_a);
-    });
-
-    auto* main_b = b.Function("main_b", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(main_b->Block(), [&] {
-        auto* load_a = b.Load(var_a);
-        auto* load_c = b.Load(var_c);
-        b.Store(var_c, b.Multiply<i32>(load_a, load_c));
-        b.Return(main_b);
-    });
-
-    auto* src = R"(
-$B1: {  # root
-  %a:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
-  %b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
-  %c:ptr<private, i32, read_write> = var undef
-}
-
-%main_a = @fragment func():void {
-  $B2: {
-    %5:i32 = load %a
-    %6:i32 = load %b
-    %7:i32 = add %5, %6
-    store %b, %7
-    ret
-  }
-}
-%main_b = @fragment func():void {
-  $B3: {
-    %9:i32 = load %a
-    %10:i32 = load %c
-    %11:i32 = mul %9, %10
-    store %c, %11
-    ret
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-tint_module_vars_struct = struct @align(1) {
-  a:ptr<uniform, i32, read> @offset(0)
-  b:ptr<storage, i32, read_write> @offset(0)
-  c:ptr<private, i32, read_write> @offset(0)
-}
-
-%main_a = @fragment func(%a:ptr<uniform, i32, read> [@binding_point(1, 2)], %b:ptr<storage, i32, read_write> [@binding_point(3, 4)]):void {
-  $B1: {
-    %4:tint_module_vars_struct = construct %a, %b, unused
-    %tint_module_vars:tint_module_vars_struct = let %4
-    %6:ptr<uniform, i32, read> = access %tint_module_vars, 0u
-    %7:i32 = load %6
-    %8:ptr<storage, i32, read_write> = access %tint_module_vars, 1u
-    %9:i32 = load %8
-    %10:i32 = add %7, %9
-    %11:ptr<storage, i32, read_write> = access %tint_module_vars, 1u
-    store %11, %10
-    ret
-  }
-}
-%main_b = @fragment func(%a_1:ptr<uniform, i32, read> [@binding_point(1, 2)]):void {  # %a_1: 'a'
-  $B2: {
-    %c:ptr<private, i32, read_write> = var undef
-    %15:tint_module_vars_struct = construct %a_1, unused, %c
-    %tint_module_vars_1:tint_module_vars_struct = let %15  # %tint_module_vars_1: 'tint_module_vars'
-    %17:ptr<uniform, i32, read> = access %tint_module_vars_1, 0u
-    %18:i32 = load %17
-    %19:ptr<private, i32, read_write> = access %tint_module_vars_1, 2u
-    %20:i32 = load %19
-    %21:i32 = mul %18, %20
-    %22:ptr<private, i32, read_write> = access %tint_module_vars_1, 2u
-    store %22, %21
-    ret
-  }
-}
-)";
-
-    Run(ModuleScopeVars);
-
-    EXPECT_EQ(expect, str());
-}
-
-TEST_F(MslWriter_ModuleScopeVarsTest, MultipleEntryPoints_DifferentUsageSets_CommonHelper) {
-    auto* var_a = b.Var("a", ty.ptr<uniform, i32, core::Access::kRead>());
-    auto* var_b = b.Var("b", ty.ptr<storage, i32, core::Access::kReadWrite>());
-    auto* var_c = b.Var("c", ty.ptr<private_, i32, core::Access::kReadWrite>());
-    var_a->SetBindingPoint(1, 2);
-    var_b->SetBindingPoint(3, 4);
-    mod.root_block->Append(var_a);
-    mod.root_block->Append(var_b);
-    mod.root_block->Append(var_c);
-
-    auto* foo = b.Function("foo", ty.i32());
-    b.Append(foo->Block(), [&] {  //
-        b.Return(foo, b.Load(var_a));
-    });
-
-    auto* main_a = b.Function("main_a", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(main_a->Block(), [&] {
-        auto* load_b = b.Load(var_b);
-        b.Store(var_b, b.Add<i32>(b.Call(foo), load_b));
-        b.Return(main_a);
-    });
-
-    auto* main_b = b.Function("main_b", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(main_b->Block(), [&] {
-        auto* load_c = b.Load(var_c);
-        b.Store(var_c, b.Multiply<i32>(b.Call(foo), load_c));
-        b.Return(main_b);
-    });
-
-    auto* src = R"(
-$B1: {  # root
-  %a:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
-  %b:ptr<storage, i32, read_write> = var undef @binding_point(3, 4)
-  %c:ptr<private, i32, read_write> = var undef
-}
-
-%foo = func():i32 {
-  $B2: {
-    %5:i32 = load %a
-    ret %5
-  }
-}
-%main_a = @fragment func():void {
-  $B3: {
-    %7:i32 = load %b
-    %8:i32 = call %foo
-    %9:i32 = add %8, %7
-    store %b, %9
-    ret
-  }
-}
-%main_b = @fragment func():void {
-  $B4: {
-    %11:i32 = load %c
-    %12:i32 = call %foo
-    %13:i32 = mul %12, %11
-    store %c, %13
-    ret
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-tint_module_vars_struct = struct @align(1) {
-  a:ptr<uniform, i32, read> @offset(0)
-  b:ptr<storage, i32, read_write> @offset(0)
-  c:ptr<private, i32, read_write> @offset(0)
-}
-
-%foo = func(%tint_module_vars:tint_module_vars_struct):i32 {
-  $B1: {
-    %3:ptr<uniform, i32, read> = access %tint_module_vars, 0u
-    %4:i32 = load %3
-    ret %4
-  }
-}
-%main_a = @fragment func(%a:ptr<uniform, i32, read> [@binding_point(1, 2)], %b:ptr<storage, i32, read_write> [@binding_point(3, 4)]):void {
-  $B2: {
-    %8:tint_module_vars_struct = construct %a, %b, unused
-    %tint_module_vars_1:tint_module_vars_struct = let %8  # %tint_module_vars_1: 'tint_module_vars'
-    %10:ptr<storage, i32, read_write> = access %tint_module_vars_1, 1u
-    %11:i32 = load %10
-    %12:i32 = call %foo, %tint_module_vars_1
-    %13:i32 = add %12, %11
-    %14:ptr<storage, i32, read_write> = access %tint_module_vars_1, 1u
-    store %14, %13
-    ret
-  }
-}
-%main_b = @fragment func(%a_1:ptr<uniform, i32, read> [@binding_point(1, 2)]):void {  # %a_1: 'a'
-  $B3: {
-    %c:ptr<private, i32, read_write> = var undef
-    %18:tint_module_vars_struct = construct %a_1, unused, %c
-    %tint_module_vars_2:tint_module_vars_struct = let %18  # %tint_module_vars_2: 'tint_module_vars'
-    %20:ptr<private, i32, read_write> = access %tint_module_vars_2, 2u
-    %21:i32 = load %20
-    %22:i32 = call %foo, %tint_module_vars_2
-    %23:i32 = mul %22, %21
-    %24:ptr<private, i32, read_write> = access %tint_module_vars_2, 2u
-    store %24, %23
-    ret
-  }
-}
-)";
-
-    Run(ModuleScopeVars);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(MslWriter_ModuleScopeVarsTest, VarsWithNoNames) {
     auto* var_a = b.Var(ty.ptr<uniform, i32, core::Access::kRead>());
     auto* var_b = b.Var(ty.ptr<storage, i32, core::Access::kReadWrite>());
diff --git a/src/tint/lang/msl/writer/raise/shader_io.cc b/src/tint/lang/msl/writer/raise/shader_io.cc
index 8c53bfd..1e18776 100644
--- a/src/tint/lang/msl/writer/raise/shader_io.cc
+++ b/src/tint/lang/msl/writer/raise/shader_io.cc
@@ -41,12 +41,6 @@
 
 namespace {
 
-/// State that persists across the whole module and can be shared between entry points.
-struct PerModuleState {
-    /// The frag_depth clamp arguments.
-    core::ir::Value* frag_depth_clamp_args = nullptr;
-};
-
 /// PIMPL state for the parts of the shader IO transform specific to MSL.
 /// For MSL, we take builtin inputs as entry point parameters, move non-builtin inputs to a struct
 /// passed as an entry point parameter, and wrap outputs in a structure returned by the entry point.
@@ -54,9 +48,6 @@
     /// The configuration options.
     const ShaderIOConfig& config;
 
-    /// The per-module state object.
-    PerModuleState& module_state;
-
     /// The input parameters of the entry point.
     Vector<core::ir::FunctionParam*, 4> input_params;
 
@@ -77,11 +68,8 @@
     std::optional<uint32_t> fixed_sample_mask_index;
 
     /// Constructor
-    StateImpl(core::ir::Module& mod,
-              core::ir::Function* f,
-              const ShaderIOConfig& cfg,
-              PerModuleState& mod_state)
-        : ShaderIOBackendState(mod, f), config(cfg), module_state(mod_state) {}
+    StateImpl(core::ir::Module& mod, core::ir::Function* f, const ShaderIOConfig& cfg)
+        : ShaderIOBackendState(mod, f), config(cfg) {}
 
     /// Destructor
     ~StateImpl() override {}
@@ -242,9 +230,8 @@
         return result;
     }
 
-    PerModuleState module_state;
     core::ir::transform::RunShaderIOBase(ir, [&](core::ir::Module& mod, core::ir::Function* func) {
-        return std::make_unique<StateImpl>(mod, func, config, module_state);
+        return std::make_unique<StateImpl>(mod, func, config);
     });
 
     return Success;
diff --git a/src/tint/lang/msl/writer/raise/shader_io_test.cc b/src/tint/lang/msl/writer/raise/shader_io_test.cc
index 74e80d3..97adaf9 100644
--- a/src/tint/lang/msl/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/msl/writer/raise/shader_io_test.cc
@@ -655,147 +655,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(MslWriter_ShaderIOTest, Struct_SharedByVertexAndFragment) {
-    auto* vec4f = ty.vec4<f32>();
-    auto* str_ty = ty.Struct(mod.symbols.New("Interface"),
-                             {
-                                 {
-                                     mod.symbols.New("position"),
-                                     vec4f,
-                                     core::IOAttributes{
-                                         .builtin = core::BuiltinValue::kPosition,
-                                     },
-                                 },
-                                 {
-                                     mod.symbols.New("color"),
-                                     vec4f,
-                                     core::IOAttributes{
-                                         .location = 0u,
-                                     },
-                                 },
-                             });
-
-    // Vertex shader.
-    {
-        auto* ep = b.Function("vert", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kVertex);
-
-        b.Append(ep->Block(), [&] {  //
-            auto* position = b.Construct(vec4f, 0_f);
-            auto* color = b.Construct(vec4f, 1_f);
-            b.Return(ep, b.Construct(str_ty, position, color));
-        });
-    }
-
-    // Fragment shader.
-    {
-        auto* ep = b.Function("frag", vec4f);
-        auto* inputs = b.FunctionParam("inputs", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-        ep->SetParams({inputs});
-        ep->SetReturnLocation(0u);
-
-        b.Append(ep->Block(), [&] {  //
-            auto* position = b.Access(vec4f, inputs, 0_u);
-            auto* color = b.Access(vec4f, inputs, 1_u);
-            b.Return(ep, b.Add(vec4f, position, color));
-        });
-    }
-
-    auto* src = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0), @builtin(position)
-  color:vec4<f32> @offset(16), @location(0)
-}
-
-%vert = @vertex func():Interface {
-  $B1: {
-    %2:vec4<f32> = construct 0.0f
-    %3:vec4<f32> = construct 1.0f
-    %4:Interface = construct %2, %3
-    ret %4
-  }
-}
-%frag = @fragment func(%inputs:Interface):vec4<f32> [@location(0)] {
-  $B2: {
-    %7:vec4<f32> = access %inputs, 0u
-    %8:vec4<f32> = access %inputs, 1u
-    %9:vec4<f32> = add %7, %8
-    ret %9
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0)
-  color:vec4<f32> @offset(16)
-}
-
-vert_outputs = struct @align(16) {
-  Interface_position:vec4<f32> @offset(0), @builtin(position)
-  Interface_color:vec4<f32> @offset(16), @location(0)
-}
-
-frag_inputs = struct @align(16) {
-  Interface_color:vec4<f32> @offset(0), @location(0)
-}
-
-frag_outputs = struct @align(16) {
-  tint_symbol:vec4<f32> @offset(0), @location(0)
-}
-
-%vert_inner = func():Interface {
-  $B1: {
-    %2:vec4<f32> = construct 0.0f
-    %3:vec4<f32> = construct 1.0f
-    %4:Interface = construct %2, %3
-    ret %4
-  }
-}
-%frag_inner = func(%inputs:Interface):vec4<f32> {
-  $B2: {
-    %7:vec4<f32> = access %inputs, 0u
-    %8:vec4<f32> = access %inputs, 1u
-    %9:vec4<f32> = add %7, %8
-    ret %9
-  }
-}
-%vert = @vertex func():vert_outputs {
-  $B3: {
-    %11:Interface = call %vert_inner
-    %12:vec4<f32> = access %11, 0u
-    %13:vec4<f32> = access %11, 1u
-    %tint_wrapper_result:ptr<function, vert_outputs, read_write> = var undef
-    %15:ptr<function, vec4<f32>, read_write> = access %tint_wrapper_result, 0u
-    store %15, %12
-    %16:ptr<function, vec4<f32>, read_write> = access %tint_wrapper_result, 1u
-    store %16, %13
-    %17:vert_outputs = load %tint_wrapper_result
-    ret %17
-  }
-}
-%frag = @fragment func(%Interface_position:vec4<f32> [@position], %inputs_1:frag_inputs):frag_outputs {  # %inputs_1: 'inputs'
-  $B4: {
-    %21:vec4<f32> = access %inputs_1, 0u
-    %22:Interface = construct %Interface_position, %21
-    %23:vec4<f32> = call %frag_inner, %22
-    %tint_wrapper_result_1:ptr<function, frag_outputs, read_write> = var undef  # %tint_wrapper_result_1: 'tint_wrapper_result'
-    %25:ptr<function, vec4<f32>, read_write> = access %tint_wrapper_result_1, 0u
-    store %25, %23
-    %26:frag_outputs = load %tint_wrapper_result_1
-    ret %26
-  }
-}
-)";
-
-    ShaderIOConfig config;
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(MslWriter_ShaderIOTest, Struct_SharedWithBuffer) {
     auto* vec4f = ty.vec4<f32>();
     auto* str_ty =
diff --git a/src/tint/lang/msl/writer/raise/simd_ballot_test.cc b/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
index 848dd7b..634be7f 100644
--- a/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
+++ b/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
@@ -159,19 +159,10 @@
         b.Return(foo, result);
     });
 
-    auto* ep1 = b.Function("ep1", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    auto* subgroup_size = b.FunctionParam("user_subgroup_size", ty.u32());
-    subgroup_size->SetLocation(0);
-    ep1->SetParams({subgroup_size});
-    b.Append(ep1->Block(), [&] {  //
-        b.Call<vec4<u32>>(foo, true);
-        b.Return(ep1);
-    });
-
-    auto* ep2 = b.Function("ep2", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(ep2->Block(), [&] {  //
+    auto* ep = b.Function("ep", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(ep->Block(), [&] {  //
         b.Call<vec4<u32>>(foo, false);
-        b.Return(ep2);
+        b.Return(ep);
     });
 
     auto* src = R"(
@@ -181,15 +172,9 @@
     ret %3
   }
 }
-%ep1 = @fragment func(%user_subgroup_size:u32 [@location(0)]):void {
+%ep = @fragment func():void {
   $B2: {
-    %6:vec4<u32> = call %foo, true
-    ret
-  }
-}
-%ep2 = @fragment func():void {
-  $B3: {
-    %8:vec4<u32> = call %foo, false
+    %5:vec4<u32> = call %foo, false
     ret
   }
 }
@@ -207,43 +192,28 @@
     ret %4
   }
 }
-%ep1 = @fragment func(%user_subgroup_size:u32 [@location(0)], %tint_subgroup_size:u32 [@subgroup_size]):void {
+%ep = @fragment func(%tint_subgroup_size:u32 [@subgroup_size]):void {
   $B3: {
-    %9:bool = gt %tint_subgroup_size, 32u
-    %10:u32 = sub 32u, %tint_subgroup_size
-    %11:u32 = shr 4294967295u, %10
-    %12:u32 = select %11, 4294967295u, %9
-    %13:u32 = sub 64u, %tint_subgroup_size
-    %14:u32 = shr 4294967295u, %13
-    %15:u32 = select 0u, %14, %9
-    store_vector_element %tint_subgroup_size_mask, 0u, %12
-    store_vector_element %tint_subgroup_size_mask, 1u, %15
-    %16:vec4<u32> = call %foo, true
-    ret
-  }
-}
-%ep2 = @fragment func(%tint_subgroup_size_1:u32 [@subgroup_size]):void {  # %tint_subgroup_size_1: 'tint_subgroup_size'
-  $B4: {
-    %19:bool = gt %tint_subgroup_size_1, 32u
-    %20:u32 = sub 32u, %tint_subgroup_size_1
-    %21:u32 = shr 4294967295u, %20
-    %22:u32 = select %21, 4294967295u, %19
-    %23:u32 = sub 64u, %tint_subgroup_size_1
-    %24:u32 = shr 4294967295u, %23
-    %25:u32 = select 0u, %24, %19
-    store_vector_element %tint_subgroup_size_mask, 0u, %22
-    store_vector_element %tint_subgroup_size_mask, 1u, %25
-    %26:vec4<u32> = call %foo, false
+    %8:bool = gt %tint_subgroup_size, 32u
+    %9:u32 = sub 32u, %tint_subgroup_size
+    %10:u32 = shr 4294967295u, %9
+    %11:u32 = select %10, 4294967295u, %8
+    %12:u32 = sub 64u, %tint_subgroup_size
+    %13:u32 = shr 4294967295u, %12
+    %14:u32 = select 0u, %13, %8
+    store_vector_element %tint_subgroup_size_mask, 0u, %11
+    store_vector_element %tint_subgroup_size_mask, 1u, %14
+    %15:vec4<u32> = call %foo, false
     ret
   }
 }
 %tint_subgroup_ballot = func(%pred_1:bool):vec4<u32> {  # %pred_1: 'pred'
-  $B5: {
-    %28:vec2<u32> = msl.simd_ballot %pred_1
-    %29:vec2<u32> = load %tint_subgroup_size_mask
-    %30:vec2<u32> = and %28, %29
-    %31:vec4<u32> = construct %30, 0u, 0u
-    ret %31
+  $B4: {
+    %17:vec2<u32> = msl.simd_ballot %pred_1
+    %18:vec2<u32> = load %tint_subgroup_size_mask
+    %19:vec2<u32> = and %17, %18
+    %20:vec4<u32> = construct %19, 0u, 0u
+    ret %20
   }
 }
 )";
diff --git a/src/tint/lang/msl/writer/writer_test.cc b/src/tint/lang/msl/writer/writer_test.cc
index 1cb9e27..895d98f 100644
--- a/src/tint/lang/msl/writer/writer_test.cc
+++ b/src/tint/lang/msl/writer/writer_test.cc
@@ -35,6 +35,29 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
+TEST_F(MslWriterTest, WorkgroupAllocations_NoAllocations) {
+    auto* var_a = b.Var("a", ty.ptr<workgroup, i32>());
+    auto* var_b = b.Var("b", ty.ptr<workgroup, i32>());
+    mod.root_block->Append(var_a);
+    mod.root_block->Append(var_b);
+
+    // No allocations, but still needs an entry in the map.
+    auto* bar = b.ComputeFunction("bar");
+    b.Append(bar->Block(), [&] { b.Return(bar); });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.msl;
+    EXPECT_EQ(output_.msl, R"(#include <metal_stdlib>
+using namespace metal;
+
+kernel void bar() {
+}
+)");
+
+    ASSERT_EQ(output_.workgroup_info.allocations.size(), 1u);
+    ASSERT_EQ(output_.workgroup_info.allocations.count("bar"), 1u);
+    EXPECT_THAT(output_.workgroup_info.allocations.at("bar"), testing::ElementsAre());
+}
+
 TEST_F(MslWriterTest, WorkgroupAllocations) {
     auto* var_a = b.Var("a", ty.ptr<workgroup, i32>());
     auto* var_b = b.Var("b", ty.ptr<workgroup, i32>());
@@ -49,10 +72,6 @@
         b.Return(foo);
     });
 
-    // No allocations, but still needs an entry in the map.
-    auto* bar = b.ComputeFunction("bar");
-    b.Append(bar->Block(), [&] { b.Return(bar); });
-
     ASSERT_TRUE(Generate()) << err_ << output_.msl;
     EXPECT_EQ(output_.msl, R"(#include <metal_stdlib>
 using namespace metal;
@@ -76,19 +95,14 @@
   (*tint_module_vars.a) = as_type<int>((as_type<uint>((*tint_module_vars.a)) + as_type<uint>((*tint_module_vars.b))));
 }
 
-kernel void bar() {
-}
-
 kernel void foo(uint tint_local_index [[thread_index_in_threadgroup]], threadgroup tint_symbol_2* v [[threadgroup(0)]]) {
   tint_module_vars_struct const tint_module_vars = tint_module_vars_struct{.a=(&(*v).tint_symbol), .b=(&(*v).tint_symbol_1)};
   foo_inner(tint_local_index, tint_module_vars);
 }
 )");
-    ASSERT_EQ(output_.workgroup_info.allocations.size(), 2u);
+    ASSERT_EQ(output_.workgroup_info.allocations.size(), 1u);
     ASSERT_EQ(output_.workgroup_info.allocations.count("foo"), 1u);
-    ASSERT_EQ(output_.workgroup_info.allocations.count("bar"), 1u);
     EXPECT_THAT(output_.workgroup_info.allocations.at("foo"), testing::ElementsAre(8u));
-    EXPECT_THAT(output_.workgroup_info.allocations.at("bar"), testing::ElementsAre());
 }
 
 TEST_F(MslWriterTest, NeedsStorageBufferSizes_False) {
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.cc b/src/tint/lang/spirv/reader/lower/shader_io.cc
index 272d800..84382b4 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io.cc
@@ -466,6 +466,7 @@
 Result<SuccessType> ShaderIO(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "spirv.ShaderIO",
                                           core::ir::Capabilities{
+                                              core::ir::Capability::kAllowMultipleEntryPoints,
                                               core::ir::Capability::kAllowOverrides,
                                           });
     if (result != Success) {
diff --git a/src/tint/lang/spirv/reader/lower/shader_io_test.cc b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
index a427870..5471411 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io_test.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
@@ -38,6 +38,9 @@
 using namespace tint::core::number_suffixes;  // NOLINT
 
 class SpirvReader_ShaderIOTest : public core::ir::transform::TransformTest {
+  public:
+    void SetUp() override { capabilities.Add(core::ir::Capability::kAllowMultipleEntryPoints); }
+
   protected:
     core::IOAttributes BuiltinAttrs(core::BuiltinValue builtin) {
         core::IOAttributes attrs;
diff --git a/src/tint/lang/spirv/reader/parser/helper_test.h b/src/tint/lang/spirv/reader/parser/helper_test.h
index b4f2577..5a0959d 100644
--- a/src/tint/lang/spirv/reader/parser/helper_test.h
+++ b/src/tint/lang/spirv/reader/parser/helper_test.h
@@ -74,6 +74,7 @@
         // Validate the IR module against the capabilities supported by the SPIR-V dialect.
         auto validated =
             core::ir::Validate(parsed.Get(), core::ir::Capabilities{
+                                                 core::ir::Capability::kAllowMultipleEntryPoints,
                                                  core::ir::Capability::kAllowOverrides,
                                                  core::ir::Capability::kAllowVectorElementPointer,
                                              });
diff --git a/src/tint/lang/spirv/writer/function_test.cc b/src/tint/lang/spirv/writer/function_test.cc
index f1d780f..8a9deae 100644
--- a/src/tint/lang/spirv/writer/function_test.cc
+++ b/src/tint/lang/spirv/writer/function_test.cc
@@ -205,60 +205,6 @@
     EXPECT_EQ(workgroup_info.z, 0u);
 }
 
-TEST_F(SpirvWriterTest, Function_EntryPoint_Multiple) {
-    auto* f1 = b.ComputeFunction("main1", 32_u, 4_u, 1_u);
-    b.Append(f1->Block(), [&] {  //
-        b.Return(f1);
-    });
-
-    auto* f2 = b.ComputeFunction("main2", 8_u, 2_u, 16_u);
-    b.Append(f2->Block(), [&] {  //
-        b.Return(f2);
-    });
-
-    auto* f3 = b.Function("main3", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    b.Append(f3->Block(), [&] {  //
-        b.Return(f3);
-    });
-
-    ASSERT_TRUE(Generate()) << Error() << output_;
-    EXPECT_INST(R"(
-               OpEntryPoint GLCompute %main1 "main1"
-               OpEntryPoint GLCompute %main2 "main2"
-               OpEntryPoint Fragment %main3 "main3"
-               OpExecutionMode %main1 LocalSize 32 4 1
-               OpExecutionMode %main2 LocalSize 8 2 16
-               OpExecutionMode %main3 OriginUpperLeft
-
-               ; Debug Information
-               OpName %main1 "main1"                ; id %1
-               OpName %main2 "main2"                ; id %5
-               OpName %main3 "main3"                ; id %7
-
-               ; Types, variables and constants
-       %void = OpTypeVoid
-          %3 = OpTypeFunction %void
-
-               ; Function main1
-      %main1 = OpFunction %void None %3
-          %4 = OpLabel
-               OpReturn
-               OpFunctionEnd
-
-               ; Function main2
-      %main2 = OpFunction %void None %3
-          %6 = OpLabel
-               OpReturn
-               OpFunctionEnd
-
-               ; Function main3
-      %main3 = OpFunction %void None %3
-          %8 = OpLabel
-               OpReturn
-               OpFunctionEnd
-)");
-}
-
 TEST_F(SpirvWriterTest, Function_ReturnValue) {
     auto* func = b.Function("foo", ty.i32());
     b.Append(func->Block(), [&] {  //
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index e12dfb9..0732fd5 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -42,12 +42,6 @@
 
 namespace {
 
-/// State that persists across the whole module and can be shared between entry points.
-struct PerModuleState {
-    /// The frag_depth clamp arguments.
-    core::ir::Value* frag_depth_clamp_args = nullptr;
-};
-
 /// PIMPL state for the parts of the shader IO transform specific to SPIR-V.
 /// For SPIR-V, we declare a global variable for each input and output. The wrapper entry point then
 /// loads from and stores to these variables. We also modify the type of the SampleMask builtin to
@@ -61,15 +55,9 @@
     /// The configuration options.
     const ShaderIOConfig& config;
 
-    /// The per-module state object.
-    PerModuleState& module_state;
-
     /// Constructor
-    StateImpl(core::ir::Module& mod,
-              core::ir::Function* f,
-              const ShaderIOConfig& cfg,
-              PerModuleState& mod_state)
-        : ShaderIOBackendState(mod, f), config(cfg), module_state(mod_state) {}
+    StateImpl(core::ir::Module& mod, core::ir::Function* f, const ShaderIOConfig& cfg)
+        : ShaderIOBackendState(mod, f), config(cfg) {}
 
     /// Destructor
     ~StateImpl() override {}
@@ -212,9 +200,8 @@
         return result;
     }
 
-    PerModuleState module_state;
     core::ir::transform::RunShaderIOBase(ir, [&](core::ir::Module& mod, core::ir::Function* func) {
-        return std::make_unique<StateImpl>(mod, func, config, module_state);
+        return std::make_unique<StateImpl>(mod, func, config);
     });
 
     return Success;
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 5acc03a..9d9126a 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
@@ -660,137 +660,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(SpirvWriter_ShaderIOTest, Struct_SharedByVertexAndFragment) {
-    auto* vec4f = ty.vec4<f32>();
-    auto* str_ty = ty.Struct(mod.symbols.New("Interface"),
-                             {
-                                 {
-                                     mod.symbols.New("position"),
-                                     vec4f,
-                                     core::IOAttributes{
-                                         .builtin = core::BuiltinValue::kPosition,
-                                     },
-                                 },
-                                 {
-                                     mod.symbols.New("color"),
-                                     vec4f,
-                                     core::IOAttributes{
-                                         .location = 0u,
-                                     },
-                                 },
-                             });
-
-    // Vertex shader.
-    {
-        auto* ep = b.Function("vert", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kVertex);
-
-        b.Append(ep->Block(), [&] {  //
-            auto* position = b.Construct(vec4f, 0_f);
-            auto* color = b.Construct(vec4f, 1_f);
-            b.Return(ep, b.Construct(str_ty, position, color));
-        });
-    }
-
-    // Fragment shader.
-    {
-        auto* ep = b.Function("frag", vec4f);
-        auto* inputs = b.FunctionParam("inputs", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-        ep->SetParams({inputs});
-        ep->SetReturnLocation(0u);
-
-        b.Append(ep->Block(), [&] {  //
-            auto* position = b.Access(vec4f, inputs, 0_u);
-            auto* color = b.Access(vec4f, inputs, 1_u);
-            b.Return(ep, b.Add(vec4f, position, color));
-        });
-    }
-
-    auto* src = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0), @builtin(position)
-  color:vec4<f32> @offset(16), @location(0)
-}
-
-%vert = @vertex func():Interface {
-  $B1: {
-    %2:vec4<f32> = construct 0.0f
-    %3:vec4<f32> = construct 1.0f
-    %4:Interface = construct %2, %3
-    ret %4
-  }
-}
-%frag = @fragment func(%inputs:Interface):vec4<f32> [@location(0)] {
-  $B2: {
-    %7:vec4<f32> = access %inputs, 0u
-    %8:vec4<f32> = access %inputs, 1u
-    %9:vec4<f32> = add %7, %8
-    ret %9
-  }
-}
-)";
-    EXPECT_EQ(src, str());
-
-    auto* expect = R"(
-Interface = struct @align(16) {
-  position:vec4<f32> @offset(0)
-  color:vec4<f32> @offset(16)
-}
-
-$B1: {  # root
-  %vert_position_Output:ptr<__out, vec4<f32>, write> = var undef @builtin(position)
-  %vert_loc0_Output:ptr<__out, vec4<f32>, write> = var undef @location(0)
-  %frag_position_Input:ptr<__in, vec4<f32>, read> = var undef @builtin(position)
-  %frag_loc0_Input:ptr<__in, vec4<f32>, read> = var undef @location(0)
-  %frag_loc0_Output:ptr<__out, vec4<f32>, write> = var undef @location(0)
-}
-
-%vert_inner = func():Interface {
-  $B2: {
-    %7:vec4<f32> = construct 0.0f
-    %8:vec4<f32> = construct 1.0f
-    %9:Interface = construct %7, %8
-    ret %9
-  }
-}
-%frag_inner = func(%inputs:Interface):vec4<f32> {
-  $B3: {
-    %12:vec4<f32> = access %inputs, 0u
-    %13:vec4<f32> = access %inputs, 1u
-    %14:vec4<f32> = add %12, %13
-    ret %14
-  }
-}
-%vert = @vertex func():void {
-  $B4: {
-    %16:Interface = call %vert_inner
-    %17:vec4<f32> = access %16, 0u
-    store %vert_position_Output, %17
-    %18:vec4<f32> = access %16, 1u
-    store %vert_loc0_Output, %18
-    ret
-  }
-}
-%frag = @fragment func():void {
-  $B5: {
-    %20:vec4<f32> = load %frag_position_Input
-    %21:vec4<f32> = load %frag_loc0_Input
-    %22:Interface = construct %20, %21
-    %23:vec4<f32> = call %frag_inner, %22
-    store %frag_loc0_Output, %23
-    ret
-  }
-}
-)";
-
-    core::ir::transform::ImmediateDataLayout immediate_data;
-    ShaderIOConfig config{immediate_data};
-    Run(ShaderIO, config);
-
-    EXPECT_EQ(expect, str());
-}
-
 TEST_F(SpirvWriter_ShaderIOTest, Struct_SharedWithBuffer) {
     auto* vec4f = ty.vec4<f32>();
     auto* str_ty =
@@ -964,8 +833,8 @@
     EXPECT_EQ(expect, str());
 }
 
-// Test that interpolation attributes are stripped from vertex inputs and fragment outputs.
-TEST_F(SpirvWriter_ShaderIOTest, InterpolationOnVertexInputOrFragmentOutput) {
+// Test that interpolation attributes are stripped from vertex inputs.
+TEST_F(SpirvWriter_ShaderIOTest, InterpolationOnVertexInput) {
     auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"),
                              {
                                  {
@@ -982,45 +851,20 @@
                                  },
                              });
 
-    // Vertex shader.
-    {
-        auto* ep = b.Function("vert", ty.vec4<f32>());
-        ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
-        ep->SetReturnInvariant(true);
-        ep->SetStage(core::ir::Function::PipelineStage::kVertex);
+    auto* ep = b.Function("vert", ty.vec4<f32>());
+    ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
+    ep->SetReturnInvariant(true);
+    ep->SetStage(core::ir::Function::PipelineStage::kVertex);
 
-        auto* str_param = b.FunctionParam("input", str_ty);
-        auto* ival = b.FunctionParam("ival", ty.i32());
-        ival->SetLocation(1);
-        ival->SetInterpolation(core::Interpolation{core::InterpolationType::kFlat});
-        ep->SetParams({str_param, ival});
+    auto* str_param = b.FunctionParam("input", str_ty);
+    auto* ival = b.FunctionParam("ival", ty.i32());
+    ival->SetLocation(1);
+    ival->SetInterpolation(core::Interpolation{core::InterpolationType::kFlat});
+    ep->SetParams({str_param, ival});
 
-        b.Append(ep->Block(), [&] {  //
-            b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f));
-        });
-    }
-
-    // Fragment shader with struct output.
-    {
-        auto* ep = b.Function("frag1", str_ty);
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-
-        b.Append(ep->Block(), [&] {  //
-            b.Return(ep, b.Construct(str_ty, 0.5_f));
-        });
-    }
-
-    // Fragment shader with non-struct output.
-    {
-        auto* ep = b.Function("frag2", ty.i32());
-        ep->SetStage(core::ir::Function::PipelineStage::kFragment);
-        ep->SetReturnLocation(0);
-        ep->SetReturnInterpolation(core::Interpolation{core::InterpolationType::kFlat});
-
-        b.Append(ep->Block(), [&] {  //
-            b.Return(ep, b.Constant(42_i));
-        });
-    }
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f));
+    });
 
     auto* src = R"(
 MyStruct = struct @align(4) {
@@ -1033,17 +877,6 @@
     ret %4
   }
 }
-%frag1 = @fragment func():MyStruct {
-  $B2: {
-    %6:MyStruct = construct 0.5f
-    ret %6
-  }
-}
-%frag2 = @fragment func():i32 [@location(0), @interpolate(flat)] {
-  $B3: {
-    ret 42i
-  }
-}
 )";
     EXPECT_EQ(src, str());
 
@@ -1056,49 +889,138 @@
   %vert_loc1_Input:ptr<__in, f32, read> = var undef @location(1)
   %vert_loc1_Input_1:ptr<__in, i32, read> = var undef @location(1)  # %vert_loc1_Input_1: 'vert_loc1_Input'
   %vert_position_Output:ptr<__out, vec4<f32>, write> = var undef @invariant @builtin(position)
-  %frag1_loc1_Output:ptr<__out, f32, write> = var undef @location(1)
-  %frag2_loc0_Output:ptr<__out, i32, write> = var undef @location(0)
 }
 
 %vert_inner = func(%input:MyStruct, %ival:i32):vec4<f32> {
   $B2: {
-    %9:vec4<f32> = construct 0.5f
-    ret %9
-  }
-}
-%frag1_inner = func():MyStruct {
-  $B3: {
-    %11:MyStruct = construct 0.5f
-    ret %11
-  }
-}
-%frag2_inner = func():i32 {
-  $B4: {
-    ret 42i
+    %7:vec4<f32> = construct 0.5f
+    ret %7
   }
 }
 %vert = @vertex func():void {
-  $B5: {
-    %14:f32 = load %vert_loc1_Input
-    %15:MyStruct = construct %14
-    %16:i32 = load %vert_loc1_Input_1
-    %17:vec4<f32> = call %vert_inner, %15, %16
-    store %vert_position_Output, %17
+  $B3: {
+    %9:f32 = load %vert_loc1_Input
+    %10:MyStruct = construct %9
+    %11:i32 = load %vert_loc1_Input_1
+    %12:vec4<f32> = call %vert_inner, %10, %11
+    store %vert_position_Output, %12
     ret
   }
 }
+)";
+
+    core::ir::transform::ImmediateDataLayout immediate_data;
+    ShaderIOConfig config{immediate_data};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Test that interpolation attributes are stripped from fragment struct outputs.
+TEST_F(SpirvWriter_ShaderIOTest, InterpolationOnFragmentOutput_Struct) {
+    auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"),
+                             {
+                                 {
+                                     mod.symbols.New("color"),
+                                     ty.f32(),
+                                     core::IOAttributes{
+                                         .location = 1u,
+                                         .interpolation =
+                                             core::Interpolation{
+                                                 core::InterpolationType::kLinear,
+                                                 core::InterpolationSampling::kSample,
+                                             },
+                                     },
+                                 },
+                             });
+
+    auto* ep = b.Function("frag1", str_ty);
+    ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Construct(str_ty, 0.5_f));
+    });
+
+    auto* src = R"(
+MyStruct = struct @align(4) {
+  color:f32 @offset(0), @location(1), @interpolate(linear, sample)
+}
+
+%frag1 = @fragment func():MyStruct {
+  $B1: {
+    %2:MyStruct = construct 0.5f
+    ret %2
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+MyStruct = struct @align(4) {
+  color:f32 @offset(0)
+}
+
+$B1: {  # root
+  %frag1_loc1_Output:ptr<__out, f32, write> = var undef @location(1)
+}
+
+%frag1_inner = func():MyStruct {
+  $B2: {
+    %3:MyStruct = construct 0.5f
+    ret %3
+  }
+}
 %frag1 = @fragment func():void {
-  $B6: {
-    %19:MyStruct = call %frag1_inner
-    %20:f32 = access %19, 0u
-    store %frag1_loc1_Output, %20
+  $B3: {
+    %5:MyStruct = call %frag1_inner
+    %6:f32 = access %5, 0u
+    store %frag1_loc1_Output, %6
     ret
   }
 }
+)";
+
+    core::ir::transform::ImmediateDataLayout immediate_data;
+    ShaderIOConfig config{immediate_data};
+    Run(ShaderIO, config);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Test that interpolation attributes are stripped from fragment non-struct outputs.
+TEST_F(SpirvWriter_ShaderIOTest, InterpolationOnFragmentOutput_NonStruct) {
+    auto* ep = b.Function("frag2", ty.i32());
+    ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+    ep->SetReturnLocation(0);
+    ep->SetReturnInterpolation(core::Interpolation{core::InterpolationType::kFlat});
+
+    b.Append(ep->Block(), [&] {  //
+        b.Return(ep, b.Constant(42_i));
+    });
+
+    auto* src = R"(
+%frag2 = @fragment func():i32 [@location(0), @interpolate(flat)] {
+  $B1: {
+    ret 42i
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %frag2_loc0_Output:ptr<__out, i32, write> = var undef @location(0)
+}
+
+%frag2_inner = func():i32 {
+  $B2: {
+    ret 42i
+  }
+}
 %frag2 = @fragment func():void {
-  $B7: {
-    %22:i32 = call %frag2_inner
-    store %frag2_loc0_Output, %22
+  $B3: {
+    %4:i32 = call %frag2_inner
+    store %frag2_loc0_Output, %4
     ret
   }
 }
@@ -1205,162 +1127,6 @@
     EXPECT_EQ(expect, str());
 }
 
-TEST_F(SpirvWriter_ShaderIOTest, ClampFragDepth_MultipleFragmentShaders) {
-    auto* str_ty =
-        ty.Struct(mod.symbols.New("Outputs"), {
-                                                  {
-                                                      mod.symbols.New("color"),
-                                                      ty.f32(),
-                                                      core::IOAttributes{
-                                                          .location = 0u,
-                                                      },
-                                                  },
-                                                  {
-                                                      mod.symbols.New("depth"),
-                                                      ty.f32(),
-                                                      core::IOAttributes{
-                                                          .builtin = core::BuiltinValue::kFragDepth,
-                                                      },
-                                                  },
-                                              });
-
-    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_immediate_data_struct = struct @align(4), @block {
-  depth_min:f32 @offset(4)
-  depth_max:f32 @offset(8)
-}
-
-$B1: {  # root
-  %tint_immediate_data:ptr<immediate, tint_immediate_data_struct, read> = var undef
-  %ep1_loc0_Output:ptr<__out, f32, write> = var undef @location(0)
-  %ep1_frag_depth_Output:ptr<__out, f32, write> = var undef @builtin(frag_depth)
-  %ep2_loc0_Output:ptr<__out, f32, write> = var undef @location(0)
-  %ep2_frag_depth_Output:ptr<__out, f32, write> = var undef @builtin(frag_depth)
-  %ep3_loc0_Output:ptr<__out, f32, write> = var undef @location(0)
-  %ep3_frag_depth_Output:ptr<__out, f32, write> = var undef @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<immediate, f32, read> = access %tint_immediate_data, 0u
-    %19:f32 = load %18
-    %20:ptr<immediate, f32, read> = access %tint_immediate_data, 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<immediate, f32, read> = access %tint_immediate_data, 0u
-    %28:f32 = load %27
-    %29:ptr<immediate, f32, read> = access %tint_immediate_data, 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<immediate, f32, read> = access %tint_immediate_data, 0u
-    %37:f32 = load %36
-    %38:ptr<immediate, f32, read> = access %tint_immediate_data, 1u
-    %39:f32 = load %38
-    %40:f32 = clamp %35, %37, %39
-    store %ep3_frag_depth_Output, %40
-    ret
-  }
-}
-)";
-
-    core::ir::transform::PrepareImmediateDataConfig immediate_data_config;
-    immediate_data_config.AddInternalImmediateData(4, mod.symbols.New("depth_min"), ty.f32());
-    immediate_data_config.AddInternalImmediateData(8, mod.symbols.New("depth_max"), ty.f32());
-    auto immediate_data = PrepareImmediateData(mod, immediate_data_config);
-    EXPECT_EQ(immediate_data, Success);
-
-    ShaderIOConfig config{immediate_data.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);
diff --git a/src/tint/lang/wgsl/reader/lower/lower.cc b/src/tint/lang/wgsl/reader/lower/lower.cc
index 6ace4ff..a4fc881 100644
--- a/src/tint/lang/wgsl/reader/lower/lower.cc
+++ b/src/tint/lang/wgsl/reader/lower/lower.cc
@@ -208,8 +208,12 @@
 }  // namespace
 
 Result<SuccessType> Lower(core::ir::Module& mod) {
-    auto res = core::ir::ValidateAndDumpIfNeeded(
-        mod, "wgsl.Lower", core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
+    auto res =
+        core::ir::ValidateAndDumpIfNeeded(mod, "wgsl.Lower",
+                                          core::ir::Capabilities{
+                                              core::ir::Capability::kAllowMultipleEntryPoints,
+                                              core::ir::Capability::kAllowOverrides,
+                                          });
     if (res != Success) {
         return res.Failure();
     }