spirv-reader: Make SPIR-V unit tests valid for WebGPU

- Add missing DescriptorSet and Binding decorations.
- Add missing Phi inbound edge
- Add a preamble with an OpMemoryModel instruction
- Add a preamble and an empty entry point that is not involved with the
  test (sometimes)
- Disable dumping of test with known bad output (tint:863, tint:98)
- Fix storage classes on variables

Bug: tint:863, tint:98
Change-Id: I56b92e8951f5749e094424f8e2da1a2396b5c10c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53261
Auto-Submit: David Neto <dneto@google.com>
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/reader/spirv/function_call_test.cc b/src/reader/spirv/function_call_test.cc
index 638cb45..143694f 100644
--- a/src/reader/spirv/function_call_test.cc
+++ b/src/reader/spirv/function_call_test.cc
@@ -25,8 +25,16 @@
 using ::testing::Eq;
 using ::testing::HasSubstr;
 
+std::string Preamble() {
+  return R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+     OpEntryPoint Vertex %100 "x_100"
+)";
+}
+
 TEST_F(SpvParserTest, EmitStatement_VoidCallNoParams) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
      %void = OpTypeVoid
      %voidfn = OpTypeFunction %void
 
@@ -50,6 +58,7 @@
     Return{}
   }
   Function $2 -> __void
+  StageDecoration{vertex}
   ()
   {
     Call[not set]{
@@ -65,7 +74,7 @@
 }
 
 TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParams) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
      %void = OpTypeVoid
      %voidfn = OpTypeFunction %void
      %uint = OpTypeInt 32 0
@@ -118,7 +127,7 @@
 }
 
 TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParamsUsedTwice) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
      %void = OpTypeVoid
      %voidfn = OpTypeFunction %void
      %uint = OpTypeInt 32 0
@@ -190,7 +199,7 @@
 }
 
 TEST_F(SpvParserTest, EmitStatement_CallWithParams) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
      %void = OpTypeVoid
      %voidfn = OpTypeFunction %void
      %uint = OpTypeInt 32 0
@@ -243,6 +252,7 @@
     }
   }
   Function x_100 -> __void
+  StageDecoration{vertex}
   ()
   {
     VariableDeclStatement{
diff --git a/src/reader/spirv/function_composite_test.cc b/src/reader/spirv/function_composite_test.cc
index 392d021..cffac8a 100644
--- a/src/reader/spirv/function_composite_test.cc
+++ b/src/reader/spirv/function_composite_test.cc
@@ -25,13 +25,17 @@
 using ::testing::Eq;
 using ::testing::HasSubstr;
 
-std::string Preamble() {
+std::string Caps() {
   return R"(
   OpCapability Shader
   OpMemoryModel Logical Simple
   OpEntryPoint GLCompute %100 "main"
   OpExecutionMode %100 LocalSize 1 1 1
+)";
+}
 
+std::string CommonTypes() {
+  return R"(
   %void = OpTypeVoid
   %voidfn = OpTypeFunction %void
 
@@ -71,6 +75,10 @@
 )";
 }
 
+std::string Preamble() {
+  return Caps() + CommonTypes();
+}
+
 using SpvParserTest_Composite_Construct = SpvParserTest;
 
 TEST_F(SpvParserTest_Composite_Construct, Vector) {
@@ -458,12 +466,12 @@
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct_DifferOnlyInMemberName) {
-  const auto assembly =
-      R"(
+  const auto assembly = Caps() +
+                        R"(
       OpMemberName %s0 0 "algo"
       OpMemberName %s1 0 "rithm"
-)" + Preamble() +
-      R"(
+)" + CommonTypes() +
+                        R"(
      %s0 = OpTypeStruct %uint
      %s1 = OpTypeStruct %uint
      %ptr0 = OpTypePointer Function %s0
@@ -513,6 +521,7 @@
     }
   })"))
       << ToString(p->builder(), got);
+  p->SkipDumpingPending("crbug.com/tint/863");
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct_IndexTooBigError) {
@@ -904,12 +913,12 @@
 }
 
 TEST_F(SpvParserTest_CompositeInsert, Struct_DifferOnlyInMemberName) {
-  const auto assembly =
-      R"(
+  const auto assembly = Caps() +
+                        R"(
       OpMemberName %s0 0 "algo"
       OpMemberName %s1 0 "rithm"
-)" + Preamble() +
-      R"(
+)" + CommonTypes() +
+                        R"(
      %s0 = OpTypeStruct %uint
      %s1 = OpTypeStruct %uint
      %ptr0 = OpTypePointer Function %s0
@@ -1057,6 +1066,7 @@
     }
   }
 })")) << body_str;
