[ir][spirv-writer] Rework remaining unit tests

Switch almost all tests over to using the `Generate()` and EXPECT_INST
helpers. Fixup some cases of invalid IR that this caught, and tweak
some tests to make sure we're testing the right thing in the presence
of sanitizer transforms.

The `Type` and `Constant` tests still use the PIMPL methods directly,
as types and constants are emitted lazily on first use.

Make almost all of the PIMPL methods private.

Bug: tint:1906
Change-Id: Ia52700e8298b5da5d22770a3949509082cb208bb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/139543
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.cc b/src/tint/writer/spirv/ir/generator_impl_ir.cc
index 2498fcc..7b96272 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.cc
@@ -194,7 +194,14 @@
 }
 
 uint32_t GeneratorImplIr::Constant(ir::Constant* constant) {
-    return Constant(constant->Value());
+    auto id = Constant(constant->Value());
+
+    // Set the name for the SPIR-V result ID if provided in the module.
+    if (auto name = ir_->NameOf(constant)) {
+        module_.PushDebug(spv::Op::OpName, {id, Operand(name.Name())});
+    }
+
+    return id;
 }
 
 uint32_t GeneratorImplIr::Constant(const constant::Value* constant) {
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.h b/src/tint/writer/spirv/ir/generator_impl_ir.h
index 01ab913..e2a78bc 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.h
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.h
@@ -85,11 +85,6 @@
     /// @returns the result ID of the constant
     uint32_t Constant(ir::Constant* constant);
 
-    /// Get the result ID of the OpConstantNull instruction for `type`, emitting it if necessary.
-    /// @param type the type to get the ID for
-    /// @returns the result ID of the OpConstantNull instruction
-    uint32_t ConstantNull(const type::Type* type);
-
     /// Get the result ID of the type `ty`, emitting a type declaration instruction if necessary.
     /// @param ty the type to get the ID for
     /// @param addrspace the optional address space that this type is being used for
@@ -97,6 +92,29 @@
     uint32_t Type(const type::Type* ty,
                   builtin::AddressSpace addrspace = builtin::AddressSpace::kUndefined);
 
+  private:
+    /// Convert a builtin to the corresponding SPIR-V enum value, taking into account the target
+    /// address space. Adds any capabilities needed for the builtin.
+    /// @param builtin the builtin to convert
+    /// @param addrspace the address space the builtin is being used in
+    /// @returns the enum value of the corresponding SPIR-V builtin
+    uint32_t Builtin(builtin::BuiltinValue builtin, builtin::AddressSpace addrspace);
+
+    /// Get the result ID of the constant `constant`, emitting its instruction if necessary.
+    /// @param constant the constant to get the ID for
+    /// @returns the result ID of the constant
+    uint32_t Constant(const constant::Value* constant);
+
+    /// Get the result ID of the OpConstantNull instruction for `type`, emitting it if necessary.
+    /// @param type the type to get the ID for
+    /// @returns the result ID of the OpConstantNull instruction
+    uint32_t ConstantNull(const type::Type* type);
+
+    /// Get the ID of the label for `block`.
+    /// @param block the block to get the label ID for
+    /// @returns the ID of the block's label
+    uint32_t Label(ir::Block* block);
+
     /// Get the result ID of the value `value`, emitting its instruction if necessary.
     /// @param value the value to get the ID for
     /// @returns the result ID of the value
@@ -112,11 +130,6 @@
     /// @returns the result ID of the instruction
     uint32_t Undef(const type::Type* ty);
 
-    /// Get the ID of the label for `block`.
-    /// @param block the block to get the label ID for
-    /// @returns the ID of the block's label
-    uint32_t Label(ir::Block* block);
-
     /// Emit a struct type.
     /// @param id the result ID to use
     /// @param addrspace the optional address space that this type is being used for
@@ -202,19 +215,6 @@
     /// @param inst the flow control instruction
     void EmitExitPhis(ir::ControlInstruction* inst);
 
-  private:
-    /// Convert a builtin to the corresponding SPIR-V enum value, taking into account the target
-    /// address space. Adds any capabilities needed for the builtin.
-    /// @param builtin the builtin to convert
-    /// @param addrspace the address space the builtin is being used in
-    /// @returns the enum value of the corresponding SPIR-V builtin
-    uint32_t Builtin(builtin::BuiltinValue builtin, builtin::AddressSpace addrspace);
-
-    /// Get the result ID of the constant `constant`, emitting its instruction if necessary.
-    /// @param constant the constant to get the ID for
-    /// @returns the result ID of the constant
-    uint32_t Constant(const constant::Value* constant);
-
     ir::Module* ir_;
     spirv::Module module_;
     BinaryWriter writer_;
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_access_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_access_test.cc
index df494fb..86d09f7 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_access_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_access_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "gmock/gmock.h"
 #include "src/tint/writer/spirv/ir/test_helper_ir.h"
 
 namespace tint::writer::spirv {
@@ -31,7 +30,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpCompositeExtract %int %arr 1");
 }
 
@@ -44,7 +43,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpAccessChain %_ptr_Function_int %arr %uint_1");
 }
 
@@ -59,7 +58,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpAccessChain %_ptr_Function_int %arr %idx");
 }
 
@@ -75,7 +74,7 @@
         mod.SetName(result_scalar, "result_scalar");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result_vector = OpCompositeExtract %v2float %mat 1");
     EXPECT_INST("%result_scalar = OpCompositeExtract %float %mat 1 0");
 }
@@ -91,7 +90,7 @@
         mod.SetName(result_scalar, "result_scalar");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result_vector = OpAccessChain %_ptr_Function_v2float %mat %uint_1");
     EXPECT_INST("%result_scalar = OpAccessChain %_ptr_Function_float %mat %uint_1 %uint_0");
 }
@@ -109,7 +108,7 @@
         mod.SetName(result_scalar, "result_scalar");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result_vector = OpAccessChain %_ptr_Function_v2float %mat %idx");
     EXPECT_INST("%result_scalar = OpAccessChain %_ptr_Function_float %mat %idx %idx");
 }
@@ -124,7 +123,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpCompositeExtract %int %vec 1");
 }
 
@@ -139,7 +138,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpVectorExtractDynamic %int %vec %idx");
 }
 
@@ -152,7 +151,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpAccessChain %_ptr_Function_int %vec %uint_1");
 }
 
@@ -167,7 +166,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result = OpAccessChain %_ptr_Function_int %vec %idx");
 }
 
@@ -182,7 +181,7 @@
         mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%14 = OpCompositeExtract %v4int %arr 1 2");
     EXPECT_INST("%result = OpVectorExtractDynamic %int %14 %idx");
 }
@@ -204,7 +203,7 @@
         mod.SetName(result_b, "result_b");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result_a = OpCompositeExtract %float %str 0");
     EXPECT_INST("%result_b = OpCompositeExtract %int %str 1 2");
 }
@@ -225,7 +224,7 @@
         mod.SetName(result_b, "result_b");
     });
 
-    ASSERT_TRUE(Generate()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
     EXPECT_INST("%result_a = OpAccessChain %_ptr_Function_float %str %uint_0");
     EXPECT_INST("%result_b = OpAccessChain %_ptr_Function_int %str %uint_1 %uint_2");
 }
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_binary_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_binary_test.cc
index 6d4d890..42e1c87 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_binary_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_binary_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/writer/spirv/ir/test_helper_ir.h"
 
-#include "gmock/gmock.h"
 #include "src/tint/ir/binary.h"
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -30,103 +29,67 @@
     enum ir::Binary::Kind kind;
     /// The expected SPIR-V instruction.
     std::string spirv_inst;
+    /// The expected SPIR-V result type name.
+    std::string spirv_type_name;
 };
 