+  p->SkipDumpingPending("crbug.com/tint/863");
 }
 
 TEST_F(SpvParserTest_CompositeInsert, Struct_IndexTooBigError) {
diff --git a/src/reader/spirv/function_decl_test.cc b/src/reader/spirv/function_decl_test.cc
index 41cc2f0..aec687b 100644
--- a/src/reader/spirv/function_decl_test.cc
+++ b/src/reader/spirv/function_decl_test.cc
@@ -24,6 +24,14 @@
 
 using ::testing::HasSubstr;
 
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %100 "x_100"
+  )";
+}
+
 /// @returns a SPIR-V assembly segment which assigns debug names
 /// to particular IDs.
 std::string Names(std::vector<std::string> ids) {
@@ -45,8 +53,17 @@
   )";
 }
 
+std::string MainBody() {
+  return R"(
+    %100 = OpFunction %void None %voidfn
+    %entry_100 = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
 TEST_F(SpvParserTest, Emit_VoidFunctionWithoutParams) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
+  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
      %100 = OpFunction %void None %voidfn
      %entry = OpLabel
      OpReturn
@@ -68,20 +85,20 @@
 }
 
 TEST_F(SpvParserTest, Emit_NonVoidResultType) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
+  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
      %fn_ret_float = OpTypeFunction %float
-     %100 = OpFunction %float None %fn_ret_float
+     %200 = OpFunction %float None %fn_ret_float
      %entry = OpLabel
      OpReturnValue %float_0
      OpFunctionEnd
-  )"));
+  )" + MainBody()));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
+  auto fe = p->function_emitter(200);
   EXPECT_TRUE(fe.Emit());
 
   auto got = p->program().to_str();
   std::string expect = R"(Module{
-  Function x_100 -> __f32
+  Function x_200 -> __f32
   ()
   {
     Return{
@@ -92,28 +109,29 @@
   }
 }
 )";
-  EXPECT_EQ(got, expect);
+  EXPECT_THAT(got, HasSubstr(expect));
 }
 
 TEST_F(SpvParserTest, Emit_MixedParamTypes) {
-  auto p = parser(test::Assemble(Names({"a", "b", "c"}) + CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %float %uint %float %int
+  auto p = parser(
+      test::Assemble(Preamble() + Names({"a", "b", "c"}) + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
 
-     %100 = OpFunction %void None %fn_mixed_params
+     %200 = OpFunction %void None %fn_mixed_params
      %a = OpFunctionParameter %uint
      %b = OpFunctionParameter %float
      %c = OpFunctionParameter %int
      %mixed_entry = OpLabel
      OpReturn
      OpFunctionEnd
-  )"));
+  )" + MainBody()));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
+  auto fe = p->function_emitter(200);
   EXPECT_TRUE(fe.Emit());
 
   auto got = p->program().to_str();
   std::string expect = R"(Module{
-  Function x_100 -> __void
+  Function x_200 -> __void
   (
     VariableConst{
       a
@@ -139,28 +157,28 @@
   }
 }
 )";
-  EXPECT_EQ(got, expect);
+  EXPECT_THAT(got, HasSubstr(expect));
 }
 
 TEST_F(SpvParserTest, Emit_GenerateParamNames) {
-  auto p = parser(test::Assemble(CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %float %uint %float %int
+  auto p = parser(test::Assemble(Preamble() + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
 
-     %100 = OpFunction %void None %fn_mixed_params
+     %200 = OpFunction %void None %fn_mixed_params
      %14 = OpFunctionParameter %uint
      %15 = OpFunctionParameter %float
      %16 = OpFunctionParameter %int
      %mixed_entry = OpLabel
      OpReturn
      OpFunctionEnd
-  )"));
+  )" + MainBody()));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
-  auto fe = p->function_emitter(100);
+  auto fe = p->function_emitter(200);
   EXPECT_TRUE(fe.Emit());
 
   auto got = p->program().to_str();
   std::string expect = R"(Module{
-  Function x_100 -> __void
+  Function x_200 -> __void
   (
     VariableConst{
       x_14
@@ -186,7 +204,7 @@
   }
 }
 )";
-  EXPECT_EQ(got, expect);
+  EXPECT_THAT(got, HasSubstr(expect));
 }
 
 }  // namespace
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 0ef8989..d564ce4 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -306,7 +306,7 @@
      %ptr_wg_ty = OpTypePointer Workgroup %ty
      %ptr_priv_ty = OpTypePointer Private %ty
      %1 = OpVariable %ptr_wg_ty Workgroup
-     %2 = OpVariable %ptr_priv_ty Workgroup
+     %2 = OpVariable %ptr_priv_ty Private
      %100 = OpFunction %void None %voidfn
      %entry = OpLabel
      OpCopyMemory %2 %1
@@ -1048,6 +1048,10 @@
 }
 
 TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughCopyObject_WithHoisting) {
+  // TODO(dneto): Hoisting non-storable values (pointers) is not yet supported.
+  // It's debatable whether this test should run at all.
+  // crbug.com/tint/98
+
   // Like the previous test, but the declaration for the copy-object
   // has its declaration hoisted.
   const auto assembly = OldStorageBufferPreamble() + R"(
@@ -1119,6 +1123,7 @@
 }
 Return{}
 )") << p->error();
+  p->SkipDumpingPending("crbug.com/tint/98");
 }
 
 TEST_F(SpvParserMemoryTest, DISABLED_RemapStorageBuffer_ThroughFunctionCall) {
diff --git a/src/reader/spirv/function_misc_test.cc b/src/reader/spirv/function_misc_test.cc
index 4ecea55..59d957f 100644
--- a/src/reader/spirv/function_misc_test.cc
+++ b/src/reader/spirv/function_misc_test.cc
@@ -130,7 +130,7 @@
      %100 = OpFunction %void None %voidfn
      %entry = OpLabel
 
-     %14 = OpCopyObject %v2uint %4
+     %14 = OpCopyObject %v2bool %4
      %11 = OpCopyObject %v2uint %1
      %12 = OpCopyObject %v2int %2
      %13 = OpCopyObject %v2float %3
diff --git a/src/reader/spirv/function_var_test.cc b/src/reader/spirv/function_var_test.cc
index 114dfd9..071d30c 100644
--- a/src/reader/spirv/function_var_test.cc
+++ b/src/reader/spirv/function_var_test.cc
@@ -1951,7 +1951,7 @@
      OpBranch %89
 
      %89 = OpLabel
-     %2 = OpPhi %uint %uint_0 %30 %uint_1 %40
+     %2 = OpPhi %uint %uint_0 %30 %uint_1 %40 %uint_0 %79
      OpStore %1 %2
      OpBranch %10
 
@@ -2026,6 +2026,10 @@
       Continue{}
     }
   }