-using Arithmetic = SpvGeneratorImplTestWithParam<BinaryTestCase>;
-TEST_P(Arithmetic, Scalar) {
+using Arithmetic_Bitwise = SpvGeneratorImplTestWithParam<BinaryTestCase>;
+TEST_P(Arithmetic_Bitwise, Scalar) {
     auto params = GetParam();
 
     auto* func = b.Function("foo", ty.void_());
     b.With(func->Block(), [&] {
-        b.Binary(params.kind, MakeScalarType(params.type), MakeScalarValue(params.type),
-                 MakeScalarValue(params.type));
+        auto* lhs = MakeScalarValue(params.type);
+        auto* rhs = MakeScalarValue(params.type);
+        auto* result = b.Binary(params.kind, MakeScalarType(params.type), lhs, rhs);
         b.Return(func);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = " + params.spirv_inst + " %" + params.spirv_type_name);
 }
-TEST_P(Arithmetic, Vector) {
+TEST_P(Arithmetic_Bitwise, Vector) {
     auto params = GetParam();
 
     auto* func = b.Function("foo", ty.void_());
     b.With(func->Block(), [&] {
-        b.Binary(params.kind, MakeVectorType(params.type), MakeVectorValue(params.type),
-                 MakeVectorValue(params.type));
+        auto* lhs = MakeVectorValue(params.type);
+        auto* rhs = MakeVectorValue(params.type);
+        auto* result = b.Binary(params.kind, MakeVectorType(params.type), lhs, rhs);
         b.Return(func);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
-}
-INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest_Binary_I32,
-                         Arithmetic,
-                         testing::Values(BinaryTestCase{kI32, ir::Binary::Kind::kAdd, "OpIAdd"},
-                                         BinaryTestCase{kI32, ir::Binary::Kind::kSubtract,
-                                                        "OpISub"}));
-INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest_Binary_U32,
-                         Arithmetic,
-                         testing::Values(BinaryTestCase{kU32, ir::Binary::Kind::kAdd, "OpIAdd"},
-                                         BinaryTestCase{kU32, ir::Binary::Kind::kSubtract,
-                                                        "OpISub"}));
-INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest_Binary_F32,
-                         Arithmetic,
-                         testing::Values(BinaryTestCase{kF32, ir::Binary::Kind::kAdd, "OpFAdd"},
-                                         BinaryTestCase{kF32, ir::Binary::Kind::kSubtract,
-                                                        "OpFSub"}));
-INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest_Binary_F16,
-                         Arithmetic,
-                         testing::Values(BinaryTestCase{kF16, ir::Binary::Kind::kAdd, "OpFAdd"},
-                                         BinaryTestCase{kF16, ir::Binary::Kind::kSubtract,
-                                                        "OpFSub"}));
-
-using Bitwise = SpvGeneratorImplTestWithParam<BinaryTestCase>;
-TEST_P(Bitwise, Scalar) {
-    auto params = GetParam();
-
-    auto* func = b.Function("foo", ty.void_());
-    b.With(func->Block(), [&] {
-        b.Binary(params.kind, MakeScalarType(params.type), MakeScalarValue(params.type),
-                 MakeScalarValue(params.type));
-        b.Return(func);
-    });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
-}
-TEST_P(Bitwise, Vector) {
-    auto params = GetParam();
-
-    auto* func = b.Function("foo", ty.void_());
-    b.With(func->Block(), [&] {
-        b.Binary(params.kind, MakeVectorType(params.type), MakeVectorValue(params.type),
-                 MakeVectorValue(params.type));
-        b.Return(func);
-    });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = " + params.spirv_inst + " %v2" + params.spirv_type_name);
 }
 INSTANTIATE_TEST_SUITE_P(
     SpvGeneratorImplTest_Binary_I32,
-    Bitwise,
-    testing::Values(BinaryTestCase{kI32, ir::Binary::Kind::kAnd, "OpBitwiseAnd"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kOr, "OpBitwiseOr"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kXor, "OpBitwiseXor"}));
+    Arithmetic_Bitwise,
+    testing::Values(BinaryTestCase{kI32, ir::Binary::Kind::kAdd, "OpIAdd", "int"},
+                    BinaryTestCase{kI32, ir::Binary::Kind::kSubtract, "OpISub", "int"},
+                    BinaryTestCase{kI32, ir::Binary::Kind::kAnd, "OpBitwiseAnd", "int"},
+                    BinaryTestCase{kI32, ir::Binary::Kind::kOr, "OpBitwiseOr", "int"},
+                    BinaryTestCase{kI32, ir::Binary::Kind::kXor, "OpBitwiseXor", "int"}));
 INSTANTIATE_TEST_SUITE_P(
     SpvGeneratorImplTest_Binary_U32,
-    Bitwise,
-    testing::Values(BinaryTestCase{kU32, ir::Binary::Kind::kAnd, "OpBitwiseAnd"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kOr, "OpBitwiseOr"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kXor, "OpBitwiseXor"}));
+    Arithmetic_Bitwise,
+    testing::Values(BinaryTestCase{kU32, ir::Binary::Kind::kAdd, "OpIAdd", "uint"},
+                    BinaryTestCase{kU32, ir::Binary::Kind::kSubtract, "OpISub", "uint"},
+                    BinaryTestCase{kU32, ir::Binary::Kind::kAnd, "OpBitwiseAnd", "uint"},
+                    BinaryTestCase{kU32, ir::Binary::Kind::kOr, "OpBitwiseOr", "uint"},
+                    BinaryTestCase{kU32, ir::Binary::Kind::kXor, "OpBitwiseXor", "uint"}));
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest_Binary_F32,
+    Arithmetic_Bitwise,
+    testing::Values(BinaryTestCase{kF32, ir::Binary::Kind::kAdd, "OpFAdd", "float"},
+                    BinaryTestCase{kF32, ir::Binary::Kind::kSubtract, "OpFSub", "float"}));
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest_Binary_F16,
+    Arithmetic_Bitwise,
+    testing::Values(BinaryTestCase{kF16, ir::Binary::Kind::kAdd, "OpFAdd", "half"},
+                    BinaryTestCase{kF16, ir::Binary::Kind::kSubtract, "OpFSub", "half"}));
 
 using Comparison = SpvGeneratorImplTestWithParam<BinaryTestCase>;
 TEST_P(Comparison, Scalar) {
@@ -134,15 +97,15 @@
 
     auto* func = b.Function("foo", ty.void_());
     b.With(func->Block(), [&] {
-        b.Binary(params.kind, ty.bool_(), MakeScalarValue(params.type),
-                 MakeScalarValue(params.type));
+        auto* lhs = MakeScalarValue(params.type);
+        auto* rhs = MakeScalarValue(params.type);
+        auto* result = b.Binary(params.kind, ty.bool_(), lhs, rhs);
         b.Return(func);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = " + params.spirv_inst + " %bool");
 }
 
 TEST_P(Comparison, Vector) {
@@ -150,87 +113,77 @@
 
     auto* func = b.Function("foo", ty.void_());
     b.With(func->Block(), [&] {
-        b.Binary(params.kind, ty.vec2(ty.bool_()), MakeVectorValue(params.type),
-                 MakeVectorValue(params.type));
+        auto* lhs = MakeVectorValue(params.type);
+        auto* rhs = MakeVectorValue(params.type);
+        auto* result = b.Binary(params.kind, ty.vec2<bool>(), lhs, rhs);
         b.Return(func);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = " + params.spirv_inst + " %v2bool");
 }
 INSTANTIATE_TEST_SUITE_P(
     SpvGeneratorImplTest_Binary_I32,
     Comparison,
-    testing::Values(BinaryTestCase{kI32, ir::Binary::Kind::kEqual, "OpIEqual"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kNotEqual, "OpINotEqual"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kGreaterThan, "OpSGreaterThan"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kGreaterThanEqual,
-                                   "OpSGreaterThanEqual"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kLessThan, "OpSLessThan"},
-                    BinaryTestCase{kI32, ir::Binary::Kind::kLessThanEqual, "OpSLessThanEqual"}));
+    testing::Values(
+        BinaryTestCase{kI32, ir::Binary::Kind::kEqual, "OpIEqual", "bool"},
+        BinaryTestCase{kI32, ir::Binary::Kind::kNotEqual, "OpINotEqual", "bool"},
+        BinaryTestCase{kI32, ir::Binary::Kind::kGreaterThan, "OpSGreaterThan", "bool"},
+        BinaryTestCase{kI32, ir::Binary::Kind::kGreaterThanEqual, "OpSGreaterThanEqual", "bool"},
+        BinaryTestCase{kI32, ir::Binary::Kind::kLessThan, "OpSLessThan", "bool"},
+        BinaryTestCase{kI32, ir::Binary::Kind::kLessThanEqual, "OpSLessThanEqual", "bool"}));
 INSTANTIATE_TEST_SUITE_P(
     SpvGeneratorImplTest_Binary_U32,
     Comparison,
-    testing::Values(BinaryTestCase{kU32, ir::Binary::Kind::kEqual, "OpIEqual"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kNotEqual, "OpINotEqual"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kGreaterThan, "OpUGreaterThan"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kGreaterThanEqual,
-                                   "OpUGreaterThanEqual"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kLessThan, "OpULessThan"},
-                    BinaryTestCase{kU32, ir::Binary::Kind::kLessThanEqual, "OpULessThanEqual"}));
+    testing::Values(
+        BinaryTestCase{kU32, ir::Binary::Kind::kEqual, "OpIEqual", "bool"},
+        BinaryTestCase{kU32, ir::Binary::Kind::kNotEqual, "OpINotEqual", "bool"},
+        BinaryTestCase{kU32, ir::Binary::Kind::kGreaterThan, "OpUGreaterThan", "bool"},
+        BinaryTestCase{kU32, ir::Binary::Kind::kGreaterThanEqual, "OpUGreaterThanEqual", "bool"},
+        BinaryTestCase{kU32, ir::Binary::Kind::kLessThan, "OpULessThan", "bool"},
+        BinaryTestCase{kU32, ir::Binary::Kind::kLessThanEqual, "OpULessThanEqual", "bool"}));
 INSTANTIATE_TEST_SUITE_P(
     SpvGeneratorImplTest_Binary_F32,
     Comparison,
-    testing::Values(BinaryTestCase{kF32, ir::Binary::Kind::kEqual, "OpFOrdEqual"},
-                    BinaryTestCase{kF32, ir::Binary::Kind::kNotEqual, "OpFOrdNotEqual"},
-                    BinaryTestCase{kF32, ir::Binary::Kind::kGreaterThan, "OpFOrdGreaterThan"},
-                    BinaryTestCase{kF32, ir::Binary::Kind::kGreaterThanEqual,
-                                   "OpFOrdGreaterThanEqual"},
-                    BinaryTestCase{kF32, ir::Binary::Kind::kLessThan, "OpFOrdLessThan"},
-                    BinaryTestCase{kF32, ir::Binary::Kind::kLessThanEqual, "OpFOrdLessThanEqual"}));
+    testing::Values(
+        BinaryTestCase{kF32, ir::Binary::Kind::kEqual, "OpFOrdEqual", "bool"},
+        BinaryTestCase{kF32, ir::Binary::Kind::kNotEqual, "OpFOrdNotEqual", "bool"},
+        BinaryTestCase{kF32, ir::Binary::Kind::kGreaterThan, "OpFOrdGreaterThan", "bool"},
+        BinaryTestCase{kF32, ir::Binary::Kind::kGreaterThanEqual, "OpFOrdGreaterThanEqual", "bool"},
+        BinaryTestCase{kF32, ir::Binary::Kind::kLessThan, "OpFOrdLessThan", "bool"},
+        BinaryTestCase{kF32, ir::Binary::Kind::kLessThanEqual, "OpFOrdLessThanEqual", "bool"}));
 INSTANTIATE_TEST_SUITE_P(
     SpvGeneratorImplTest_Binary_F16,
     Comparison,
-    testing::Values(BinaryTestCase{kF16, ir::Binary::Kind::kEqual, "OpFOrdEqual"},
-                    BinaryTestCase{kF16, ir::Binary::Kind::kNotEqual, "OpFOrdNotEqual"},
-                    BinaryTestCase{kF16, ir::Binary::Kind::kGreaterThan, "OpFOrdGreaterThan"},
-                    BinaryTestCase{kF16, ir::Binary::Kind::kGreaterThanEqual,
-                                   "OpFOrdGreaterThanEqual"},
-                    BinaryTestCase{kF16, ir::Binary::Kind::kLessThan, "OpFOrdLessThan"},
-                    BinaryTestCase{kF16, ir::Binary::Kind::kLessThanEqual, "OpFOrdLessThanEqual"}));
-INSTANTIATE_TEST_SUITE_P(
-    SpvGeneratorImplTest_Binary_Bool,
-    Comparison,
-    testing::Values(BinaryTestCase{kBool, ir::Binary::Kind::kEqual, "OpLogicalEqual"},
-                    BinaryTestCase{kBool, ir::Binary::Kind::kNotEqual, "OpLogicalNotEqual"}));
+    testing::Values(
+        BinaryTestCase{kF16, ir::Binary::Kind::kEqual, "OpFOrdEqual", "bool"},
+        BinaryTestCase{kF16, ir::Binary::Kind::kNotEqual, "OpFOrdNotEqual", "bool"},
+        BinaryTestCase{kF16, ir::Binary::Kind::kGreaterThan, "OpFOrdGreaterThan", "bool"},
+        BinaryTestCase{kF16, ir::Binary::Kind::kGreaterThanEqual, "OpFOrdGreaterThanEqual", "bool"},
+        BinaryTestCase{kF16, ir::Binary::Kind::kLessThan, "OpFOrdLessThan", "bool"},
+        BinaryTestCase{kF16, ir::Binary::Kind::kLessThanEqual, "OpFOrdLessThanEqual", "bool"}));
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest_Binary_Bool,
+                         Comparison,
+                         testing::Values(BinaryTestCase{kBool, ir::Binary::Kind::kEqual,
+                                                        "OpLogicalEqual", "bool"},
+                                         BinaryTestCase{kBool, ir::Binary::Kind::kNotEqual,
+                                                        "OpLogicalNotEqual", "bool"}));
 
 TEST_F(SpvGeneratorImplTest, Binary_Chain) {
     auto* func = b.Function("foo", ty.void_());
 
     b.With(func->Block(), [&] {
-        auto* a = b.Subtract(ty.i32(), 1_i, 2_i);
-        b.Add(ty.i32(), a, a);
+        auto* sub = b.Subtract(ty.i32(), 1_i, 2_i);
+        auto* add = b.Add(ty.i32(), sub, sub);
         b.Return(func);
+        mod.SetName(sub, "sub");
+        mod.SetName(add, "add");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%6 = OpTypeInt 32 1
-%7 = OpConstant %6 1
-%8 = OpConstant %6 2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%5 = OpISub %6 %7 %8
-%9 = OpIAdd %6 %5 %5
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%sub = OpISub %int %int_1 %int_2");
+    EXPECT_INST("%add = OpIAdd %int %sub %sub");
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc
index f9bbcd6..758bdf8 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_builtin_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/writer/spirv/ir/test_helper_ir.h"
 
-#include "gmock/gmock.h"
 #include "src/tint/builtin/function.h"
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -43,10 +42,8 @@
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.spirv_inst);
 }
 TEST_P(Builtin_1arg, Vector) {
     auto params = GetParam();
@@ -57,10 +54,8 @@
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.spirv_inst);
 }
 INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
                          Builtin_1arg,
@@ -71,45 +66,36 @@
 TEST_F(SpvGeneratorImplTest, Builtin_Abs_u32) {
     auto* func = b.Function("foo", MakeScalarType(kU32));
     b.With(func->Block(), [&] {
-        auto* result = b.Call(MakeScalarType(kU32), builtin::Function::kAbs, MakeScalarValue(kU32));
+        auto* arg = MakeScalarValue(kU32);
+        auto* result = b.Call(MakeScalarType(kU32), builtin::Function::kAbs, arg);
         b.Return(func, result);
+        mod.SetName(arg, "arg");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 0
-%3 = OpTypeFunction %2
-%5 = OpConstant %2 1
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturnValue %5
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+        %foo = OpFunction %uint None %3
+          %4 = OpLabel
+               OpReturnValue %arg
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Builtin_Abs_vec2u) {
     auto* func = b.Function("foo", MakeVectorType(kU32));
     b.With(func->Block(), [&] {
-        auto* result = b.Call(MakeVectorType(kU32), builtin::Function::kAbs, MakeVectorValue(kU32));
+        auto* arg = MakeVectorValue(kU32);
+        auto* result = b.Call(MakeVectorType(kU32), builtin::Function::kAbs, arg);
         b.Return(func, result);
+        mod.SetName(arg, "arg");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%3 = OpTypeInt 32 0
-%2 = OpTypeVector %3 2
-%4 = OpTypeFunction %2
-%7 = OpConstant %3 42
-%8 = OpConstant %3 10
-%6 = OpConstantComposite %2 %7 %8
-%1 = OpFunction %2 None %4
-%5 = OpLabel
-OpReturnValue %6
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+        %foo = OpFunction %v2uint None %4
+          %5 = OpLabel
+               OpReturnValue %arg
+               OpFunctionEnd
 )");
 }
 
@@ -125,10 +111,8 @@
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.spirv_inst);
 }
 TEST_P(Builtin_2arg, Vector) {
     auto params = GetParam();
@@ -140,10 +124,8 @@
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_THAT(DumpModule(generator_.Module()), ::testing::HasSubstr(params.spirv_inst));
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(params.spirv_inst);
 }
 INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
                          Builtin_2arg,
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_constant_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_constant_test.cc
index 40fae96..e526f0b 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_constant_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_constant_test.cc
@@ -22,46 +22,41 @@
 TEST_F(SpvGeneratorImplTest, Constant_Bool) {
     generator_.Constant(b.Constant(true));
     generator_.Constant(b.Constant(false));
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeBool
-%1 = OpConstantTrue %2
-%3 = OpConstantFalse %2
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%true = OpConstantTrue %bool");
+    EXPECT_INST("%false = OpConstantFalse %bool");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_I32) {
     generator_.Constant(b.Constant(i32(42)));
     generator_.Constant(b.Constant(i32(-1)));
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeInt 32 1
-%1 = OpConstant %2 42
-%3 = OpConstant %2 -1
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%int_42 = OpConstant %int 42");
+    EXPECT_INST("%int_n1 = OpConstant %int -1");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_U32) {
     generator_.Constant(b.Constant(u32(42)));
     generator_.Constant(b.Constant(u32(4000000000)));
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeInt 32 0
-%1 = OpConstant %2 42
-%3 = OpConstant %2 4000000000
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%uint_42 = OpConstant %uint 42");
+    EXPECT_INST("%uint_4000000000 = OpConstant %uint 4000000000");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_F32) {
     generator_.Constant(b.Constant(f32(42)));
     generator_.Constant(b.Constant(f32(-1)));
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeFloat 32
-%1 = OpConstant %2 42
-%3 = OpConstant %2 -1
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%float_42 = OpConstant %float 42");
+    EXPECT_INST("%float_n1 = OpConstant %float -1");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_F16) {
     generator_.Constant(b.Constant(f16(42)));
     generator_.Constant(b.Constant(f16(-1)));
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeFloat 16
-%1 = OpConstant %2 0x1.5p+5
-%3 = OpConstant %2 -0x1p+0
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%half_0x1_5p_5 = OpConstant %half 0x1.5p+5");
+    EXPECT_INST("%half_n0x1p_0 = OpConstant %half -0x1p+0");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Vec4Bool) {
@@ -71,12 +66,8 @@
         utils::Vector{const_bool(true), const_bool(false), const_bool(false), const_bool(true)});
 
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeBool
-%2 = OpTypeVector %3 4
-%4 = OpConstantTrue %3
-%5 = OpConstantFalse %3
-%1 = OpConstantComposite %2 %4 %5 %5 %4
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %v4bool %true %false %false %true");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Vec2i) {
@@ -84,12 +75,8 @@
     auto* v = mod.constant_values.Composite(ty.vec2(ty.i32()),
                                             utils::Vector{const_i32(42), const_i32(-1)});
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeInt 32 1
-%2 = OpTypeVector %3 2
-%4 = OpConstant %3 42
-%5 = OpConstant %3 -1
-%1 = OpConstantComposite %2 %4 %5
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %v2int %int_42 %int_n1");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Vec3u) {
@@ -97,13 +84,8 @@
     auto* v = mod.constant_values.Composite(
         ty.vec3(ty.u32()), utils::Vector{const_u32(42), const_u32(0), const_u32(4000000000)});
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeInt 32 0
-%2 = OpTypeVector %3 3
-%4 = OpConstant %3 42
-%5 = OpConstant %3 0
-%6 = OpConstant %3 4000000000
-%1 = OpConstantComposite %2 %4 %5 %6
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %v3uint %uint_42 %uint_0 %uint_4000000000");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Vec4f) {
@@ -112,14 +94,8 @@
         ty.vec4(ty.f32()),
         utils::Vector{const_f32(42), const_f32(0), const_f32(0.25), const_f32(-1)});
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeFloat 32
-%2 = OpTypeVector %3 4
-%4 = OpConstant %3 42
-%5 = OpConstant %3 0
-%6 = OpConstant %3 0.25
-%7 = OpConstant %3 -1
-%1 = OpConstantComposite %2 %4 %5 %6 %7
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %v4float %float_42 %float_0 %float_0_25 %float_n1");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Vec2h) {
@@ -127,12 +103,8 @@
     auto* v = mod.constant_values.Composite(ty.vec2(ty.f16()),
                                             utils::Vector{const_f16(42), const_f16(0.25)});
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeFloat 16
-%2 = OpTypeVector %3 2
-%4 = OpConstant %3 0x1.5p+5
-%5 = OpConstant %3 0x1p-2
-%1 = OpConstantComposite %2 %4 %5
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %v2half %half_0x1_5p_5 %half_0x1pn2");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Mat2x3f) {
@@ -147,18 +119,17 @@
                 ty.vec3(f32), utils::Vector{const_f32(-42), const_f32(0), const_f32(-0.25)}),
         });
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypeMatrix %3 2
-%6 = OpConstant %4 42
-%7 = OpConstant %4 -1
-%8 = OpConstant %4 0.25
-%5 = OpConstantComposite %3 %6 %7 %8
-%10 = OpConstant %4 -42
-%11 = OpConstant %4 0
-%12 = OpConstant %4 -0.25
-%9 = OpConstantComposite %3 %10 %11 %12
-%1 = OpConstantComposite %2 %5 %9
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+   %float_42 = OpConstant %float 42
+   %float_n1 = OpConstant %float -1
+ %float_0_25 = OpConstant %float 0.25
+          %5 = OpConstantComposite %v3float %float_42 %float_n1 %float_0_25
+  %float_n42 = OpConstant %float -42
+    %float_0 = OpConstant %float 0
+%float_n0_25 = OpConstant %float -0.25
+          %9 = OpConstantComposite %v3float %float_n42 %float_0 %float_n0_25
+          %1 = OpConstantComposite %mat2v3float %5 %9
 )");
 }
 