+  Assignment{
+    Identifier[not set]{x_2_phi}
+    ScalarConstructor[not set]{0u}
+  }
   continuing {
     VariableDeclStatement{
       VariableConst{
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index ea46b0f..6268a01 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -23,6 +23,25 @@
 
 using ::testing::Eq;
 
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "x_100"
+  )";
+}
+
+std::string MainBody() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %main = OpFunction %void None %voidfn
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
 TEST_F(SpvParserTest, ConvertType_PreservesExistingFailure) {
   auto p = parser(std::vector<uint32_t>{});
   p->Fail() << "boing";
@@ -41,17 +60,24 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_NotAnId) {
-  auto p = parser(test::Assemble("%1 = OpExtInstImport \"GLSL.std.450\""));
+  auto assembly = Preamble() + MainBody();
+  auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildInternalModule());
 
-  auto* type = p->ConvertType(10);
+  auto* type = p->ConvertType(900);
   EXPECT_EQ(type, nullptr);
   EXPECT_EQ(nullptr, type);
-  EXPECT_THAT(p->error(), Eq("ID is not a SPIR-V type: 10"));
+  EXPECT_THAT(p->error(), Eq("ID is not a SPIR-V type: 900"));
 }
 
 TEST_F(SpvParserTest, ConvertType_IdExistsButIsNotAType) {
-  auto p = parser(test::Assemble("%1 = OpExtInstImport \"GLSL.std.450\""));
+  auto assembly = R"(
+     OpCapability Shader
+     %1 = OpExtInstImport "GLSL.std.450"
+     OpMemoryModel Logical Simple
+     OpEntryPoint Vertex %main "x_100"
+)" + MainBody();
+  auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
@@ -71,7 +97,13 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_Void) {
-  auto p = parser(test::Assemble("%1 = OpTypeVoid"));
+  auto p = parser(test::Assemble(Preamble() + "%1 = OpTypeVoid" + R"(
+   %voidfn = OpTypeFunction %1
+   %main = OpFunction %1 None %voidfn
+   %entry = OpLabel
+   OpReturn
+   OpFunctionEnd
+  )"));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
@@ -80,7 +112,8 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_Bool) {
-  auto p = parser(test::Assemble("%100 = OpTypeBool"));
+  auto p =
+      parser(test::Assemble(Preamble() + "%100 = OpTypeBool" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(100);
@@ -89,7 +122,8 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_I32) {
-  auto p = parser(test::Assemble("%2 = OpTypeInt 32 1"));
+  auto p =
+      parser(test::Assemble(Preamble() + "%2 = OpTypeInt 32 1" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(2);
@@ -98,7 +132,8 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_U32) {
-  auto p = parser(test::Assemble("%3 = OpTypeInt 32 0"));
+  auto p =
+      parser(test::Assemble(Preamble() + "%3 = OpTypeInt 32 0" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -107,7 +142,8 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_F32) {
-  auto p = parser(test::Assemble("%4 = OpTypeFloat 32"));
+  auto p =
+      parser(test::Assemble(Preamble() + "%4 = OpTypeFloat 32" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(4);
@@ -116,7 +152,8 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_BadIntWidth) {
-  auto p = parser(test::Assemble("%5 = OpTypeInt 17 1"));
+  auto p =
+      parser(test::Assemble(Preamble() + "%5 = OpTypeInt 17 1" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(5);
@@ -125,7 +162,8 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_BadFloatWidth) {
-  auto p = parser(test::Assemble("%6 = OpTypeFloat 19"));
+  auto p =
+      parser(test::Assemble(Preamble() + "%6 = OpTypeFloat 19" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(6);
@@ -134,10 +172,10 @@
 }
 
 TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidVectorElement) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %5 = OpTypePipe ReadOnly
     %20 = OpTypeVector %5 2
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(20);
@@ -146,12 +184,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_VecOverF32) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %float = OpTypeFloat 32
     %20 = OpTypeVector %float 2
     %30 = OpTypeVector %float 3
     %40 = OpTypeVector %float 4
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* v2xf32 = p->ConvertType(20);
@@ -173,12 +211,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_VecOverI32) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %int = OpTypeInt 32 1
     %20 = OpTypeVector %int 2
     %30 = OpTypeVector %int 3
     %40 = OpTypeVector %int 4
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* v2xi32 = p->ConvertType(20);
@@ -200,12 +238,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_VecOverU32) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %uint = OpTypeInt 32 0
     %20 = OpTypeVector %uint 2
     %30 = OpTypeVector %uint 3
     %40 = OpTypeVector %uint 4
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* v2xu32 = p->ConvertType(20);
@@ -227,11 +265,11 @@
 }
 
 TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidMatrixElement) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %5 = OpTypePipe ReadOnly
     %10 = OpTypeVector %5 2
     %20 = OpTypeMatrix %10 2
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(20);
@@ -241,7 +279,7 @@
 
 TEST_F(SpvParserTest, ConvertType_MatrixOverF32) {
   // Matrices are only defined over floats.
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %float = OpTypeFloat 32
     %v2 = OpTypeVector %float 2
     %v3 = OpTypeVector %float 3
@@ -257,7 +295,7 @@
     %42 = OpTypeMatrix %v4 2
     %43 = OpTypeMatrix %v4 3
     %44 = OpTypeMatrix %v4 4
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* m22 = p->ConvertType(22);
@@ -318,10 +356,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_RuntimeArray) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %uint = OpTypeInt 32 0
     %10 = OpTypeRuntimeArray %uint
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -338,11 +376,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_RuntimeArray_InvalidDecoration) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 Block
     %uint = OpTypeInt 32 0
     %10 = OpTypeRuntimeArray %uint
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   auto* type = p->ConvertType(10);
   EXPECT_EQ(type, nullptr);
@@ -352,11 +390,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_RuntimeArray_ArrayStride_Valid) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 ArrayStride 64
     %uint = OpTypeInt 32 0
     %10 = OpTypeRuntimeArray %uint
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   auto* type = p->ConvertType(10);
   ASSERT_NE(type, nullptr);
@@ -367,11 +405,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_RuntimeArray_ArrayStride_ZeroIsError) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 ArrayStride 0
     %uint = OpTypeInt 32 0
     %10 = OpTypeRuntimeArray %uint
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   auto* type = p->ConvertType(10);
   EXPECT_EQ(type, nullptr);
@@ -381,12 +419,12 @@
 
 TEST_F(SpvParserTest,
        ConvertType_RuntimeArray_ArrayStride_SpecifiedTwiceIsError) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 ArrayStride 64
     OpDecorate %10 ArrayStride 64
     %uint = OpTypeInt 32 0
     %10 = OpTypeRuntimeArray %uint
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   auto* type = p->ConvertType(10);
   EXPECT_EQ(type, nullptr);
@@ -395,11 +433,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_Array) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %uint = OpTypeInt 32 0
     %uint_42 = OpConstant %uint 42
     %10 = OpTypeArray %uint %uint_42
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -416,12 +454,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantValue) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %uint_42 SpecId 12
     %uint = OpTypeInt 32 0
     %uint_42 = OpSpecConstant %uint 42
     %10 = OpTypeArray %uint %uint_42
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -431,12 +469,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantExpr) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %uint = OpTypeInt 32 0
     %uint_42 = OpConstant %uint 42
     %sum = OpSpecConstantOp %uint IAdd %uint_42 %uint_42
     %10 = OpTypeArray %uint %sum
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -450,11 +488,11 @@
 // optimizer representation doesn't handle it and asserts out instead.
 
 TEST_F(SpvParserTest, ConvertType_ArrayBadTooBig) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %uint64 = OpTypeInt 64 0
     %uint64_big = OpConstant %uint64 5000000000
     %10 = OpTypeArray %uint64 %uint64_big
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -465,12 +503,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_Array_InvalidDecoration) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 Block
     %uint = OpTypeInt 32 0
     %uint_5 = OpConstant %uint 5
     %10 = OpTypeArray %uint %uint_5
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   auto* type = p->ConvertType(10);
   EXPECT_EQ(type, nullptr);