@@ -177,21 +148,20 @@
                                 ty.vec2(f16), utils::Vector{const_f16(0.5), const_f16(-0)}),
                         });
     generator_.Constant(b.Constant(v));
-    EXPECT_EQ(DumpTypes(), R"(%4 = OpTypeFloat 16
-%3 = OpTypeVector %4 2
-%2 = OpTypeMatrix %3 4
-%6 = OpConstant %4 0x1.5p+5
-%7 = OpConstant %4 -0x1p+0
-%5 = OpConstantComposite %3 %6 %7
-%9 = OpConstant %4 0x0p+0
-%10 = OpConstant %4 0x1p-2
-%8 = OpConstantComposite %3 %9 %10
-%12 = OpConstant %4 -0x1.5p+5
-%13 = OpConstant %4 0x1p+0
-%11 = OpConstantComposite %3 %12 %13
-%15 = OpConstant %4 0x1p-1
-%14 = OpConstantComposite %3 %15 %9
-%1 = OpConstantComposite %2 %5 %8 %11 %14
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+%half_0x1_5p_5 = OpConstant %half 0x1.5p+5
+%half_n0x1p_0 = OpConstant %half -0x1p+0
+          %5 = OpConstantComposite %v2half %half_0x1_5p_5 %half_n0x1p_0
+%half_0x0p_0 = OpConstant %half 0x0p+0
+%half_0x1pn2 = OpConstant %half 0x1p-2
+          %8 = OpConstantComposite %v2half %half_0x0p_0 %half_0x1pn2
+%half_n0x1_5p_5 = OpConstant %half -0x1.5p+5
+%half_0x1p_0 = OpConstant %half 0x1p+0
+         %11 = OpConstantComposite %v2half %half_n0x1_5p_5 %half_0x1p_0
+%half_0x1pn1 = OpConstant %half 0x1p-1
+         %14 = OpConstantComposite %v2half %half_0x1pn1 %half_0x0p_0
+          %1 = OpConstantComposite %mat4v2half %5 %8 %11 %14
 )");
 }
 
@@ -204,16 +174,8 @@
                                                                  mod.constant_values.Get(4_i),
                                                              });
     generator_.Constant(b.Constant(arr));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeInt 32 1
-%5 = OpTypeInt 32 0
-%4 = OpConstant %5 4
-%2 = OpTypeArray %3 %4
-%6 = OpConstant %3 1
-%7 = OpConstant %3 2
-%8 = OpConstant %3 3
-%9 = OpConstant %3 4
-%1 = OpConstantComposite %2 %6 %7 %8 %9
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %_arr_int_uint_4 %int_1 %int_2 %int_3 %int_4");
 }
 
 TEST_F(SpvGeneratorImplTest, Constant_Array_Array_I32) {
@@ -231,21 +193,14 @@
                                                                                       inner,
                                                                                   });
     generator_.Constant(b.Constant(arr));
-    EXPECT_EQ(DumpTypes(), R"(%4 = OpTypeInt 32 1
-%6 = OpTypeInt 32 0
-%5 = OpConstant %6 4
-%3 = OpTypeArray %4 %5
-%2 = OpTypeArray %3 %5
-%8 = OpConstant %4 1
-%9 = OpConstant %4 2
-%10 = OpConstant %4 3
-%11 = OpConstant %4 4
-%7 = OpConstantComposite %3 %8 %9 %10 %11
-%1 = OpConstantComposite %2 %7 %7 %7 %7
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %7 = OpConstantComposite %_arr_int_uint_4 %int_1 %int_2 %int_3 %int_4
+          %1 = OpConstantComposite %_arr__arr_int_uint_4_uint_4 %7 %7 %7 %7
 )");
 }
 
-TEST_F(SpvGeneratorImplTest, Struct) {
+TEST_F(SpvGeneratorImplTest, Constant_Struct) {
     auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
                                                               {mod.symbols.New("a"), ty.i32()},
                                                               {mod.symbols.New("b"), ty.u32()},
@@ -257,15 +212,8 @@
                                                           mod.constant_values.Get(3_f),
                                                       });
     generator_.Constant(b.Constant(str));
-    EXPECT_EQ(DumpTypes(), R"(%3 = OpTypeInt 32 1
-%4 = OpTypeInt 32 0
-%5 = OpTypeFloat 32
-%2 = OpTypeStruct %3 %4 %5
-%6 = OpConstant %3 1
-%7 = OpConstant %4 2
-%8 = OpConstant %5 3
-%1 = OpConstantComposite %2 %6 %7 %8
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%1 = OpConstantComposite %MyStruct %int_1 %uint_2 %float_3");
 }
 
 // Test that we do not emit the same constant more than once.
@@ -273,9 +221,8 @@
     generator_.Constant(b.Constant(i32(42)));
     generator_.Constant(b.Constant(i32(42)));
     generator_.Constant(b.Constant(i32(42)));
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeInt 32 1
-%1 = OpConstant %2 42
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%int_42 = OpConstant %int 42");
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_construct_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_construct_test.cc
index 7ba7cff..0f33beb 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_construct_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_construct_test.cc
@@ -23,93 +23,54 @@
 TEST_F(SpvGeneratorImplTest, Construct_Vector) {
     auto* func = b.Function("foo", ty.vec4<i32>());
     func->SetParams({
-        b.FunctionParam(ty.i32()),
-        b.FunctionParam(ty.i32()),
-        b.FunctionParam(ty.i32()),
-        b.FunctionParam(ty.i32()),
+        b.FunctionParam("a", ty.i32()),
+        b.FunctionParam("b", ty.i32()),
+        b.FunctionParam("c", ty.i32()),
+        b.FunctionParam("d", ty.i32()),
+    });
+    b.With(func->Block(), [&] {
+        auto* result = b.Construct(ty.vec4<i32>(), func->Params());
+        b.Return(func, result);
+        mod.SetName(result, "result");
     });
 
-    b.With(func->Block(), [&] { b.Return(func, b.Construct(ty.vec4<i32>(), func->Params())); });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%3 = OpTypeInt 32 1
-%2 = OpTypeVector %3 4
-%8 = OpTypeFunction %2 %3 %3 %3 %3
-%1 = OpFunction %2 None %8
-%4 = OpFunctionParameter %3
-%5 = OpFunctionParameter %3
-%6 = OpFunctionParameter %3
-%7 = OpFunctionParameter %3
-%9 = OpLabel
-%10 = OpCompositeConstruct %2 %4 %5 %6 %7
-OpReturnValue %10
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = OpCompositeConstruct %v4int %a %b %c %d");
 }
 
 TEST_F(SpvGeneratorImplTest, Construct_Matrix) {
     auto* func = b.Function("foo", ty.mat3x4<f32>());
     func->SetParams({
-        b.FunctionParam(ty.vec4<f32>()),
-        b.FunctionParam(ty.vec4<f32>()),
-        b.FunctionParam(ty.vec4<f32>()),
+        b.FunctionParam("a", ty.vec4<f32>()),
+        b.FunctionParam("b", ty.vec4<f32>()),
+        b.FunctionParam("c", ty.vec4<f32>()),
+    });
+    b.With(func->Block(), [&] {
+        auto* result = b.Construct(ty.mat3x4<f32>(), func->Params());
+        b.Return(func, result);
+        mod.SetName(result, "result");
     });
 
-    b.With(func->Block(), [&] { b.Return(func, b.Construct(ty.mat3x4<f32>(), func->Params())); });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 4
-%2 = OpTypeMatrix %3 3
-%8 = OpTypeFunction %2 %3 %3 %3
-%1 = OpFunction %2 None %8
-%5 = OpFunctionParameter %3
-%6 = OpFunctionParameter %3
-%7 = OpFunctionParameter %3
-%9 = OpLabel
-%10 = OpCompositeConstruct %2 %5 %6 %7
-OpReturnValue %10
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = OpCompositeConstruct %mat3v4float %a %b %c");
 }
 
 TEST_F(SpvGeneratorImplTest, Construct_Array) {
     auto* func = b.Function("foo", ty.array<f32, 4>());
     func->SetParams({
-        b.FunctionParam(ty.f32()),
-        b.FunctionParam(ty.f32()),
-        b.FunctionParam(ty.f32()),
-        b.FunctionParam(ty.f32()),
+        b.FunctionParam("a", ty.f32()),
+        b.FunctionParam("b", ty.f32()),
+        b.FunctionParam("c", ty.f32()),
+        b.FunctionParam("d", ty.f32()),
+    });
+    b.With(func->Block(), [&] {
+        auto* result = b.Construct(ty.array<f32, 4>(), func->Params());
+        b.Return(func, result);
+        mod.SetName(result, "result");
     });
 
-    b.With(func->Block(), [&] { b.Return(func, b.Construct(ty.array<f32, 4>(), func->Params())); });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-OpDecorate %2 ArrayStride 4
-%3 = OpTypeFloat 32
-%5 = OpTypeInt 32 0
-%4 = OpConstant %5 4
-%2 = OpTypeArray %3 %4
-%10 = OpTypeFunction %2 %3 %3 %3 %3
-%1 = OpFunction %2 None %10
-%6 = OpFunctionParameter %3
-%7 = OpFunctionParameter %3
-%8 = OpFunctionParameter %3
-%9 = OpFunctionParameter %3
-%11 = OpLabel
-%12 = OpCompositeConstruct %2 %6 %7 %8 %9
-OpReturnValue %12
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = OpCompositeConstruct %_arr_float_uint_4 %a %b %c %d");
 }
 
 TEST_F(SpvGeneratorImplTest, Construct_Struct) {
@@ -119,42 +80,20 @@
                                                    {mod.symbols.Register("b"), ty.u32()},
                                                    {mod.symbols.Register("c"), ty.vec4<f32>()},
                                                });
-
     auto* func = b.Function("foo", str);
     func->SetParams({
-        b.FunctionParam(ty.i32()),
-        b.FunctionParam(ty.u32()),
-        b.FunctionParam(ty.vec4<f32>()),
+        b.FunctionParam("a", ty.i32()),
+        b.FunctionParam("b", ty.u32()),
+        b.FunctionParam("c", ty.vec4<f32>()),
+    });
+    b.With(func->Block(), [&] {
+        auto* result = b.Construct(str, func->Params());
+        b.Return(func, result);
+        mod.SetName(result, "result");
     });
 