@@ -480,12 +518,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_ArrayStride_Valid) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 ArrayStride 8
     %uint = OpTypeInt 32 0
     %uint_5 = OpConstant %uint 5
     %10 = OpTypeArray %uint %uint_5
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -498,12 +536,12 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_ArrayStride_ZeroIsError) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 ArrayStride 0
     %uint = OpTypeInt 32 0
     %uint_5 = OpConstant %uint 5
     %10 = OpTypeArray %uint %uint_5
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -513,13 +551,13 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_ArrayStride_SpecifiedTwiceIsError) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 ArrayStride 4
     OpDecorate %10 ArrayStride 4
     %uint = OpTypeInt 32 0
     %uint_5 = OpConstant %uint 5
     %10 = OpTypeArray %uint %uint_5
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(10);
@@ -529,11 +567,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_StructTwoMembers) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     %uint = OpTypeInt 32 0
     %float = OpTypeFloat 32
     %10 = OpTypeStruct %uint %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
 
@@ -547,11 +585,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_StructWithBlockDecoration) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpDecorate %10 Block
     %uint = OpTypeInt 32 0
     %10 = OpTypeStruct %uint
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
 
@@ -565,7 +603,7 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_StructWithMemberDecorations) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
     OpMemberDecorate %10 0 Offset 0
     OpMemberDecorate %10 1 Offset 8
     OpMemberDecorate %10 2 Offset 16
@@ -573,7 +611,7 @@
     %vec = OpTypeVector %float 2
     %mat = OpTypeMatrix %vec 2
     %10 = OpTypeStruct %float %vec %mat
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
   EXPECT_TRUE(p->RegisterUserAndStructMemberNames());
 
@@ -593,10 +631,16 @@
 
 TEST_F(SpvParserTest, ConvertType_InvalidPointeetype) {
   // Disallow pointer-to-function
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %void = OpTypeVoid
   %42 = OpTypeFunction %void
   %3 = OpTypePointer Input %42
+
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
   )"));
   EXPECT_TRUE(p->BuildInternalModule()) << p->error();
 