-    b.With(func->Block(), [&] { b.Return(func, b.Construct(str, func->Params())); });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-OpMemberName %2 0 "a"
-OpMemberName %2 1 "b"
-OpMemberName %2 2 "c"
-OpName %2 "MyStruct"
-OpMemberDecorate %2 0 Offset 0
-OpMemberDecorate %2 1 Offset 4
-OpMemberDecorate %2 2 Offset 16
-%3 = OpTypeInt 32 1
-%4 = OpTypeInt 32 0
-%6 = OpTypeFloat 32
-%5 = OpTypeVector %6 4
-%2 = OpTypeStruct %3 %4 %5
-%10 = OpTypeFunction %2 %3 %4 %5
-%1 = OpFunction %2 None %10
-%7 = OpFunctionParameter %3
-%8 = OpFunctionParameter %4
-%9 = OpFunctionParameter %5
-%11 = OpLabel
-%12 = OpCompositeConstruct %2 %7 %8 %9
-OpReturnValue %12
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = OpCompositeConstruct %MyStruct %a %b %c");
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_function_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_function_test.cc
index 8450235..5251157 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_function_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_function_test.cc
@@ -17,151 +17,215 @@
 namespace tint::writer::spirv {
 namespace {
 
+using namespace tint::builtin::fluent_types;  // NOLINT
+using namespace tint::number_suffixes;        // NOLINT
+
 TEST_F(SpvGeneratorImplTest, Function_Empty) {
     auto* func = b.Function("foo", ty.void_());
-    func->Block()->Append(b.Return(func));
+    b.With(func->Block(), [&] {  //
+        b.Return(func);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+        %foo = OpFunction %void None %3
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 // Test that we do not emit the same function type more than once.
 TEST_F(SpvGeneratorImplTest, Function_DeduplicateType) {
-    auto* func = b.Function("foo", ty.void_());
-    func->Block()->Append(b.Return(func));
+    auto* func_a = b.Function("func_a", ty.void_());
+    b.With(func_a->Block(), [&] {  //
+        b.Return(func_a);
+    });
+    auto* func_b = b.Function("func_b", ty.void_());
+    b.With(func_b->Block(), [&] {  //
+        b.Return(func_b);
+    });
+    auto* func_c = b.Function("func_c", ty.void_());
+    b.With(func_c->Block(), [&] {  //
+        b.Return(func_c);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               ; Types, variables and constants
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
 
-    generator_.EmitFunction(func);
-    generator_.EmitFunction(func);
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeVoid
-%3 = OpTypeFunction %2
+               ; Function func_a
+     %func_a = OpFunction %void None %3
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+               ; Function func_b
+     %func_b = OpFunction %void None %3
+          %6 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+               ; Function func_c
+     %func_c = OpFunction %void None %3
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Compute) {
     auto* func =
         b.Function("main", ty.void_(), ir::Function::PipelineStage::kCompute, {{32, 4, 1}});
-    func->Block()->Append(b.Return(func));
+    b.With(func->Block(), [&] {  //
+        b.Return(func);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 32 4 1
 
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint GLCompute %1 "main"
-OpExecutionMode %1 LocalSize 32 4 1
-OpName %1 "main"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
+               ; Debug Information
+               OpName %main "main"  ; id %1
+
+               ; Types, variables and constants
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+
+               ; Function main
+       %main = OpFunction %void None %3
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Fragment) {
     auto* func = b.Function("main", ty.void_(), ir::Function::PipelineStage::kFragment);
-    func->Block()->Append(b.Return(func));
+    b.With(func->Block(), [&] {  //
+        b.Return(func);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
 
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint Fragment %1 "main"
-OpExecutionMode %1 OriginUpperLeft
-OpName %1 "main"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
+               ; Debug Information
+               OpName %main "main"  ; id %1
+
+               ; Types, variables and constants
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+
+               ; Function main
+       %main = OpFunction %void None %3
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Vertex) {
     auto* func = b.Function("main", ty.void_(), ir::Function::PipelineStage::kVertex);
-    func->Block()->Append(b.Return(func));
+    b.With(func->Block(), [&] {  //
+        b.Return(func);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpEntryPoint Vertex %main "main"
 
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint Vertex %1 "main"
-OpName %1 "main"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
+               ; Debug Information
+               OpName %main "main"  ; id %1
+
+               ; Types, variables and constants
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+
+               ; Function main
+       %main = OpFunction %void None %3
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Multiple) {
     auto* f1 = b.Function("main1", ty.void_(), ir::Function::PipelineStage::kCompute, {{32, 4, 1}});
-    f1->Block()->Append(b.Return(f1));
+    b.With(f1->Block(), [&] {  //
+        b.Return(f1);
+    });
 
     auto* f2 = b.Function("main2", ty.void_(), ir::Function::PipelineStage::kCompute, {{8, 2, 16}});
-    f2->Block()->Append(b.Return(f2));
+    b.With(f2->Block(), [&] {  //
+        b.Return(f2);
+    });
 
     auto* f3 = b.Function("main3", ty.void_(), ir::Function::PipelineStage::kFragment);
-    f3->Block()->Append(b.Return(f3));
+    b.With(f3->Block(), [&] {  //
+        b.Return(f3);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    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
 
-    generator_.EmitFunction(f1);
-    generator_.EmitFunction(f2);
-    generator_.EmitFunction(f3);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint GLCompute %1 "main1"
-OpEntryPoint GLCompute %5 "main2"
-OpEntryPoint Fragment %7 "main3"
-OpExecutionMode %1 LocalSize 32 4 1
-OpExecutionMode %5 LocalSize 8 2 16
-OpExecutionMode %7 OriginUpperLeft
-OpName %1 "main1"
-OpName %5 "main2"
-OpName %7 "main3"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-%5 = OpFunction %2 None %3
-%6 = OpLabel
-OpReturn
-OpFunctionEnd
-%7 = OpFunction %2 None %3
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+               ; 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(SpvGeneratorImplTest, Function_ReturnValue) {
     auto* func = b.Function("foo", ty.i32());
-    func->Block()->Append(b.Return(func, i32(42)));
+    b.With(func->Block(), [&] {  //
+        b.Return(func, 42_i);
+    });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %3 = OpTypeFunction %int
+     %int_42 = OpConstant %int 42
+       %void = OpTypeVoid
+          %8 = OpTypeFunction %void
 
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%5 = OpConstant %2 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturnValue %5
-OpFunctionEnd
+               ; Function foo
+        %foo = OpFunction %int None %3
+          %4 = OpLabel
+               OpReturnValue %int_42
+               OpFunctionEnd
 )");
 }
 
@@ -177,97 +241,61 @@
         b.Return(func, result);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %5 = OpTypeFunction %int %int %int
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
 
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-OpName %3 "x"
-OpName %4 "y"
-%2 = OpTypeInt 32 1
-%5 = OpTypeFunction %2 %2 %2
-%1 = OpFunction %2 None %5
-%3 = OpFunctionParameter %2
-%4 = OpFunctionParameter %2
-%6 = OpLabel
-%7 = OpIAdd %2 %3 %4
-OpReturnValue %7
-OpFunctionEnd
+               ; Function foo
+        %foo = OpFunction %int None %5
+          %x = OpFunctionParameter %int
+          %y = OpFunctionParameter %int
+          %6 = OpLabel
+          %7 = OpIAdd %int %x %y
+               OpReturnValue %7
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Function_Call) {
-    auto* i32_ty = ty.i32();
-    auto* x = b.FunctionParam(i32_ty);
-    auto* y = b.FunctionParam(i32_ty);
-    auto* foo = b.Function("foo", i32_ty);
+    auto* i32 = ty.i32();
+    auto* x = b.FunctionParam("x", i32);
+    auto* y = b.FunctionParam("y", i32);
+    auto* foo = b.Function("foo", i32);
     foo->SetParams({x, y});
 
     b.With(foo->Block(), [&] {
-        auto* result = b.Add(i32_ty, x, y);
+        auto* result = b.Add(i32, x, y);
         b.Return(foo, result);
     });
 
     auto* bar = b.Function("bar", ty.void_());
     b.With(bar->Block(), [&] {
-        b.Call(i32_ty, foo, i32(2), i32(3));
+        auto* result = b.Call(i32, foo, 2_i, 3_i);
         b.Return(bar);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(foo);
-    generator_.EmitFunction(bar);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-OpName %8 "bar"
-%2 = OpTypeInt 32 1
-%5 = OpTypeFunction %2 %2 %2
-%9 = OpTypeVoid
-%10 = OpTypeFunction %9
-%13 = OpConstant %2 2
-%14 = OpConstant %2 3
-%1 = OpFunction %2 None %5
-%3 = OpFunctionParameter %2
-%4 = OpFunctionParameter %2
-%6 = OpLabel
-%7 = OpIAdd %2 %3 %4
-OpReturnValue %7
-OpFunctionEnd
-%8 = OpFunction %9 None %10
-%11 = OpLabel
-%12 = OpFunctionCall %2 %1 %13 %14
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = OpFunctionCall %int %foo %int_2 %int_3");
 }
 
 TEST_F(SpvGeneratorImplTest, Function_Call_Void) {
     auto* foo = b.Function("foo", ty.void_());
-    foo->Block()->Append(b.Return(foo));
+    b.With(foo->Block(), [&] {  //
+        b.Return(foo);
+    });
 
     auto* bar = b.Function("bar", ty.void_());
     b.With(bar->Block(), [&] {
-        b.Call(ty.void_(), foo, utils::Empty);
+        auto* result = b.Call(ty.void_(), foo);
         b.Return(bar);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(foo);
-    generator_.EmitFunction(bar);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-OpName %5 "bar"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpReturn
-OpFunctionEnd
-%5 = OpFunction %2 None %3
-%6 = OpLabel
-%7 = OpFunctionCall %2 %1
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%result = OpFunctionCall %void %foo");
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_if_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_if_test.cc
index 892409d..b548745 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_if_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_if_test.cc
@@ -21,316 +21,270 @@
 
 TEST_F(SpvGeneratorImplTest, If_TrueEmpty_FalseEmpty) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        b.With(i->True(), [&] {  //
+            b.ExitIf(i);
+        });
+        b.With(i->False(), [&] {  //
+            b.ExitIf(i);
+        });
+        b.Return(func);
+    });
 
-    auto* i = b.If(true);
-    i->True()->Append(b.ExitIf(i));
-    i->False()->Append(b.ExitIf(i));
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeBool
-%6 = OpConstantTrue %7
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %6 %5 %5
-%5 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %5 %5
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_FalseEmpty) {
     auto* func = b.Function("foo", ty.void_());
-
-    auto* i = b.If(true);
-    i->False()->Append(b.ExitIf(i));
-
-    b.With(i->True(), [&] {
-        b.Add(ty.i32(), 1_i, 1_i);
-        b.ExitIf(i);
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        b.With(i->True(), [&] {
+            b.Add(ty.i32(), 1_i, 1_i);
+            b.ExitIf(i);
+        });
+        b.With(i->False(), [&] {  //
+            b.ExitIf(i);
+        });
+        b.Return(func);
     });
 
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%8 = OpTypeBool
-%7 = OpConstantTrue %8
-%10 = OpTypeInt 32 1
-%11 = OpConstant %10 1
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %7 %6 %5
-%6 = OpLabel
-%9 = OpIAdd %10 %11 %11
-OpBranch %5
-%5 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %6 %5
+          %6 = OpLabel
+          %9 = OpIAdd %int %int_1 %int_1
+               OpBranch %5
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_TrueEmpty) {
     auto* func = b.Function("foo", ty.void_());
-
-    auto* i = b.If(true);
-    i->True()->Append(b.ExitIf(i));
-
-    b.With(i->False(), [&] {
-        b.Add(ty.i32(), 1_i, 1_i);
-        b.ExitIf(i);
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        b.With(i->True(), [&] {  //
+            b.ExitIf(i);
+        });
+        b.With(i->False(), [&] {
+            b.Add(ty.i32(), 1_i, 1_i);
+            b.ExitIf(i);
+        });
+        b.Return(func);
     });
 
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%8 = OpTypeBool
-%7 = OpConstantTrue %8
-%10 = OpTypeInt 32 1
-%11 = OpConstant %10 1
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %7 %5 %6
-%6 = OpLabel
-%9 = OpIAdd %10 %11 %11
-OpBranch %5
-%5 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %5 %6
+          %6 = OpLabel
+          %9 = OpIAdd %int %int_1 %int_1
+               OpBranch %5
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_BothBranchesReturn) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        b.With(i->True(), [&] {  //
+            b.Return(func);
+        });
+        b.With(i->False(), [&] {  //
+            b.Return(func);
+        });
+        b.Unreachable();
+    });
 
-    auto* i = b.If(true);
-    i->True()->Append(b.Return(func));
-    i->False()->Append(b.Return(func));
-
-    func->Block()->Append(i);
-    func->Block()->Append(b.Unreachable());
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpReturn
-%7 = OpLabel
-OpReturn
-%5 = OpLabel
-OpUnreachable
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %5 %5
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_Phi_SingleValue) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        i->SetResults(b.InstructionResult(ty.i32()));
+        b.With(i->True(), [&] {  //
+            b.ExitIf(i, 10_i);
+        });
+        b.With(i->False(), [&] {  //
+            b.ExitIf(i, 20_i);
+        });
+        b.Return(func, i);
+    });
 
-    auto* i = b.If(true);
-    i->SetResults(b.InstructionResult(ty.i32()));
-    i->True()->Append(b.ExitIf(i, 10_i));
-    i->False()->Append(b.ExitIf(i, 20_i));
-
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func, i));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%11 = OpConstant %2 10
-%12 = OpConstant %2 20
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpBranch %5
-%7 = OpLabel
-OpBranch %5
-%5 = OpLabel
-%10 = OpPhi %2 %11 %6 %12 %7
-OpReturnValue %10
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %6 %7
+          %6 = OpLabel
+               OpBranch %5
+          %7 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %10 = OpPhi %int %int_10 %6 %int_20 %7
+               OpReturnValue %10
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_Phi_SingleValue_TrueReturn) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        i->SetResults(b.InstructionResult(ty.i32()));
+        b.With(i->True(), [&] {  //
+            b.Return(func, 42_i);
+        });
+        b.With(i->False(), [&] {  //
+            b.ExitIf(i, 20_i);
+        });
+        b.Return(func, i);
+    });
 
-    auto* i = b.If(true);
-    i->SetResults(b.InstructionResult(ty.i32()));
-    i->True()->Append(b.Return(func, 42_i));
-    i->False()->Append(b.ExitIf(i, 20_i));
-
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func, i));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%10 = OpConstant %2 42
-%12 = OpConstant %2 20
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpReturnValue %10
-%7 = OpLabel
-OpBranch %5
-%5 = OpLabel
-%11 = OpPhi %2 %12 %7
-OpReturnValue %11
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%17 = OpUndef %int");
+    EXPECT_INST(R"(
+               OpSelectionMerge %11 None
+               OpBranchConditional %true %12 %13
+         %12 = OpLabel
+               OpStore %continue_execution %false
+               OpStore %return_value %int_42
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %16 = OpPhi %int %17 %12 %int_20 %13
+         %19 = OpLoad %bool %continue_execution
+               OpSelectionMerge %20 None
+               OpBranchConditional %19 %21 %20
+         %21 = OpLabel
+               OpStore %return_value %16
+               OpBranch %20
+         %20 = OpLabel
+         %22 = OpLoad %int %return_value
+               OpReturnValue %22
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_Phi_SingleValue_FalseReturn) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        i->SetResults(b.InstructionResult(ty.i32()));
+        b.With(i->True(), [&] {  //
+            b.ExitIf(i, 10_i);
+        });
+        b.With(i->False(), [&] {  //
+            b.Return(func, 42_i);
+        });
+        b.Return(func, i);
+    });
 
-    auto* i = b.If(true);
-    i->SetResults(b.InstructionResult(ty.i32()));
-    i->True()->Append(b.ExitIf(i, 10_i));
-    i->False()->Append(b.Return(func, 42_i));
-
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func, i));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%10 = OpConstant %2 42
-%12 = OpConstant %2 10
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpBranch %5
-%7 = OpLabel
-OpReturnValue %10
-%5 = OpLabel
-%11 = OpPhi %2 %12 %6
-OpReturnValue %11
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%18 = OpUndef %int");
+    EXPECT_INST(R"(
+               OpSelectionMerge %11 None
+               OpBranchConditional %true %12 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpStore %continue_execution %false
+               OpStore %return_value %int_42
+               OpBranch %11
+         %11 = OpLabel
+         %16 = OpPhi %int %int_10 %12 %18 %13
+         %19 = OpLoad %bool %continue_execution
+               OpSelectionMerge %20 None
+               OpBranchConditional %19 %21 %20
+         %21 = OpLabel
+               OpStore %return_value %16
+               OpBranch %20
+         %20 = OpLabel
+         %22 = OpLoad %int %return_value
+               OpReturnValue %22
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_Phi_MultipleValue_0) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        i->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
+        b.With(i->True(), [&] {  //
+            b.ExitIf(i, 10_i, true);
+        });
+        b.With(i->False(), [&] {  //
+            b.ExitIf(i, 20_i, false);
+        });
+        b.Return(func, i->Result(0));
+    });
 
-    auto* i = b.If(true);
-    i->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
-    i->True()->Append(b.ExitIf(i, 10_i, true));
-    i->False()->Append(b.ExitIf(i, 20_i, false));
-
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func, i->Result(0)));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%11 = OpConstant %2 10
-%12 = OpConstant %2 20
-%14 = OpConstantFalse %9
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpBranch %5
-%7 = OpLabel
-OpBranch %5
-%5 = OpLabel
-%10 = OpPhi %2 %11 %6 %12 %7
-%13 = OpPhi %9 %8 %6 %14 %7
-OpReturnValue %10
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %6 %7
+          %6 = OpLabel
+               OpBranch %5
+          %7 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %10 = OpPhi %int %int_10 %6 %int_20 %7
+         %13 = OpPhi %bool %true %6 %false %7
+               OpReturnValue %10
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, If_Phi_MultipleValue_1) {
     auto* func = b.Function("foo", ty.bool_());
+    b.With(func->Block(), [&] {
+        auto* i = b.If(true);
+        i->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
+        b.With(i->True(), [&] {  //
+            b.ExitIf(i, 10_i, true);
+        });
+        b.With(i->False(), [&] {  //
+            b.ExitIf(i, 20_i, false);
+        });
+        b.Return(func, i->Result(1));
+    });
 
-    auto* i = b.If(true);
-    i->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
-    i->True()->Append(b.ExitIf(i, 10_i, true));
-    i->False()->Append(b.ExitIf(i, 20_i, false));
-
-    func->Block()->Append(i);
-    func->Block()->Append(b.Return(func, i->Result(1)));
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeBool
-%3 = OpTypeFunction %2
-%8 = OpConstantTrue %2
-%9 = OpTypeInt 32 1
-%11 = OpConstant %9 10
-%12 = OpConstant %9 20
-%14 = OpConstantFalse %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpBranch %5
-%7 = OpLabel
-OpBranch %5
-%5 = OpLabel
-%10 = OpPhi %9 %11 %6 %12 %7
-%13 = OpPhi %2 %8 %6 %14 %7
-OpReturnValue %13
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %6 %7
+          %6 = OpLabel
+               OpBranch %5
+          %7 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %10 = OpPhi %int %int_10 %6 %int_20 %7
+         %13 = OpPhi %bool %true %6 %false %7
+               OpReturnValue %13
+               OpFunctionEnd
 )");
 }
 
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_loop_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_loop_test.cc
index 31bb501..67efd6a 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_loop_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_loop_test.cc
@@ -21,159 +21,146 @@
 
 TEST_F(SpvGeneratorImplTest, Loop_BreakIf) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {  //
+            b.Continue(loop);
 
-    auto* loop = b.Loop();
+            b.With(loop->Continuing(), [&] {  //
+                b.BreakIf(loop, true);
+            });
+        });
+        b.Return(func);
+    });
 
-    loop->Body()->Append(b.Continue(loop));
-    loop->Continuing()->Append(b.BreakIf(loop, true));
-
-    func->Block()->Append(loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%10 = OpTypeBool
-%9 = OpConstantTrue %10
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpBranch %6
-%6 = OpLabel
-OpBranchConditional %9 %8 %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpBranchConditional %true %8 %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 // Test that we still emit the continuing block with a back-edge, even when it is unreachable.
 TEST_F(SpvGeneratorImplTest, Loop_UnconditionalBreakInBody) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {  //
+            b.ExitLoop(loop);
+        });
+        b.Return(func);
+    });
 
-    auto* loop = b.Loop();
-
-    loop->Body()->Append(b.ExitLoop(loop));
-
-    func->Block()->Append(loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpBranch %8
-%6 = OpLabel
-OpBranch %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpBranch %8
+          %6 = OpLabel
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Loop_ConditionalBreakInBody) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {
+            auto* cond_break = b.If(true);
+            b.With(cond_break->True(), [&] {  //
+                b.ExitLoop(loop);
+            });
+            b.With(cond_break->False(), [&] {  //
+                b.ExitIf(cond_break);
+            });
+            b.Continue(loop);
 
-    auto* loop = b.Loop();
+            b.With(loop->Continuing(), [&] {  //
+                b.NextIteration(loop);
+            });
+        });
+        b.Return(func);
+    });
 
-    auto* cond_break = b.If(true);
-    cond_break->True()->Append(b.ExitLoop(loop));
-    cond_break->False()->Append(b.ExitIf(cond_break));
-
-    loop->Body()->Append(cond_break);
-    loop->Body()->Append(b.Continue(loop));
-    loop->Continuing()->Append(b.NextIteration(loop));
-
-    func->Block()->Append(loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%12 = OpTypeBool
-%11 = OpConstantTrue %12
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpSelectionMerge %9 None
-OpBranchConditional %11 %10 %9
-%10 = OpLabel
-OpBranch %8
-%9 = OpLabel
-OpBranch %6
-%6 = OpLabel
-OpBranch %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %true %10 %9
+         %10 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Loop_ConditionalContinueInBody) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {
+            auto* cond_break = b.If(true);
+            b.With(cond_break->True(), [&] {  //
+                b.Continue(loop);
+            });
+            b.With(cond_break->False(), [&] {  //
+                b.ExitIf(cond_break);
+            });
+            b.ExitLoop(loop);
 
-    auto* loop = b.Loop();
+            b.With(loop->Continuing(), [&] {  //
+                b.NextIteration(loop);
+            });
+        });
+        b.Return(func);
+    });
 
-    auto* cond_break = b.If(true);
-    cond_break->True()->Append(b.Continue(loop));
-    cond_break->False()->Append(b.ExitIf(cond_break));
-
-    loop->Body()->Append(cond_break);
-    loop->Body()->Append(b.ExitLoop(loop));
-    loop->Continuing()->Append(b.NextIteration(loop));
-
-    func->Block()->Append(loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%12 = OpTypeBool
-%11 = OpConstantTrue %12
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpSelectionMerge %9 None
-OpBranchConditional %11 %10 %9
-%10 = OpLabel
-OpBranch %6
-%9 = OpLabel
-OpBranch %8
-%6 = OpLabel
-OpBranch %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %true %10 %9
+         %10 = OpLabel
+               OpBranch %6
+          %9 = OpLabel
+               OpBranch %8
+          %6 = OpLabel
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
@@ -181,169 +168,158 @@
 // they are unreachable.
 TEST_F(SpvGeneratorImplTest, Loop_UnconditionalReturnInBody) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {  //
+            b.Return(func);
+        });
+        b.Unreachable();
+    });
 
-    auto* loop = b.Loop();
-    loop->Body()->Append(b.Return(func));
-
-    func->Block()->Append(loop);
-    func->Block()->Append(b.Unreachable());
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpReturn
-%6 = OpLabel
-OpBranch %7
-%8 = OpLabel
-OpUnreachable
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpBranch %8
+          %6 = OpLabel
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Loop_UseResultFromBodyInContinuing) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {
+            auto* result = b.Equal(ty.bool_(), 1_i, 2_i);
+            b.Continue(loop, result);
 
-    auto* loop = b.Loop();
+            b.With(loop->Continuing(), [&] {  //
+                b.BreakIf(loop, result);
+            });
+        });
+        b.Return(func);
+    });
 
-    auto* result = loop->Body()->Append(b.Equal(ty.i32(), 1_i, 2_i));
-    loop->Body()->Append(b.Continue(loop, result));
-
-    loop->Continuing()->Append(b.BreakIf(loop, result));
-
-    func->Block()->Append(loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%10 = OpTypeInt 32 1
-%11 = OpConstant %10 1
-%12 = OpConstant %10 2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-%9 = OpIEqual %10 %11 %12
-OpBranch %6
-%6 = OpLabel
-OpBranchConditional %9 %8 %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+          %9 = OpIEqual %bool %int_1 %int_2
+               OpBranch %6
+          %6 = OpLabel
+               OpBranchConditional %9 %8 %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Loop_NestedLoopInBody) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* outer_loop = b.Loop();
+        b.With(outer_loop->Body(), [&] {
+            auto* inner_loop = b.Loop();
+            b.With(inner_loop->Body(), [&] {
+                b.ExitLoop(inner_loop);
 
-    auto* outer_loop = b.Loop();
-    auto* inner_loop = b.Loop();
+                b.With(inner_loop->Continuing(), [&] {  //
+                    b.NextIteration(inner_loop);
+                });
+            });
+            b.Continue(outer_loop);
 
-    inner_loop->Body()->Append(b.ExitLoop(inner_loop));
-    inner_loop->Continuing()->Append(b.NextIteration(inner_loop));
+            b.With(outer_loop->Continuing(),
+                   [&] {  //
+                       b.BreakIf(outer_loop, true);
+                   });
+        });
+        b.Return(func);
+    });
 
-    outer_loop->Body()->Append(inner_loop);
-    outer_loop->Body()->Append(b.Continue(outer_loop));
-    outer_loop->Continuing()->Append(b.BreakIf(outer_loop, true));
-
-    func->Block()->Append(outer_loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%14 = OpTypeBool
-%13 = OpConstantTrue %14
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpBranch %11
-%11 = OpLabel
-OpLoopMerge %12 %10 None
-OpBranch %9
-%9 = OpLabel
-OpBranch %12
-%10 = OpLabel
-OpBranch %11
-%12 = OpLabel
-OpBranch %6
-%6 = OpLabel
-OpBranchConditional %13 %8 %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %12 %10 None
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %12
+         %10 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpBranchConditional %true %8 %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Loop_NestedLoopInContinuing) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* outer_loop = b.Loop();
+        b.With(outer_loop->Body(), [&] {
+            b.Continue(outer_loop);
 
-    auto* outer_loop = b.Loop();
-    auto* inner_loop = b.Loop();
+            b.With(outer_loop->Continuing(), [&] {
+                auto* inner_loop = b.Loop();
+                b.With(inner_loop->Body(), [&] {
+                    b.Continue(inner_loop);
 
-    inner_loop->Body()->Append(b.Continue(inner_loop));
-    inner_loop->Continuing()->Append(b.BreakIf(inner_loop, true));
+                    b.With(inner_loop->Continuing(), [&] {  //
+                        b.BreakIf(inner_loop, true);
+                    });
+                });
+                b.BreakIf(outer_loop, true);
+            });
+        });
+        b.Return(func);
+    });
 
-    outer_loop->Body()->Append(b.Continue(outer_loop));
-    outer_loop->Continuing()->Append(inner_loop);
-    outer_loop->Continuing()->Append(b.BreakIf(outer_loop, true));
-
-    func->Block()->Append(outer_loop);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%14 = OpTypeBool
-%13 = OpConstantTrue %14
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %7
-%7 = OpLabel
-OpLoopMerge %8 %6 None
-OpBranch %5
-%5 = OpLabel
-OpBranch %6
-%6 = OpLabel
-OpBranch %11
-%11 = OpLabel
-OpLoopMerge %12 %10 None
-OpBranch %9
-%9 = OpLabel
-OpBranch %10
-%10 = OpLabel
-OpBranchConditional %13 %12 %11
-%12 = OpLabel
-OpBranchConditional %13 %8 %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %12 %10 None
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranchConditional %true %12 %11
+         %12 = OpLabel
+               OpBranchConditional %true %8 %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
@@ -353,7 +329,9 @@
     b.With(func->Block(), [&] {
         auto* loop = b.Loop();
 
-        b.With(loop->Initializer(), [&] { b.NextIteration(loop, 1_i, false); });
+        b.With(loop->Initializer(), [&] {  //
+            b.NextIteration(loop, 1_i, false);
+        });
 
         auto* loop_param = b.BlockParam(ty.i32());
         loop->Body()->SetParams({loop_param});
@@ -373,35 +351,24 @@
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%10 = OpTypeInt 32 1
-%12 = OpConstant %10 1
-%16 = OpTypeBool
-%17 = OpConstant %10 5
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %5
-%5 = OpLabel
-OpBranch %8
-%8 = OpLabel
-%11 = OpPhi %10 %12 %5 %13 %7
-OpLoopMerge %9 %7 None
-OpBranch %6
-%6 = OpLabel
-%14 = OpIAdd %10 %11 %12
-OpBranch %7
-%7 = OpLabel
-%13 = OpPhi %10 %14 %6
-%15 = OpSGreaterThan %16 %13 %17
-OpBranchConditional %15 %9 %8
-%9 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpPhi %int %int_1 %5 %13 %7
+               OpLoopMerge %9 %7 None
+               OpBranch %6
+          %6 = OpLabel
+         %14 = OpIAdd %int %11 %int_1
+               OpBranch %7
+          %7 = OpLabel
+         %13 = OpPhi %int %14 %6
+         %15 = OpSGreaterThan %bool %13 %int_5
+               OpBranchConditional %15 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
@@ -411,7 +378,9 @@
     b.With(func->Block(), [&] {
         auto* loop = b.Loop();
 
-        b.With(loop->Initializer(), [&] { b.NextIteration(loop, 1_i, false); });
+        b.With(loop->Initializer(), [&] {  //
+            b.NextIteration(loop, 1_i, false);
+        });
 
         auto* loop_param_a = b.BlockParam(ty.i32());
         auto* loop_param_b = b.BlockParam(ty.bool_());
@@ -434,39 +403,27 @@
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%10 = OpTypeInt 32 1
-%12 = OpConstant %10 1
-%14 = OpTypeBool
-%16 = OpConstantFalse %14
-%21 = OpConstant %10 5
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpBranch %5
-%5 = OpLabel
-OpBranch %8
-%8 = OpLabel
-%11 = OpPhi %10 %12 %5 %13 %7
-%15 = OpPhi %14 %16 %5 %17 %7
-OpLoopMerge %9 %7 None
-OpBranch %6
-%6 = OpLabel
-%18 = OpIAdd %10 %11 %12
-OpBranch %7
-%7 = OpLabel
-%13 = OpPhi %10 %18 %6
-%19 = OpPhi %14 %15 %6
-%20 = OpSGreaterThan %14 %13 %21
-%17 = OpLogicalEqual %14 %19 %16
-OpBranchConditional %20 %9 %8
-%9 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpPhi %int %int_1 %5 %13 %7
+         %15 = OpPhi %bool %false %5 %17 %7
+               OpLoopMerge %9 %7 None
+               OpBranch %6
+          %6 = OpLabel
+         %18 = OpIAdd %int %11 %int_1
+               OpBranch %7
+          %7 = OpLabel
+         %13 = OpPhi %int %18 %6
+         %19 = OpPhi %bool %15 %6
+         %20 = OpSGreaterThan %bool %13 %int_5
+         %17 = OpLogicalEqual %bool %19 %false
+               OpBranchConditional %20 %9 %8
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_switch_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_switch_test.cc
index 396e0fe..d851066 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_switch_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_switch_test.cc
@@ -21,368 +21,351 @@
 
 TEST_F(SpvGeneratorImplTest, Switch_Basic) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* swtch = b.Switch(42_i);
 
-    auto* swtch = b.Switch(42_i);
+        auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
+        b.With(def_case, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
-    def_case->Append(b.ExitSwitch(swtch));
+        b.Return(func);
+    });
 
-    func->Block()->Append(swtch);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpConstant %7 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %8 None
-OpSwitch %6 %5
-%5 = OpLabel
-OpBranch %8
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %8 None
+               OpSwitch %int_42 %5
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_MultipleCases) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* swtch = b.Switch(42_i);
 
-    auto* swtch = b.Switch(42_i);
+        auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)}});
+        b.With(case_a, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)}});
-    case_a->Append(b.ExitSwitch(swtch));
+        auto* case_b = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
+        b.With(case_b, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* case_b = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
-    case_b->Append(b.ExitSwitch(swtch));
+        auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
+        b.With(def_case, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
-    def_case->Append(b.ExitSwitch(swtch));
+        b.Return(func);
+    });
 
-    func->Block()->Append(swtch);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpConstant %7 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %10 None
-OpSwitch %6 %5 1 %8 2 %9
-%8 = OpLabel
-OpBranch %10
-%9 = OpLabel
-OpBranch %10
-%5 = OpLabel
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %int_42 %5 1 %8 2 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_MultipleSelectorsPerCase) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* swtch = b.Switch(42_i);
 