@@ -608,19 +652,19 @@
 
 TEST_F(SpvParserTest, DISABLED_ConvertType_InvalidStorageClass) {
   // Disallow invalid storage class
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %1 = OpTypeFloat 32
   %3 = OpTypePointer !999 %1   ; Special syntax to inject 999 as the storage class
-  )"));
+  )" + MainBody()));
   // TODO(dneto): I can't get it past module building.
   EXPECT_FALSE(p->BuildInternalModule()) << p->error();
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerInput) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Input %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -633,10 +677,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerOutput) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Output %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -649,10 +693,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerUniform) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Uniform %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -665,10 +709,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerWorkgroup) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Workgroup %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -681,10 +725,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerUniformConstant) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer UniformConstant %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -697,10 +741,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerStorageBuffer) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer StorageBuffer %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -713,10 +757,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerImage) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Image %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -729,10 +773,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerPrivate) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Private %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -745,10 +789,10 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_PointerFunction) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %3 = OpTypePointer Function %float
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -762,11 +806,11 @@
 
 TEST_F(SpvParserTest, ConvertType_PointerToPointer) {
   // FYI:  The reader suports pointer-to-pointer even while WebGPU does not.
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %42 = OpTypePointer Output %float
   %3 = OpTypePointer Input %42
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(3);
@@ -788,9 +832,9 @@
 
 TEST_F(SpvParserTest, ConvertType_Sampler_PretendVoid) {
   // We fake the type suport for samplers, images, and sampled images.
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %1 = OpTypeSampler
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
@@ -800,10 +844,10 @@
 
 TEST_F(SpvParserTest, ConvertType_Image_PretendVoid) {
   // We fake the type suport for samplers, images, and sampled images.
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %1 = OpTypeImage %float 2D 0 0 0 1 Unknown
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
@@ -812,11 +856,11 @@
 }
 
 TEST_F(SpvParserTest, ConvertType_SampledImage_PretendVoid) {
-  auto p = parser(test::Assemble(R"(
+  auto p = parser(test::Assemble(Preamble() + R"(
   %float = OpTypeFloat 32
   %im = OpTypeImage %float 2D 0 0 0 1 Unknown
   %1 = OpTypeSampledImage %im
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildInternalModule());
 
   auto* type = p->ConvertType(1);
diff --git a/src/reader/spirv/parser_impl_function_decl_test.cc b/src/reader/spirv/parser_impl_function_decl_test.cc
index a78ce0a..afe5437 100644
--- a/src/reader/spirv/parser_impl_function_decl_test.cc
+++ b/src/reader/spirv/parser_impl_function_decl_test.cc
@@ -23,6 +23,28 @@
 
 using ::testing::HasSubstr;
 
+std::string Caps() {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+  )";
+}
+
+std::string Preamble() {
+  return Caps() + R"(
+    OpEntryPoint Vertex %main "x_100"
+  )";
+}
+
+std::string MainBody() {
+  return R"(
+    %main = OpFunction %void None %voidfn
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+  )";
+}
+
 /// @returns a SPIR-V assembly segment which assigns debug names
 /// to particular IDs.
 std::string Names(std::vector<std::string> ids) {
@@ -45,16 +67,22 @@
 }
 
 TEST_F(SpvParserTest, EmitFunctions_NoFunctions) {
-  auto p = parser(test::Assemble(CommonTypes()));
+  auto p = parser(test::Assemble(
+      R"(
+     OpCapability Shader
+     OpMemoryModel Logical Simple
+)" + CommonTypes()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
   const auto program_ast = program.to_str(false);
   EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
+  p->SkipDumpingPending("Not valid for Vulkan: needs an entry point");
 }
 
 TEST_F(SpvParserTest, EmitFunctions_FunctionWithoutBody) {
-  auto p = parser(test::Assemble(Names({"main"}) + CommonTypes() + R"(
+  auto p =
+      parser(test::Assemble(Preamble() + Names({"main"}) + CommonTypes() + R"(
      %main = OpFunction %void None %voidfn
      OpFunctionEnd
   )"));
@@ -63,11 +91,12 @@
   Program program = p->program();
   const auto program_ast = program.to_str(false);
   EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
+  p->SkipDumpingPending("Missing an entry point body requires Linkage");
 }
 
 TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_Vertex) {
-  std::string input = Names({"main"}) + R"(OpEntryPoint Vertex %main "main"
-)" + CommonTypes() + R"(
+  std::string input = Caps() + R"(OpEntryPoint Vertex %main "main" )" +
+                      Names({"main"}) + CommonTypes() + R"(
 %main = OpFunction %void None %voidfn
 %entry = OpLabel
 OpReturn
@@ -87,12 +116,11 @@
 }
 
 TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_Fragment) {
-  std::string input = Names({"main"}) + R"(OpEntryPoint Fragment %main "main"
-)" + CommonTypes() + R"(
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
+  std::string input = Caps() + R"(
+     OpEntryPoint Fragment %main "main"
+     OpExecutionMode %main OriginUpperLeft
+)" + Names({"main"}) + CommonTypes() +
+                      MainBody();
 
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
@@ -108,12 +136,11 @@
 }
 
 TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_GLCompute) {
-  std::string input = Names({"main"}) + R"(OpEntryPoint GLCompute %main "main"
-)" + CommonTypes() + R"(
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
+  std::string input = Caps() + R"(
+      OpEntryPoint GLCompute %main "main"
+      OpExecutionMode %main LocalSize 1 1 1
+)" + Names({"main"}) + CommonTypes() +
+                      MainBody();
 
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
@@ -129,14 +156,12 @@
 }
 
 TEST_F(SpvParserTest, EmitFunctions_Function_EntryPoint_MultipleEntryPoints) {
-  std::string input = Names({"main"}) +
-                      R"(OpEntryPoint GLCompute %main "comp_main"
-OpEntryPoint Fragment %main "frag_main"
-)" + CommonTypes() + R"(
-%main = OpFunction %void None %voidfn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd)";
+  std::string input = Caps() +
+                      R"(
+OpEntryPoint Vertex %main "first_shader"
+OpEntryPoint Vertex %main "second_shader"
+)" + Names({"main"}) + CommonTypes() +
+                      MainBody();
 
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
@@ -144,32 +169,33 @@
   Program program = p->program();
   const auto program_ast = program.to_str(false);
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + program.Symbols().Get("frag_main").to_str() +
+  Function )" + program.Symbols().Get("first_shader").to_str() +
                                      R"( -> __void
-  StageDecoration{fragment}
+  StageDecoration{vertex}
   ()
   {)"));
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + program.Symbols().Get("comp_main").to_str() +
+  Function )" + program.Symbols().Get("second_shader").to_str() +
                                      R"( -> __void
-  StageDecoration{compute}
+  StageDecoration{vertex}
   ()
   {)"));
 }
 
 TEST_F(SpvParserTest, EmitFunctions_VoidFunctionWithoutParams) {
-  auto p = parser(test::Assemble(Names({"main"}) + CommonTypes() + R"(
-     %main = OpFunction %void None %voidfn
-     %entry = OpLabel
-     OpReturn
-     OpFunctionEnd
-  )"));
+  auto p = parser(test::Assemble(Preamble() + Names({"another_function"}) +
+                                 CommonTypes() + R"(
+    %another_function = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)" + MainBody()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
   const auto program_ast = program.to_str(false);
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + program.Symbols().Get("main").to_str() +
+  Function )" + program.Symbols().Get("another_function").to_str() +
                                      R"( -> __void
   ()
   {)"));
@@ -177,6 +203,7 @@
 
 TEST_F(SpvParserTest, EmitFunctions_CalleePrecedesCaller) {
   auto p = parser(test::Assemble(
+      Preamble() +
       Names({"root", "branch", "leaf", "leaf_result", "branch_result"}) +
       CommonTypes() + R"(
      %uintfn = OpTypeFunction %uint
@@ -198,7 +225,7 @@
      %leaf_entry = OpLabel
      OpReturnValue %uint_0
      OpFunctionEnd
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
@@ -257,18 +284,25 @@
     }
     Return{}
   }
+  Function x_100 -> __void
+  StageDecoration{vertex}
+  ()
+  {
+    Return{}
+  }
 })")) << program_ast;
 }
 
 TEST_F(SpvParserTest, EmitFunctions_NonVoidResultType) {
-  auto p = parser(test::Assemble(Names({"ret_float"}) + CommonTypes() + R"(
+  auto p = parser(
+      test::Assemble(Preamble() + Names({"ret_float"}) + CommonTypes() + R"(
      %fn_ret_float = OpTypeFunction %float
 
      %ret_float = OpFunction %float None %fn_ret_float
      %ret_float_entry = OpLabel
      OpReturnValue %float_0
      OpFunctionEnd
-  )"));
+)" + MainBody()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
@@ -287,9 +321,9 @@
 }
 
 TEST_F(SpvParserTest, EmitFunctions_MixedParamTypes) {
-  auto p = parser(test::Assemble(Names({"mixed_params", "a", "b", "c"}) +
-                                 CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %float %uint %float %int
+  auto p = parser(test::Assemble(
+      Preamble() + Names({"mixed_params", "a", "b", "c"}) + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
 
      %mixed_params = OpFunction %void None %fn_mixed_params
      %a = OpFunctionParameter %uint
@@ -298,7 +332,7 @@
      %mixed_entry = OpLabel
      OpReturn
      OpFunctionEnd
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
@@ -331,8 +365,9 @@
 }
 
 TEST_F(SpvParserTest, EmitFunctions_GenerateParamNames) {
-  auto p = parser(test::Assemble(Names({"mixed_params"}) + CommonTypes() + R"(
-     %fn_mixed_params = OpTypeFunction %float %uint %float %int
+  auto p = parser(
+      test::Assemble(Preamble() + Names({"mixed_params"}) + CommonTypes() + R"(
+     %fn_mixed_params = OpTypeFunction %void %uint %float %int
 
      %mixed_params = OpFunction %void None %fn_mixed_params
      %14 = OpFunctionParameter %uint
@@ -341,7 +376,7 @@
      %mixed_entry = OpLabel
      OpReturn
      OpFunctionEnd
-  )"));
+  )" + MainBody()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
   Program program = p->program();
diff --git a/src/reader/spirv/parser_impl_get_decorations_test.cc b/src/reader/spirv/parser_impl_get_decorations_test.cc
index f70af99..f4fa13d 100644
--- a/src/reader/spirv/parser_impl_get_decorations_test.cc
+++ b/src/reader/spirv/parser_impl_get_decorations_test.cc
@@ -26,12 +26,15 @@
 
 using SpvParserGetDecorationsTest = SpvParserTest;
 
+const char* kSkipReason = "This example is deliberately a SPIR-V fragment";
+
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_NotAnId) {
   auto p = parser(test::Assemble(""));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   auto decorations = p->GetDecorationsFor(42);
   EXPECT_TRUE(decorations.empty());
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_NoDecorations) {
@@ -40,6 +43,7 @@
   auto decorations = p->GetDecorationsFor(1);
   EXPECT_TRUE(decorations.empty());
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_OneDecoration) {
@@ -53,6 +57,7 @@
   EXPECT_THAT(decorations,
               UnorderedElementsAre(Decoration{SpvDecorationBlock}));
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsFor_MultiDecoration) {
@@ -68,6 +73,7 @@
               UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision},
                                    Decoration{SpvDecorationLocation, 7}));
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_NotAnId) {
@@ -76,6 +82,7 @@
   auto decorations = p->GetDecorationsForMember(42, 9);
   EXPECT_TRUE(decorations.empty());
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_NotAStruct) {
@@ -84,6 +91,7 @@
   auto decorations = p->GetDecorationsFor(1);
   EXPECT_TRUE(decorations.empty());
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest,
@@ -96,6 +104,7 @@
   auto decorations = p->GetDecorationsForMember(10, 0);
   EXPECT_TRUE(decorations.empty());
   EXPECT_TRUE(p->error().empty());
+  p->SkipDumpingPending(kSkipReason);
 }
 
 TEST_F(SpvParserGetDecorationsTest, GetDecorationsForMember_RelaxedPrecision) {
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 6109957..dcead04 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -1701,6 +1701,7 @@
 TEST_F(SpvModuleScopeVarParserTest, DescriptorGroupDecoration_Valid) {
   auto p = parser(test::Assemble(Preamble() + FragMain() + CommonLayout() + R"(
      OpDecorate %1 DescriptorSet 3
+     OpDecorate %1 Binding 9 ; Required to pass WGSL validation
      OpDecorate %strct Block
 )" + CommonTypes() + R"(
      %ptr_sb_strct = OpTypePointer StorageBuffer %strct
@@ -1713,6 +1714,7 @@
   Variable{
     Decorations{
       GroupDecoration{3}
+      BindingDecoration{9}
     }
     x_1
     storage
@@ -1753,6 +1755,7 @@
 
 TEST_F(SpvModuleScopeVarParserTest, BindingDecoration_Valid) {
   auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 DescriptorSet 0 ; WGSL validation requires this already
      OpDecorate %1 Binding 3
      OpDecorate %strct Block
 )" + CommonLayout() + CommonTypes() +
@@ -1766,6 +1769,7 @@
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     Decorations{
+      GroupDecoration{0}
       BindingDecoration{3}
     }
     x_1
@@ -1807,10 +1811,13 @@
 
 TEST_F(SpvModuleScopeVarParserTest,
        StructMember_NonReadableDecoration_Dropped) {
-  auto p = parser(test::Assemble(Preamble() + FragMain() + CommonLayout() + R"(
+  auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
      OpDecorate %strct Block
      OpMemberDecorate %strct 0 NonReadable
-)" + CommonTypes() + R"(
+)" + CommonLayout() + CommonTypes() +
+                                 R"(
      %ptr_sb_strct = OpTypePointer StorageBuffer %strct
      %1 = OpVariable %ptr_sb_strct StorageBuffer
   )" + MainBody()));
@@ -1826,6 +1833,10 @@
     StructMember{[[ offset 8 ]] field2: __type_name_Arr}
   }
   Variable{
+    Decorations{
+      GroupDecoration{0}
+      BindingDecoration{0}
+    }
     x_1
     storage
     read_write
@@ -1837,6 +1848,8 @@
 TEST_F(SpvModuleScopeVarParserTest, ColMajorDecoration_Dropped) {
   auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
      OpName %myvar "myvar"
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
      OpDecorate %s Block
      OpMemberDecorate %s 0 ColMajor
      OpMemberDecorate %s 0 Offset 0
@@ -1860,6 +1873,10 @@
     StructMember{[[ offset 0 ]] field0: __mat_2_3__f32}
   }
   Variable{
+    Decorations{
+      GroupDecoration{0}
+      BindingDecoration{0}
+    }
     myvar
     storage
     read_write
@@ -1871,6 +1888,8 @@
 TEST_F(SpvModuleScopeVarParserTest, MatrixStrideDecoration_Dropped) {
   auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
      OpName %myvar "myvar"
+     OpDecorate %myvar DescriptorSet 0
+     OpDecorate %myvar Binding 0
      OpDecorate %s Block
      OpMemberDecorate %s 0 MatrixStride 8
      OpMemberDecorate %s 0 Offset 0
@@ -1893,6 +1912,10 @@
     StructMember{[[ offset 0 ]] field0: __mat_2_3__f32}
   }
   Variable{
+    Decorations{
+      GroupDecoration{0}
+      BindingDecoration{0}
+    }
     myvar
     storage
     read_write
@@ -1928,6 +1951,8 @@
   // Variable should have access(read)
   auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
      OpDecorate %s Block
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
      OpMemberDecorate %s 0 NonWritable
      OpMemberDecorate %s 1 NonWritable
      OpMemberDecorate %s 0 Offset 0
@@ -1950,6 +1975,10 @@
     StructMember{[[ offset 4 ]] field1: __f32}
   }
   Variable{
+    Decorations{
+      GroupDecoration{0}
+      BindingDecoration{0}
+    }
     x_1
     storage
     read
@@ -1961,6 +1990,8 @@
 TEST_F(SpvModuleScopeVarParserTest, StorageBuffer_NonWritable_NotAllMembers) {
   // Variable should have access(read_write)
   auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
      OpDecorate %s Block
      OpMemberDecorate %s 0 NonWritable
      OpMemberDecorate %s 0 Offset 0
@@ -1983,6 +2014,10 @@
     StructMember{[[ offset 4 ]] field1: __f32}
   }
   Variable{
+    Decorations{
+      GroupDecoration{0}
+      BindingDecoration{0}
+    }
     x_1
     storage
     read_write
@@ -1997,6 +2032,8 @@
   // Variable should have access(read_write)
   auto p = parser(test::Assemble(Preamble() + FragMain() + R"(
      OpDecorate %s Block
+     OpDecorate %1 DescriptorSet 0
+     OpDecorate %1 Binding 0
      OpMemberDecorate %s 0 NonWritable
      OpMemberDecorate %s 0 NonWritable ; same member. Don't double-count it
      OpMemberDecorate %s 0 Offset 0
@@ -2019,6 +2056,10 @@
     StructMember{[[ offset 4 ]] field1: __f32}
   }
   Variable{
+    Decorations{
+      GroupDecoration{0}
+      BindingDecoration{0}
+    }
     x_1
     storage
     read_write