-    auto* swtch = b.Switch(42_i);
+        auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
+                                                   ir::Switch::CaseSelector{b.Constant(3_i)}});
+        b.With(case_a, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
-                                               ir::Switch::CaseSelector{b.Constant(3_i)}});
-    case_a->Append(b.ExitSwitch(swtch));
+        auto* case_b = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)},
+                                                   ir::Switch::CaseSelector{b.Constant(4_i)}});
+        b.With(case_b, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* case_b = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)},
-                                               ir::Switch::CaseSelector{b.Constant(4_i)}});
-    case_b->Append(b.ExitSwitch(swtch));
+        auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(5_i)},
+                                                     ir::Switch::CaseSelector()});
+        b.With(def_case, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(5_i)},
-                                                 ir::Switch::CaseSelector()});
-    def_case->Append(b.ExitSwitch(swtch));
+        b.Return(func);
+    });
 
-    func->Block()->Append(swtch);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpConstant %7 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %10 None
-OpSwitch %6 %5 1 %8 3 %8 2 %9 4 %9 5 %5
-%8 = OpLabel
-OpBranch %10
-%9 = OpLabel
-OpBranch %10
-%5 = OpLabel
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %int_42 %5 1 %8 3 %8 2 %9 4 %9 5 %5
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_AllCasesReturn) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* swtch = b.Switch(42_i);
 
-    auto* swtch = b.Switch(42_i);
+        auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)}});
+        b.With(case_a, [&] {  //
+            b.Return(func);
+        });
 
-    auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)}});
-    case_a->Append(b.Return(func));
+        auto* case_b = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
+        b.With(case_b, [&] {  //
+            b.Return(func);
+        });
 
-    auto* case_b = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
-    case_b->Append(b.Return(func));
+        auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
+        b.With(def_case, [&] {  //
+            b.Return(func);
+        });
 
-    auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
-    def_case->Append(b.Return(func));
+        b.Unreachable();
+    });
 
-    func->Block()->Append(swtch);
-    func->Block()->Append(b.Unreachable());
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpConstant %7 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %10 None
-OpSwitch %6 %5 1 %8 2 %9
-%8 = OpLabel
-OpReturn
-%9 = OpLabel
-OpReturn
-%5 = OpLabel
-OpReturn
-%10 = OpLabel
-OpUnreachable
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %int_42 %5 1 %8 2 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_ConditionalBreak) {
     auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* swtch = b.Switch(42_i);
 
-    auto* swtch = b.Switch(42_i);
+        auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)}});
+        b.With(case_a, [&] {
+            auto* cond_break = b.If(true);
+            b.With(cond_break->True(), [&] {  //
+                b.ExitSwitch(swtch);
+            });
+            b.With(cond_break->False(), [&] {  //
+                b.ExitIf(cond_break);
+            });
 
-    auto* cond_break = b.If(true);
-    cond_break->True()->Append(b.ExitSwitch(swtch));
-    cond_break->False()->Append(b.ExitIf(cond_break));
+            b.Return(func);
+        });
 
-    auto* case_a = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)}});
-    case_a->Append(cond_break);
-    case_a->Append(b.Return(func));
+        auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
+        b.With(def_case, [&] {  //
+            b.ExitSwitch(swtch);
+        });
 
-    auto* def_case = b.Case(swtch, utils::Vector{ir::Switch::CaseSelector()});
-    def_case->Append(b.ExitSwitch(swtch));
+        b.Return(func);
+    });
 
-    func->Block()->Append(swtch);
-    func->Block()->Append(b.Return(func));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpConstant %7 42
-%13 = OpTypeBool
-%12 = OpConstantTrue %13
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %9 None
-OpSwitch %6 %5 1 %8
-%8 = OpLabel
-OpSelectionMerge %10 None
-OpBranchConditional %12 %11 %10
-%11 = OpLabel
-OpBranch %9
-%10 = OpLabel
-OpReturn
-%5 = OpLabel
-OpBranch %9
-%9 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %9 None
+               OpSwitch %int_42 %5 1 %8
+          %8 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %true %11 %10
+         %11 = OpLabel
+               OpBranch %9
+         %10 = OpLabel
+               OpBranch %9
+          %5 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_Phi_SingleValue) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* s = b.Switch(42_i);
+        s->SetResults(b.InstructionResult(ty.i32()));
+        auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
+                                               ir::Switch::CaseSelector{nullptr}});
+        b.With(case_a, [&] {  //
+            b.ExitSwitch(s, 10_i);
+        });
 
-    auto* s = b.Switch(42_i);
-    s->SetResults(b.InstructionResult(ty.i32()));
-    auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
-                                           ir::Switch::CaseSelector{nullptr}});
-    case_a->Append(b.ExitSwitch(s, 10_i));
+        auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
+        b.With(case_b, [&] {  //
+            b.ExitSwitch(s, 20_i);
+        });
 
-    auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
-    case_b->Append(b.ExitSwitch(s, 20_i));
+        b.Return(func, s);
+    });
 
-    func->Block()->Append(s);
-    func->Block()->Append(b.Return(func, s));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%6 = OpConstant %2 42
-%10 = OpConstant %2 10
-%11 = OpConstant %2 20
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %8 None
-OpSwitch %6 %5 1 %5 2 %7
-%5 = OpLabel
-OpBranch %8
-%7 = OpLabel
-OpBranch %8
-%8 = OpLabel
-%9 = OpPhi %2 %10 %5 %11 %7
-OpReturnValue %9
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %8 None
+               OpSwitch %int_42 %5 1 %5 2 %7
+          %5 = OpLabel
+               OpBranch %8
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+          %9 = OpPhi %int %int_10 %5 %int_20 %7
+               OpReturnValue %9
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_Phi_SingleValue_CaseReturn) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* s = b.Switch(42_i);
+        s->SetResults(b.InstructionResult(ty.i32()));
+        auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
+                                               ir::Switch::CaseSelector{nullptr}});
+        b.With(case_a, [&] {  //
+            b.Return(func, 10_i);
+        });
 
-    auto* s = b.Switch(42_i);
-    s->SetResults(b.InstructionResult(ty.i32()));
-    auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
-                                           ir::Switch::CaseSelector{nullptr}});
-    case_a->Append(b.Return(func, 10_i));
+        auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
+        b.With(case_b, [&] {  //
+            b.ExitSwitch(s, 20_i);
+        });
 
-    auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
-    case_b->Append(b.ExitSwitch(s, 20_i));
+        b.Return(func, s);
+    });
 
-    func->Block()->Append(s);
-    func->Block()->Append(b.Return(func, s));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%6 = OpConstant %2 42
-%9 = OpConstant %2 10
-%11 = OpConstant %2 20
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %8 None
-OpSwitch %6 %5 1 %5 2 %7
-%5 = OpLabel
-OpReturnValue %9
-%7 = OpLabel
-OpBranch %8
-%8 = OpLabel
-%10 = OpPhi %2 %11 %7
-OpReturnValue %10
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+%return_value = OpVariable %_ptr_Function_int Function
+%continue_execution = OpVariable %_ptr_Function_bool Function
+               OpStore %continue_execution %true
+               OpSelectionMerge %14 None
+               OpSwitch %int_42 %11 1 %11 2 %13
+         %11 = OpLabel
+               OpStore %continue_execution %false
+               OpStore %return_value %int_10
+               OpBranch %14
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+         %17 = OpPhi %int %18 %11 %int_20 %13
+         %20 = OpLoad %bool %continue_execution
+               OpSelectionMerge %21 None
+               OpBranchConditional %20 %22 %21
+         %22 = OpLabel
+               OpStore %return_value %17
+               OpBranch %21
+         %21 = OpLabel
+         %23 = OpLoad %int %return_value
+               OpReturnValue %23
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_Phi_MultipleValue_0) {
     auto* func = b.Function("foo", ty.i32());
+    b.With(func->Block(), [&] {
+        auto* s = b.Switch(42_i);
+        s->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
+        auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
+                                               ir::Switch::CaseSelector{nullptr}});
+        b.With(case_a, [&] {  //
+            b.ExitSwitch(s, 10_i, true);
+        });
 
-    auto* s = b.Switch(42_i);
-    s->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
-    auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
-                                           ir::Switch::CaseSelector{nullptr}});
-    case_a->Append(b.ExitSwitch(s, 10_i, true));
+        auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
+        b.With(case_b, [&] {  //
+            b.ExitSwitch(s, 20_i, false);
+        });
 
-    auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
-    case_b->Append(b.ExitSwitch(s, 20_i, false));
+        b.Return(func, s->Result(0));
+    });
 
-    func->Block()->Append(s);
-    func->Block()->Append(b.Return(func, s->Result(0)));
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%6 = OpConstant %2 42
-%10 = OpConstant %2 10
-%11 = OpConstant %2 20
-%12 = OpTypeBool
-%14 = OpConstantTrue %12
-%15 = OpConstantFalse %12
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %8 None
-OpSwitch %6 %5 1 %5 2 %7
-%5 = OpLabel
-OpBranch %8
-%7 = OpLabel
-OpBranch %8
-%8 = OpLabel
-%9 = OpPhi %2 %10 %5 %11 %7
-%13 = OpPhi %12 %14 %5 %15 %7
-OpReturnValue %9
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %8 None
+               OpSwitch %int_42 %5 1 %5 2 %7
+          %5 = OpLabel
+               OpBranch %8
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+          %9 = OpPhi %int %int_10 %5 %int_20 %7
+         %13 = OpPhi %bool %true %5 %false %7
+               OpReturnValue %9
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, Switch_Phi_MultipleValue_1) {
     auto* func = b.Function("foo", ty.bool_());
+    b.With(func->Block(), [&] {
+        auto* s = b.Switch(b.Constant(42_i));
+        s->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
+        auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
+                                               ir::Switch::CaseSelector{nullptr}});
+        b.With(case_a, [&] {  //
+            b.ExitSwitch(s, 10_i, true);
+        });
 
-    auto* s = b.Switch(b.Constant(42_i));
-    s->SetResults(b.InstructionResult(ty.i32()), b.InstructionResult(ty.bool_()));
-    auto* case_a = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(1_i)},
-                                           ir::Switch::CaseSelector{nullptr}});
-    case_a->Append(b.ExitSwitch(s, 10_i, true));
+        auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
+        b.With(case_b, [&] {  //
+            b.ExitSwitch(s, 20_i, false);
+        });
 
-    auto* case_b = b.Case(s, utils::Vector{ir::Switch::CaseSelector{b.Constant(2_i)}});
-    case_b->Append(b.ExitSwitch(s, 20_i, false));
+        b.Return(func, s->Result(1));
+    });
 
-    func->Block()->Append(s);
-    func->Block()->Append(b.Return(func, s->Result(1)));
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeBool
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpConstant %7 42
-%11 = OpConstant %7 10
-%12 = OpConstant %7 20
-%14 = OpConstantTrue %2
-%15 = OpConstantFalse %2
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %9 None
-OpSwitch %6 %5 1 %5 2 %8
-%5 = OpLabel
-OpBranch %9
-%8 = OpLabel
-OpBranch %9
-%9 = OpLabel
-%10 = OpPhi %7 %11 %5 %12 %8
-%13 = OpPhi %2 %14 %5 %15 %8
-OpReturnValue %13
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpSelectionMerge %9 None
+               OpSwitch %int_42 %5 1 %5 2 %8
+          %5 = OpLabel
+               OpBranch %9
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+         %10 = OpPhi %int %int_10 %5 %int_20 %8
+         %13 = OpPhi %bool %true %5 %false %8
+               OpReturnValue %13
+               OpFunctionEnd
 )");
 }
 
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_test.cc
index 9ec2a47..08d15bd 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_test.cc
@@ -30,36 +30,48 @@
 }
 
 TEST_F(SpvGeneratorImplTest, Unreachable) {
-    auto* func = b.Function("foo", ty.i32());
+    auto* func = b.Function("foo", ty.void_());
+    b.With(func->Block(), [&] {
+        auto* loop = b.Loop();
+        b.With(loop->Body(), [&] {
+            auto* ifelse = b.If(true);
+            b.With(ifelse->True(), [&] {  //
+                b.Continue(loop);
+            });
+            b.With(ifelse->False(), [&] {  //
+                b.Continue(loop);
+            });
+            b.Unreachable();
 
-    auto* i = b.If(true);
-    i->True()->Append(b.Return(func, 10_i));
-    i->False()->Append(b.Return(func, 20_i));
+            b.With(loop->Continuing(), [&] {  //
+                b.NextIteration(loop);
+            });
+        });
+        b.Return(func);
+    });
 
-    func->Block()->Append(i);
-    func->Block()->Append(b.Unreachable());
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeInt 32 1
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%10 = OpConstant %2 10
-%11 = OpConstant %2 20
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpReturnValue %10
-%7 = OpLabel
-OpReturnValue %11
-%5 = OpLabel
-OpUnreachable
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+        %foo = OpFunction %void None %3
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %true %10 %11
+         %10 = OpLabel
+               OpBranch %6
+         %11 = OpLabel
+               OpBranch %6
+          %9 = OpLabel
+               OpUnreachable
+          %6 = OpLabel
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
index 7cd64e4..9caa027 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_type_test.cc
@@ -25,164 +25,140 @@
 namespace {
 
 TEST_F(SpvGeneratorImplTest, Type_Void) {
-    auto id = generator_.Type(ty.void_());
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeVoid\n");
+    generator_.Type(ty.void_());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%void = OpTypeVoid");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Bool) {
-    auto id = generator_.Type(ty.bool_());
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeBool\n");
+    generator_.Type(ty.bool_());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%bool = OpTypeBool");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_I32) {
-    auto id = generator_.Type(ty.i32());
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 1\n");
+    generator_.Type(ty.i32());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%int = OpTypeInt 32 1");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_U32) {
-    auto id = generator_.Type(ty.u32());
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 0\n");
+    generator_.Type(ty.u32());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%uint = OpTypeInt 32 0");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_F32) {
-    auto id = generator_.Type(ty.f32());
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeFloat 32\n");
+    generator_.Type(ty.f32());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%float = OpTypeFloat 32");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_F16) {
-    auto id = generator_.Type(ty.f16());
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeFloat 16\n");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Capabilities()),
-              "OpCapability Float16\n"
-              "OpCapability UniformAndStorageBuffer16BitAccess\n"
-              "OpCapability StorageBuffer16BitAccess\n"
-              "OpCapability StorageInputOutput16\n");
+    generator_.Type(ty.f16());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpCapability Float16");
+    EXPECT_INST("OpCapability UniformAndStorageBuffer16BitAccess");
+    EXPECT_INST("OpCapability StorageBuffer16BitAccess");
+    EXPECT_INST("OpCapability StorageInputOutput16");
+    EXPECT_INST("%half = OpTypeFloat 16");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Vec2i) {
-    auto id = generator_.Type(ty.vec2(ty.i32()));
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeInt 32 1\n"
-              "%1 = OpTypeVector %2 2\n");
+    generator_.Type(ty.vec2<i32>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v2int = OpTypeVector %int 2");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Vec3u) {
-    auto id = generator_.Type(ty.vec3(ty.u32()));
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeInt 32 0\n"
-              "%1 = OpTypeVector %2 3\n");
+    generator_.Type(ty.vec3<u32>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v3uint = OpTypeVector %uint 3");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Vec4f) {
-    auto id = generator_.Type(ty.vec4(ty.f32()));
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeFloat 32\n"
-              "%1 = OpTypeVector %2 4\n");
+    generator_.Type(ty.vec4<f32>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v4float = OpTypeVector %float 4");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Vec2h) {
-    auto id = generator_.Type(ty.vec2(ty.f16()));
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeFloat 16\n"
-              "%1 = OpTypeVector %2 2\n");
+    generator_.Type(ty.vec2<f16>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v2half = OpTypeVector %half 2");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Vec4Bool) {
-    auto id = generator_.Type(ty.vec4(ty.bool_()));
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeBool\n"
-              "%1 = OpTypeVector %2 4\n");
+    generator_.Type(ty.vec4<bool>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v4bool = OpTypeVector %bool 4");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Mat2x3f) {
-    auto* vec = ty.mat2x3(ty.f32());
-    auto id = generator_.Type(vec);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%3 = OpTypeFloat 32\n"
-              "%2 = OpTypeVector %3 3\n"
-              "%1 = OpTypeMatrix %2 2\n");
+    generator_.Type(ty.mat2x3(ty.f32()));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%mat2v3float = OpTypeMatrix %v3float 2");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Mat4x2h) {
-    auto* vec = ty.mat4x2(ty.f16());
-    auto id = generator_.Type(vec);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%3 = OpTypeFloat 16\n"
-              "%2 = OpTypeVector %3 2\n"
-              "%1 = OpTypeMatrix %2 4\n");
+    generator_.Type(ty.mat4x2(ty.f16()));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%mat4v2half = OpTypeMatrix %v2half 4");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Array_DefaultStride) {
-    auto* arr = ty.array(ty.f32(), 4u);
-    auto id = generator_.Type(arr);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeFloat 32\n"
-              "%4 = OpTypeInt 32 0\n"
-              "%3 = OpConstant %4 4\n"
-              "%1 = OpTypeArray %2 %3\n");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), "OpDecorate %1 ArrayStride 4\n");
+    generator_.Type(ty.array<f32, 4>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpDecorate %_arr_float_uint_4 ArrayStride 4");
+    EXPECT_INST("%_arr_float_uint_4 = OpTypeArray %float %uint_4");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Array_ExplicitStride) {
-    auto* arr = ty.array(ty.f32(), 4u, 16);
-    auto id = generator_.Type(arr);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeFloat 32\n"
-              "%4 = OpTypeInt 32 0\n"
-              "%3 = OpConstant %4 4\n"
-              "%1 = OpTypeArray %2 %3\n");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), "OpDecorate %1 ArrayStride 16\n");
+    generator_.Type(ty.array<f32, 4>(16));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpDecorate %_arr_float_uint_4 ArrayStride 16");
+    EXPECT_INST("%_arr_float_uint_4 = OpTypeArray %float %uint_4");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Array_NestedArray) {
-    auto* arr = ty.array(ty.array(ty.f32(), 64u), 4u);
-    auto id = generator_.Type(arr);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%3 = OpTypeFloat 32\n"
-              "%5 = OpTypeInt 32 0\n"
-              "%4 = OpConstant %5 64\n"
-              "%2 = OpTypeArray %3 %4\n"
-              "%6 = OpConstant %5 4\n"
-              "%1 = OpTypeArray %2 %6\n");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()),
-              "OpDecorate %2 ArrayStride 4\n"
-              "OpDecorate %1 ArrayStride 256\n");
+    generator_.Type(ty.array(ty.array<f32, 64u>(), 4u));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpDecorate %_arr_float_uint_64 ArrayStride 4");
+    EXPECT_INST("OpDecorate %_arr__arr_float_uint_64_uint_4 ArrayStride 256");
+    EXPECT_INST("%_arr_float_uint_64 = OpTypeArray %float %uint_64");
+    EXPECT_INST("%_arr__arr_float_uint_64_uint_4 = OpTypeArray %_arr_float_uint_64 %uint_4");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_RuntimeArray_DefaultStride) {
-    auto* arr = ty.runtime_array(ty.f32());
-    auto id = generator_.Type(arr);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeFloat 32\n"
-              "%1 = OpTypeRuntimeArray %2\n");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), "OpDecorate %1 ArrayStride 4\n");
+    generator_.Type(ty.array<f32>());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpDecorate %_runtimearr_float ArrayStride 4");
+    EXPECT_INST("%_runtimearr_float = OpTypeRuntimeArray %float");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_RuntimeArray_ExplicitStride) {
-    auto* arr = ty.runtime_array(ty.f32(), 16);
-    auto id = generator_.Type(arr);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(),
-              "%2 = OpTypeFloat 32\n"
-              "%1 = OpTypeRuntimeArray %2\n");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), "OpDecorate %1 ArrayStride 16\n");
+    generator_.Type(ty.array<f32>(16));
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpDecorate %_runtimearr_float ArrayStride 16");
+    EXPECT_INST("%_runtimearr_float = OpTypeRuntimeArray %float");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Struct) {
@@ -191,20 +167,15 @@
                                                    {mod.symbols.Register("a"), ty.f32()},
                                                    {mod.symbols.Register("b"), ty.vec4<i32>()},
                                                });
-    auto id = generator_.Type(str);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeFloat 32
-%4 = OpTypeInt 32 1
-%3 = OpTypeVector %4 4
-%1 = OpTypeStruct %2 %3
-)");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
-OpMemberDecorate %1 1 Offset 16
-)");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Debug()), R"(OpMemberName %1 0 "a"
-OpMemberName %1 1 "b"
-OpName %1 "MyStruct"
-)");
+    generator_.Type(str);
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpMemberName %MyStruct 0 \"a\"");
+    EXPECT_INST("OpMemberName %MyStruct 1 \"b\"");
+    EXPECT_INST("OpName %MyStruct \"MyStruct\"");
+    EXPECT_INST("OpMemberDecorate %MyStruct 0 Offset 0");
+    EXPECT_INST("OpMemberDecorate %MyStruct 1 Offset 16");
+    EXPECT_INST("%MyStruct = OpTypeStruct %float %v4int");
 }
 
 TEST_F(SpvGeneratorImplTest, Type_Struct_MatrixLayout) {
@@ -215,56 +186,40 @@
             // Matrices nested inside arrays need layout decorations on the struct member too.
             {mod.symbols.Register("arr"), ty.array(ty.array(ty.mat2x4<f16>(), 4), 4)},
         });
-    auto id = generator_.Type(str);
-    EXPECT_EQ(id, 1u);
-    EXPECT_EQ(DumpTypes(), R"(%4 = OpTypeFloat 32
-%3 = OpTypeVector %4 3
-%2 = OpTypeMatrix %3 3
-%9 = OpTypeFloat 16
-%8 = OpTypeVector %9 4
-%7 = OpTypeMatrix %8 2
-%11 = OpTypeInt 32 0
-%10 = OpConstant %11 4
-%6 = OpTypeArray %7 %10
-%5 = OpTypeArray %6 %10
-%1 = OpTypeStruct %2 %5
-)");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
-OpMemberDecorate %1 0 ColMajor
-OpMemberDecorate %1 0 MatrixStride 16
-OpDecorate %6 ArrayStride 16
-OpDecorate %5 ArrayStride 64
-OpMemberDecorate %1 1 Offset 48
-OpMemberDecorate %1 1 ColMajor
-OpMemberDecorate %1 1 MatrixStride 8
-)");
-    EXPECT_EQ(DumpInstructions(generator_.Module().Debug()), R"(OpMemberName %1 0 "m"
-OpMemberName %1 1 "arr"
-OpName %1 "MyStruct"
-)");
+    generator_.Type(str);
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("OpMemberDecorate %MyStruct 0 ColMajor");
+    EXPECT_INST("OpMemberDecorate %MyStruct 0 MatrixStride 16");
+    EXPECT_INST("OpMemberDecorate %MyStruct 1 ColMajor");
+    EXPECT_INST("OpMemberDecorate %MyStruct 1 MatrixStride 8");
+    EXPECT_INST("%MyStruct = OpTypeStruct %mat3v3float %_arr__arr_mat2v4half_uint_4_uint_4");
 }
 
 // Test that we can emit multiple types.
 // Includes types with the same opcode but different parameters.
 TEST_F(SpvGeneratorImplTest, Type_Multiple) {
-    EXPECT_EQ(generator_.Type(ty.i32()), 1u);
-    EXPECT_EQ(generator_.Type(ty.u32()), 2u);
-    EXPECT_EQ(generator_.Type(ty.f32()), 3u);
-    EXPECT_EQ(generator_.Type(ty.f16()), 4u);
-    EXPECT_EQ(DumpTypes(), R"(%1 = OpTypeInt 32 1
-%2 = OpTypeInt 32 0
-%3 = OpTypeFloat 32
-%4 = OpTypeFloat 16
+    generator_.Type(ty.i32());
+    generator_.Type(ty.u32());
+    generator_.Type(ty.f32());
+    generator_.Type(ty.f16());
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+       %half = OpTypeFloat 16
 )");
 }
 
 // Test that we do not emit the same type more than once.
 TEST_F(SpvGeneratorImplTest, Type_Deduplicate) {
-    auto* i32 = ty.i32();
-    EXPECT_EQ(generator_.Type(i32), 1u);
-    EXPECT_EQ(generator_.Type(i32), 1u);
-    EXPECT_EQ(generator_.Type(i32), 1u);
-    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 1\n");
+    auto id = generator_.Type(ty.i32());
+    EXPECT_EQ(generator_.Type(ty.i32()), id);
+    EXPECT_EQ(generator_.Type(ty.i32()), id);
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc
index a79e326..8c50131 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_var_test.cc
@@ -23,597 +23,236 @@
 
 TEST_F(SpvGeneratorImplTest, FunctionVar_NoInit) {
     auto* func = b.Function("foo", ty.void_());
-
     b.With(func->Block(), [&] {
-        b.Var(ty.ptr<function, i32>());
+        b.Var("v", ty.ptr<function, i32>());
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypePointer Function %7
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%5 = OpVariable %6 Function
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Function_int Function");
 }
 
 TEST_F(SpvGeneratorImplTest, FunctionVar_WithInit) {
     auto* func = b.Function("foo", ty.void_());
-
     b.With(func->Block(), [&] {
-        auto* v = b.Var(ty.ptr<function, i32>());
+        auto* v = b.Var("v", ty.ptr<function, i32>());
         v->SetInitializer(b.Constant(42_i));
-
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypePointer Function %7
-%8 = OpConstant %7 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%5 = OpVariable %6 Function
-OpStore %5 %8
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(SpvGeneratorImplTest, FunctionVar_Name) {
-    auto* func = b.Function("foo", ty.void_());
-
-    b.With(func->Block(), [&] {
-        b.Var("myvar", ty.ptr<function, i32>());
-        b.Return(func);
-    });
-
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-OpName %5 "myvar"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypePointer Function %7
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%5 = OpVariable %6 Function
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Function_int Function");
+    EXPECT_INST("OpStore %v %int_42");
 }
 
 TEST_F(SpvGeneratorImplTest, FunctionVar_DeclInsideBlock) {
     auto* func = b.Function("foo", ty.void_());
-
     b.With(func->Block(), [&] {
         auto* i = b.If(true);
         b.With(i->True(), [&] {
-            auto* v = b.Var(ty.ptr<function, i32>());
+            auto* v = b.Var("v", ty.ptr<function, i32>());
             v->SetInitializer(b.Constant(42_i));
             b.ExitIf(i);
         });
-        b.With(i->False(), [&] { b.Return(func); });
-
         b.Return(func);
     });
-    ASSERT_TRUE(IRIsValid()) << Error();
 
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%9 = OpTypeBool
-%8 = OpConstantTrue %9
-%12 = OpTypeInt 32 1
-%11 = OpTypePointer Function %12
-%13 = OpConstant %12 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%10 = OpVariable %11 Function
-OpSelectionMerge %5 None
-OpBranchConditional %8 %6 %7
-%6 = OpLabel
-OpStore %10 %13
-OpBranch %5
-%7 = OpLabel
-OpReturn
-%5 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+        %foo = OpFunction %void None %3
+          %4 = OpLabel
+          %v = OpVariable %_ptr_Function_int Function
+               OpSelectionMerge %5 None
+               OpBranchConditional %true %6 %5
+          %6 = OpLabel
+               OpStore %v %int_42
+               OpBranch %5
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, FunctionVar_Load) {
     auto* func = b.Function("foo", ty.void_());
-
     b.With(func->Block(), [&] {
-        auto* store_ty = ty.i32();
-        auto* v = b.Var(ty.ptr(function, store_ty));
-        b.Load(v);
+        auto* v = b.Var("v", ty.ptr<function, i32>());
+        auto* result = b.Load(v);
         b.Return(func);
+        mod.SetName(result, "result");
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypePointer Function %7
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%5 = OpVariable %6 Function
-%8 = OpLoad %7 %5
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Function_int Function");
+    EXPECT_INST("%result = OpLoad %int %v");
 }
 
 TEST_F(SpvGeneratorImplTest, FunctionVar_Store) {
     auto* func = b.Function("foo", ty.void_());
-
     b.With(func->Block(), [&] {
-        auto* v = b.Var(ty.ptr<function, i32>());
+        auto* v = b.Var("v", ty.ptr<function, i32>());
         b.Store(v, 42_i);
         b.Return(func);
     });
 
-    ASSERT_TRUE(IRIsValid()) << Error();
-
-    generator_.EmitFunction(func);
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
-%2 = OpTypeVoid
-%3 = OpTypeFunction %2
-%7 = OpTypeInt 32 1
-%6 = OpTypePointer Function %7
-%8 = OpConstant %7 42
-%1 = OpFunction %2 None %3
-%4 = OpLabel
-%5 = OpVariable %6 Function
-OpStore %5 %8
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Function_int Function");
+    EXPECT_INST("OpStore %v %int_42");
 }
 
 TEST_F(SpvGeneratorImplTest, PrivateVar_NoInit) {
-    b.RootBlock()->Append(b.Var(ty.ptr<private_, i32>()));
+    b.RootBlock()->Append(b.Var("v", ty.ptr<private_, i32>()));
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %4 "unused_entry_point"
-OpExecutionMode %4 LocalSize 1 1 1
-OpName %4 "unused_entry_point"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%1 = OpVariable %2 Private
-%5 = OpTypeVoid
-%6 = OpTypeFunction %5
-%4 = OpFunction %5 None %6
-%7 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Private_int Private");
 }
 
 TEST_F(SpvGeneratorImplTest, PrivateVar_WithInit) {
-    auto* v = b.Var(ty.ptr<private_, i32>());
+    auto* v = b.Var("v", ty.ptr<private_, i32>());
     v->SetInitializer(b.Constant(42_i));
     b.RootBlock()->Append(v);
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpName %5 "unused_entry_point"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstant %3 42
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(SpvGeneratorImplTest, PrivateVar_Name) {
-    auto* v = b.Var("myvar", ty.ptr<private_, i32>());
-    v->SetInitializer(b.Constant(42_i));
-    b.RootBlock()->Append(v);
-
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpName %1 "myvar"
-OpName %5 "unused_entry_point"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstant %3 42
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Private_int Private %int_42");
 }
 
 TEST_F(SpvGeneratorImplTest, PrivateVar_LoadAndStore) {
-    auto* func = b.Function("foo", ty.void_(), ir::Function::PipelineStage::kFragment);
-
-    auto* store_ty = ty.i32();
-    auto* v = b.Var(ty.ptr(private_, store_ty));
+    auto* v = b.Var("v", ty.ptr<private_, i32>());
     v->SetInitializer(b.Constant(42_i));
     b.RootBlock()->Append(v);
 
+    auto* func = b.Function("foo", ty.void_(), ir::Function::PipelineStage::kFragment);
     b.With(func->Block(), [&] {
-        b.Load(v);
-        auto* add = b.Add(store_ty, v, 1_i);
+        auto* load = b.Load(v);
+        auto* add = b.Add(ty.i32(), load, 1_i);
         b.Store(v, add);
         b.Return(func);
+        mod.SetName(load, "load");
+        mod.SetName(add, "add");
     });
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %5 "foo"
-OpExecutionMode %5 OriginUpperLeft
-OpName %5 "foo"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstant %3 42
-%1 = OpVariable %2 Private %4
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%11 = OpConstant %3 1
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-%9 = OpLoad %3 %1
-%10 = OpIAdd %3 %1 %11
-OpStore %1 %10
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Private_int Private %int_42");
+    EXPECT_INST("%load = OpLoad %int %v");
+    EXPECT_INST("OpStore %v %add");
 }
 
 TEST_F(SpvGeneratorImplTest, WorkgroupVar) {
-    b.RootBlock()->Append(b.Var(ty.ptr<workgroup, i32>()));
+    b.RootBlock()->Append(b.Var("v", ty.ptr<workgroup, i32>()));
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %4 "unused_entry_point"
-OpExecutionMode %4 LocalSize 1 1 1
-OpName %4 "unused_entry_point"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Workgroup %3
-%1 = OpVariable %2 Workgroup
-%5 = OpTypeVoid
-%6 = OpTypeFunction %5
-%4 = OpFunction %5 None %6
-%7 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_F(SpvGeneratorImplTest, WorkgroupVar_Name) {
-    b.RootBlock()->Append(b.Var("myvar", ty.ptr<workgroup, i32>()));
-
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %4 "unused_entry_point"
-OpExecutionMode %4 LocalSize 1 1 1
-OpName %1 "myvar"
-OpName %4 "unused_entry_point"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Workgroup %3
-%1 = OpVariable %2 Workgroup
-%5 = OpTypeVoid
-%6 = OpTypeFunction %5
-%4 = OpFunction %5 None %6
-%7 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Workgroup_int Workgroup");
 }
 
 TEST_F(SpvGeneratorImplTest, WorkgroupVar_LoadAndStore) {
+    auto* v = b.RootBlock()->Append(b.Var("v", ty.ptr<workgroup, i32>()));
+
     auto* func = b.Function("foo", ty.void_(), ir::Function::PipelineStage::kCompute,
                             std::array{1u, 1u, 1u});
-
-    auto* store_ty = ty.i32();
-    auto* v = b.RootBlock()->Append(b.Var(ty.ptr(workgroup, store_ty)));
-
     b.With(func->Block(), [&] {
-        b.Load(v);
-        auto* add = b.Add(store_ty, v, 1_i);
+        auto* load = b.Load(v);
+        auto* add = b.Add(ty.i32(), load, 1_i);
         b.Store(v, add);
         b.Return(func);
+        mod.SetName(load, "load");
+        mod.SetName(add, "add");
     });
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %4 "foo"
-OpExecutionMode %4 LocalSize 1 1 1
-OpName %4 "foo"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Workgroup %3
-%1 = OpVariable %2 Workgroup
-%5 = OpTypeVoid
-%6 = OpTypeFunction %5
-%10 = OpConstant %3 1
-%4 = OpFunction %5 None %6
-%7 = OpLabel
-%8 = OpLoad %3 %1
-%9 = OpIAdd %3 %1 %10
-OpStore %1 %9
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST("%v = OpVariable %_ptr_Workgroup_int Workgroup");
+    EXPECT_INST("%load = OpLoad %int %v");
+    EXPECT_INST("OpStore %v %add");
 }
 
 TEST_F(SpvGeneratorImplTest, WorkgroupVar_ZeroInitializeWithExtension) {
-    b.RootBlock()->Append(b.Var(ty.ptr<workgroup, i32>()));
+    b.RootBlock()->Append(b.Var("v", ty.ptr<workgroup, i32>()));
 
     // Create a generator with the zero_init_workgroup_memory flag set to `true`.
     spirv::GeneratorImplIr gen(&mod, true);
-    ASSERT_TRUE(gen.Generate()) << gen.Diagnostics().str();
-    EXPECT_EQ(DumpModule(gen.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpName %5 "unused_entry_point"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Workgroup %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Workgroup %4
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
+    ASSERT_TRUE(Generate(gen)) << Error() << output_;
+    EXPECT_INST("%4 = OpConstantNull %int");
+    EXPECT_INST("%v = OpVariable %_ptr_Workgroup_int Workgroup %4");
 }
 
 TEST_F(SpvGeneratorImplTest, StorageVar) {
-    auto* v = b.Var(ty.ptr<storage, i32>());
+    auto* v = b.Var("v", ty.ptr<storage, i32>());
     v->SetBindingPoint(0, 0);
     b.RootBlock()->Append(v);
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpMemberName %3 0 "tint_symbol"
-OpName %3 "tint_symbol_1"
-OpName %5 "unused_entry_point"
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %3 Block
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpDecorate %tint_symbol_1 Block
+               OpDecorate %1 DescriptorSet 0
+               OpDecorate %1 Binding 0
 )");
-}
-
-TEST_F(SpvGeneratorImplTest, StorageVar_Name) {
-    auto* v = b.Var("myvar", ty.ptr<storage, i32>());
-    v->SetBindingPoint(0, 0);
-    b.RootBlock()->Append(v);
-
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpMemberName %3 0 "tint_symbol"
-OpName %3 "tint_symbol_1"
-OpName %5 "unused_entry_point"
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %3 Block
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    EXPECT_INST(R"(
+%tint_symbol_1 = OpTypeStruct %int
+%_ptr_StorageBuffer_tint_symbol_1 = OpTypePointer StorageBuffer %tint_symbol_1
+          %1 = OpVariable %_ptr_StorageBuffer_tint_symbol_1 StorageBuffer
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, StorageVar_LoadAndStore) {
-    auto* v = b.Var(ty.ptr<storage, i32>());
+    auto* v = b.Var("v", ty.ptr<storage, i32>());
     v->SetBindingPoint(0, 0);
     b.RootBlock()->Append(v);
 
     auto* func = b.Function("foo", ty.void_(), ir::Function::PipelineStage::kCompute,
                             std::array{1u, 1u, 1u});
-
     b.With(func->Block(), [&] {
-        b.Load(v);
-        auto* add = b.Add(ty.i32(), v, 1_i);
+        auto* load = b.Load(v);
+        auto* add = b.Add(ty.i32(), load, 1_i);
         b.Store(v, add);
         b.Return(func);
+        mod.SetName(load, "load");
+        mod.SetName(add, "add");
     });
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "foo"
-OpExecutionMode %5 LocalSize 1 1 1
-OpMemberName %3 0 "tint_symbol"
-OpName %3 "tint_symbol_1"
-OpName %5 "foo"
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %3 Block
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer StorageBuffer %3
-%1 = OpVariable %2 StorageBuffer
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%10 = OpTypePointer StorageBuffer %4
-%12 = OpTypeInt 32 0
-%11 = OpConstant %12 0
-%16 = OpConstant %4 1
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-%9 = OpAccessChain %10 %1 %11
-%13 = OpLoad %4 %9
-%14 = OpAccessChain %10 %1 %11
-%15 = OpIAdd %4 %14 %16
-%17 = OpAccessChain %10 %1 %11
-OpStore %17 %15
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %9 = OpAccessChain %_ptr_StorageBuffer_int %1 %uint_0
+       %load = OpLoad %int %9
+        %add = OpIAdd %int %load %int_1
+         %16 = OpAccessChain %_ptr_StorageBuffer_int %1 %uint_0
+               OpStore %16 %add
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, UniformVar) {
-    auto* v = b.Var(ty.ptr<uniform, i32>());
+    auto* v = b.Var("v", ty.ptr<uniform, i32>());
     v->SetBindingPoint(0, 0);
     b.RootBlock()->Append(v);
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpMemberName %3 0 "tint_symbol"
-OpName %3 "tint_symbol_1"
-OpName %5 "unused_entry_point"
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %3 Block
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Uniform %3
-%1 = OpVariable %2 Uniform
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+               OpDecorate %tint_symbol_1 Block
+               OpDecorate %1 DescriptorSet 0
+               OpDecorate %1 Binding 0
 )");
-}
-
-TEST_F(SpvGeneratorImplTest, UniformVar_Name) {
-    auto* v = b.Var("myvar", ty.ptr<uniform, i32>());
-    v->SetBindingPoint(0, 0);
-    b.RootBlock()->Append(v);
-
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "unused_entry_point"
-OpExecutionMode %5 LocalSize 1 1 1
-OpMemberName %3 0 "tint_symbol"
-OpName %3 "tint_symbol_1"
-OpName %5 "unused_entry_point"
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %3 Block
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Uniform %3
-%1 = OpVariable %2 Uniform
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
+    EXPECT_INST(R"(
+%tint_symbol_1 = OpTypeStruct %int
+%_ptr_Uniform_tint_symbol_1 = OpTypePointer Uniform %tint_symbol_1
+          %1 = OpVariable %_ptr_Uniform_tint_symbol_1 Uniform
 )");
 }
 
 TEST_F(SpvGeneratorImplTest, UniformVar_Load) {
-    auto* v = b.Var(ty.ptr<uniform, i32>());
+    auto* v = b.Var("v", ty.ptr<uniform, i32>());
     v->SetBindingPoint(0, 0);
     b.RootBlock()->Append(v);
 
     auto* func = b.Function("foo", ty.void_(), ir::Function::PipelineStage::kCompute,
                             std::array{1u, 1u, 1u});
-
     b.With(func->Block(), [&] {
-        b.Load(v);
+        auto* load = b.Load(v);
         b.Return(func);
+        mod.SetName(load, "load");
     });
 
-    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
-    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %5 "foo"
-OpExecutionMode %5 LocalSize 1 1 1
-OpMemberName %3 0 "tint_symbol"
-OpName %3 "tint_symbol_1"
-OpName %5 "foo"
-OpMemberDecorate %3 0 Offset 0
-OpDecorate %3 Block
-OpDecorate %1 DescriptorSet 0
-OpDecorate %1 Binding 0
-%4 = OpTypeInt 32 1
-%3 = OpTypeStruct %4
-%2 = OpTypePointer Uniform %3
-%1 = OpVariable %2 Uniform
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%10 = OpTypePointer Uniform %4
-%12 = OpTypeInt 32 0
-%11 = OpConstant %12 0
-%5 = OpFunction %6 None %7
-%8 = OpLabel
-%9 = OpAccessChain %10 %1 %11
-%13 = OpLoad %4 %9
-OpReturn
-OpFunctionEnd
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %9 = OpAccessChain %_ptr_Uniform_int %1 %uint_0
+       %load = OpLoad %int %9
 )");
 }
 
diff --git a/src/tint/writer/spirv/ir/test_helper_ir.h b/src/tint/writer/spirv/ir/test_helper_ir.h
index 27559be..5fbf6e4 100644
--- a/src/tint/writer/spirv/ir/test_helper_ir.h
+++ b/src/tint/writer/spirv/ir/test_helper_ir.h
@@ -17,7 +17,9 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "spirv-tools/libspirv.hpp"
 #include "src/tint/ir/builder.h"
@@ -57,7 +59,7 @@
     /// The SPIR-V generator.
     GeneratorImplIr generator_;
 
-    /// Validation errors
+    /// Errors produced during codegen or SPIR-V validation.
     std::string err_;
 
     /// SPIR-V output.
@@ -66,38 +68,34 @@
     /// @returns the error string from the validation
     std::string Error() const { return err_; }
 
-    /// @returns true if the IR module is valid
-    bool IRIsValid() {
-        auto res = ir::Validate(mod);
-        if (!res) {
-            err_ = res.Failure().str();
+    /// Run the specified generator on the IR module and validate the result.
+    /// @param generator the generator to use for SPIR-V generation
+    /// @returns true if generation and validation succeeded
+    bool Generate(GeneratorImplIr& generator) {
+        if (!generator.Generate()) {
+            err_ = generator.Diagnostics().str();
             return false;
         }
+
+        output_ = Disassemble(generator.Result(), SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
+                                                      SPV_BINARY_TO_TEXT_OPTION_INDENT |
+                                                      SPV_BINARY_TO_TEXT_OPTION_COMMENT);
+
+        if (!Validate(generator.Result())) {
+            return false;
+        }
+
         return true;
     }
 
     /// Run the generator on the IR module and validate the result.
     /// @returns true if generation and validation succeeded
-    bool Generate() {
-        if (!generator_.Generate()) {
-            err_ = generator_.Diagnostics().str();
-            return false;
-        }
-        if (!Validate()) {
-            return false;
-        }
-
-        output_ = Disassemble(generator_.Result(), SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
-                                                       SPV_BINARY_TO_TEXT_OPTION_INDENT |
-                                                       SPV_BINARY_TO_TEXT_OPTION_COMMENT);
-        return true;
-    }
+    bool Generate() { return Generate(generator_); }
 
     /// Validate the generated SPIR-V using the SPIR-V Tools Validator.
+    /// @param binary the SPIR-V binary module to validate
     /// @returns true if validation succeeded, false otherwise
-    bool Validate() {
-        auto binary = generator_.Result();
-
+    bool Validate(const std::vector<uint32_t>& binary) {
         std::string spv_errors;
         auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
                                           const spv_position_t& position, const char* message) {