Import Tint changes from Dawn

Changes:
  - a187e9e281254475088161ca906a905282f5663e spirv-reader: Error for OpSpecConstantComposite expression by James Price <jrprice@google.com>
  - b703afc061cb518b81664c2fdb7f23570edf8934 tint/utils: Support hetrogeneous hashmap key lookups by Ben Clayton <bclayton@google.com>
  - 4204bb3ef11e2ea9d10a7a18f32a4ed69cf870a9 tint/writer/spirv: Fix build on MSVC / some clang builds by Ben Clayton <bclayton@google.com>
  - 1dd578ad35a3be57ffe3ef59bf45c6664b5d1ada Revert "ir/spirv-writer: Emit entry point declarations" by Ben Clayton <bclayton@google.com>
  - 146b67e0cdfae428a4787b0760b56649f49e9af6 tint/ir: Replace getter with raw fields for more classes by Ben Clayton <bclayton@google.com>
  - 895d240fbf9ec44dfd8900fa13b0aa2dc6dea880 tint/ir: Replace Converter with FromProgram() free function by Ben Clayton <bclayton@google.com>
  - 90789ea1f8f6aad42238dde3d75d39d8e07d0d60 ir/spirv-writer: Emit entry point declarations by James Price <jrprice@google.com>
  - 436fffe2a13cb8c4074fdd09944bc8b72a873788 ir/spirv-writer: Emit function declarations by James Price <jrprice@google.com>
  - c9734160bcf0509faf4f2a10296b8305fe1ba2a2 [ir] Add tests for compound operators. by dan sinclair <dsinclair@chromium.org>
  - eae9902c815a31bc0bcd4c465111fdf622c54ebe [ir] Add store test by dan sinclair <dsinclair@chromium.org>
  - 02b5b224e3d1591f9b9f7d53ec46779c90ecba7d ir/spirv-writer: Add support for scalar types by James Price <jrprice@google.com>
  - b169165633d5023111db83c22acaf71588d73f19 [ir] Add tests for unary conversion. by dan sinclair <dsinclair@chromium.org>
  - 63716c55cc4f6ea9872bf736a3d2b1e6d2a19d1f [ir] Spit builder_impl tests. by dan sinclair <dsinclair@chromium.org>
  - 34f41c7bad4ab5fbe90d9f6ca6e987e8333d3b2c [ir] Change unary not to a binary equal by dan sinclair <dsinclair@chromium.org>
  - e964f5163c9fe1cde50407c36aecde033079ce72 [ir] Update type display in disassembly, remove string me... by dan sinclair <dsinclair@chromium.org>
  - b298b6a222e2dddea59e4b126734976395d1be44 ir/function: Add missing <array> include by James Price <jrprice@google.com>
  - 29bff642fc0b1624350a78c673366934335a261b ir: Use std::optional::value_or to simplify code by Ben Clayton <bclayton@google.com>
  - f789854a94287fafd0b08abb9b67fe6bca95287f tint: fix undetected overflow in const-eval refract by Antonio Maiorano <amaiorano@google.com>
  - 8f9ea96c20734fb17d69576bfc4af7d7a224f466 tint/writer/spirv: Add path for generating from IR by James Price <jrprice@google.com>
  - 057b7f326e6383944e5ade235f2ee70d7fb568cd tint/writer/spirv: Use Diagnostics() for errors by James Price <jrprice@google.com>
  - cb8f3308a3e24388ee0e28ceb5a0b5bbeafdd24b spirv-reader: Error for OpUndef image argument by James Price <jrprice@google.com>
  - f885a90a5feabbf75ead60f60200098ea80adb9f tint/writer/spirv: Create Module class by James Price <jrprice@google.com>
  - 09b02ffc7bcb4b95f52315f1a11bfc833f69ec27 [ir] Split the Terminator into two nodes. by dan sinclair <dsinclair@chromium.org>
  - 69bb5dd816781b7490c9a3cc6a5de1f186bc9f2f [ir] Add function return information. by dan sinclair <dsinclair@chromium.org>
  - 9d9a38336e0571ce6fa6fccd2e7868fcc341c04a [ir] Add function attributes by dan sinclair <dsinclair@chromium.org>
  - 4cadbc4daf9fea260feb80f570674a9f71d7d50a [ir] Handle IdentifierExpression by dan sinclair <dsinclair@chromium.org>
  - 1545ca191bd5b4a9f31e10c9924e7cdbc395ea29 tint: Remove Program|ProgramBuilder::FriendlyName() by Ben Clayton <bclayton@google.com>
  - 6ac51c1c572d0327e62c53f6eeac388279cf9aec [ir] Update binary and unary names. by dan sinclair <dsinclair@chromium.org>
  - fa00fe9d417fbbbfcdae4390a85faedc9dc6dfb0 tint/hlsl+glsl: fix workgroupUniformLoad polyfills by Antonio Maiorano <amaiorano@google.com>
  - e903396ff2c5d4c205bffc5c6b91cb48ebefd5f7 [ir] Emit short-circuit as an `If` node by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: a187e9e281254475088161ca906a905282f5663e
Change-Id: Iab6728a5ca72ed77818aa6bb5095315ba47fa4a8
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/131680
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index c6affb1..8cf3daa 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -955,6 +955,8 @@
     "writer/spirv/generator_impl.h",
     "writer/spirv/instruction.cc",
     "writer/spirv/instruction.h",
+    "writer/spirv/module.cc",
+    "writer/spirv/module.h",
     "writer/spirv/operand.cc",
     "writer/spirv/operand.h",
     "writer/spirv/scalar_constant.h",
@@ -972,6 +974,17 @@
     ":libtint_utils_src",
     ":libtint_writer_src",
   ]
+
+  if (tint_build_ir) {
+    sources += [
+      "writer/spirv/generator_impl_ir.cc",
+      "writer/spirv/generator_impl_ir.h",
+    ]
+    deps += [
+      ":libtint_ir_builder_src",
+      ":libtint_ir_src",
+    ]
+  }
 }
 
 libtint_source_set("libtint_wgsl_reader_src") {
@@ -1109,8 +1122,8 @@
   sources = [
     "ir/builder_impl.cc",
     "ir/builder_impl.h",
-    "ir/converter.cc",
-    "ir/converter.h",
+    "ir/from_program.cc",
+    "ir/from_program.h",
   ]
   deps = [
     ":libtint_ast_src",
@@ -1118,6 +1131,7 @@
     ":libtint_ir_src",
     ":libtint_program_src",
     ":libtint_sem_src",
+    ":libtint_symbols_src",
     ":libtint_type_src",
     ":libtint_utils_src",
   ]
@@ -1153,6 +1167,8 @@
     "ir/flow_node.h",
     "ir/function.cc",
     "ir/function.h",
+    "ir/function_terminator.cc",
+    "ir/function_terminator.h",
     "ir/if.cc",
     "ir/if.h",
     "ir/instruction.cc",
@@ -1161,12 +1177,12 @@
     "ir/loop.h",
     "ir/module.cc",
     "ir/module.h",
+    "ir/root_terminator.cc",
+    "ir/root_terminator.h",
     "ir/store.cc",
     "ir/store.h",
     "ir/switch.cc",
     "ir/switch.h",
-    "ir/terminator.cc",
-    "ir/terminator.h",
     "ir/unary.cc",
     "ir/unary.h",
     "ir/user_call.cc",
@@ -1825,6 +1841,7 @@
       "writer/spirv/builder_type_test.cc",
       "writer/spirv/builder_unary_op_expression_test.cc",
       "writer/spirv/instruction_test.cc",
+      "writer/spirv/module_test.cc",
       "writer/spirv/operand_test.cc",
       "writer/spirv/scalar_constant_test.cc",
       "writer/spirv/spv_dump.cc",
@@ -1839,6 +1856,15 @@
       ":tint_unittests_ast_src",
       "${tint_spirv_tools_dir}/:spvtools",
     ]
+
+    if (tint_build_ir) {
+      sources += [
+        "writer/spirv/generator_impl_function_test.cc",
+        "writer/spirv/generator_impl_ir_test.cc",
+        "writer/spirv/generator_impl_type_test.cc",
+      ]
+      deps += [ ":libtint_ir_src" ]
+    }
   }
 
   tint_unittests_source_set("tint_unittests_wgsl_reader_src") {
@@ -2120,7 +2146,14 @@
     sources = [
       "ir/binary_test.cc",
       "ir/bitcast_test.cc",
+      "ir/builder_impl_binary_test.cc",
+      "ir/builder_impl_call_test.cc",
+      "ir/builder_impl_literal_test.cc",
+      "ir/builder_impl_materialize_test.cc",
+      "ir/builder_impl_store_test.cc",
       "ir/builder_impl_test.cc",
+      "ir/builder_impl_unary_test.cc",
+      "ir/builder_impl_var_test.cc",
       "ir/constant_test.cc",
       "ir/discard_test.cc",
       "ir/store_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 2e1ded8..f47d2ce 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -644,10 +644,19 @@
     writer/spirv/generator_impl.h
     writer/spirv/instruction.cc
     writer/spirv/instruction.h
+    writer/spirv/module.cc
+    writer/spirv/module.h
     writer/spirv/operand.cc
     writer/spirv/operand.h
     writer/spirv/scalar_constant.h
   )
+
+  if(${TINT_BUILD_IR})
+    list(APPEND TINT_LIB_SRCS
+      writer/spirv/generator_impl_ir.cc
+      writer/spirv/generator_impl_ir.h
+    )
+  endif()
 endif()
 
 if(${TINT_BUILD_WGSL_WRITER})
@@ -718,18 +727,20 @@
     ir/construct.h
     ir/convert.cc
     ir/convert.h
-    ir/converter.cc
-    ir/converter.h
     ir/debug.cc
     ir/debug.h
     ir/disassembler.cc
     ir/disassembler.h
     ir/discard.cc
     ir/discard.h
+    ir/from_program.cc
+    ir/from_program.h
     ir/flow_node.cc
     ir/flow_node.h
     ir/function.cc
     ir/function.h
+    ir/function_terminator.cc
+    ir/function_terminator.h
     ir/if.cc
     ir/if.h
     ir/instruction.cc
@@ -738,12 +749,12 @@
     ir/loop.h
     ir/module.cc
     ir/module.h
+    ir/root_terminator.cc
+    ir/root_terminator.h
     ir/store.cc
     ir/store.h
     ir/switch.cc
     ir/switch.h
-    ir/terminator.cc
-    ir/terminator.h
     ir/unary.cc
     ir/unary.h
     ir/user_call.cc
@@ -1206,12 +1217,22 @@
       writer/spirv/builder_type_test.cc
       writer/spirv/builder_unary_op_expression_test.cc
       writer/spirv/instruction_test.cc
+      writer/spirv/module_test.cc
       writer/spirv/operand_test.cc
       writer/spirv/scalar_constant_test.cc
       writer/spirv/spv_dump.cc
       writer/spirv/spv_dump.h
       writer/spirv/test_helper.h
     )
+
+    if(${TINT_BUILD_IR})
+      list(APPEND TINT_TEST_SRCS
+        writer/spirv/generator_impl_function_test.cc
+        writer/spirv/generator_impl_ir_test.cc
+        writer/spirv/generator_impl_type_test.cc
+        writer/spirv/test_helper_ir.h
+      )
+    endif()
   endif()
 
   if(${TINT_BUILD_WGSL_WRITER})
@@ -1422,7 +1443,14 @@
     list(APPEND TINT_TEST_SRCS
       ir/binary_test.cc
       ir/bitcast_test.cc
+      ir/builder_impl_binary_test.cc
+      ir/builder_impl_call_test.cc
+      ir/builder_impl_literal_test.cc
+      ir/builder_impl_materialize_test.cc
+      ir/builder_impl_store_test.cc
       ir/builder_impl_test.cc
+      ir/builder_impl_unary_test.cc
+      ir/builder_impl_var_test.cc
       ir/constant_test.cc
       ir/discard_test.cc
       ir/store_test.cc
diff --git a/src/tint/cmd/loopy.cc b/src/tint/cmd/loopy.cc
index 75102eb..34ded9d 100644
--- a/src/tint/cmd/loopy.cc
+++ b/src/tint/cmd/loopy.cc
@@ -19,7 +19,7 @@
 #include "tint/tint.h"
 
 #if TINT_BUILD_IR
-#include "src/tint/ir/converter.h"
+#include "src/tint/ir/from_program.h"
 #include "src/tint/ir/module.h"
 #endif  // TINT_BUILD_IR
 
@@ -378,7 +378,7 @@
             loop_count = options.loop_count;
         }
         for (uint32_t i = 0; i < loop_count; ++i) {
-            auto result = tint::ir::Converter::FromProgram(program.get());
+            auto result = tint::ir::FromProgram(program.get());
             if (!result) {
                 std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
             }
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index c8260f1..76a9256 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -49,9 +49,9 @@
 #include "tint/tint.h"
 
 #if TINT_BUILD_IR
-#include "src/tint/ir/converter.h"     // nogncheck
 #include "src/tint/ir/debug.h"         // nogncheck
 #include "src/tint/ir/disassembler.h"  // nogncheck
+#include "src/tint/ir/from_program.h"  // nogncheck
 #include "src/tint/ir/module.h"        // nogncheck
 #endif                                 // TINT_BUILD_IR
 
@@ -111,6 +111,7 @@
 #if TINT_BUILD_IR
     bool dump_ir = false;
     bool dump_ir_graph = false;
+    bool use_ir = false;
 #endif  // TINT_BUILD_IR
 
 #if TINT_BUILD_SYNTAX_TREE_WRITER
@@ -388,6 +389,8 @@
             opts->dump_ir = true;
         } else if (arg == "--dump-ir-graph") {
             opts->dump_ir_graph = true;
+        } else if (arg == "--use-ir") {
+            opts->use_ir = true;
 #endif  // TINT_BUILD_IR
 #if TINT_BUILD_SYNTAX_TREE_WRITER
         } else if (arg == "--dump-ast") {
@@ -548,6 +551,9 @@
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(program);
+#if TINT_BUILD_IR
+    gen_options.use_tint_ir = options.use_ir;
+#endif
     auto result = tint::writer::spirv::Generate(program, gen_options);
     if (!result.success) {
         tint::cmd::PrintWGSL(std::cerr, *program);
@@ -1023,7 +1029,8 @@
 #if TINT_BUILD_IR
         usage +=
             "  --dump-ir                 -- Writes the IR to stdout\n"
-            "  --dump-ir-graph           -- Writes the IR graph to 'tint.dot' as a dot graph\n";
+            "  --dump-ir-graph           -- Writes the IR graph to 'tint.dot' as a dot graph\n"
+            "  --use-ir                  -- Use the IR for writers and transforms when possible\n";
 #endif  // TINT_BUILD_IR
 #if TINT_BUILD_SYNTAX_TREE_WRITER
         usage += "  --dump-ast                -- Writes the AST to stdout\n";
@@ -1079,7 +1086,7 @@
 
 #if TINT_BUILD_IR
     if (options.dump_ir || options.dump_ir_graph) {
-        auto result = tint::ir::Converter::FromProgram(program.get());
+        auto result = tint::ir::FromProgram(program.get());
         if (!result) {
             std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
         } else {
diff --git a/src/tint/ir/binary.cc b/src/tint/ir/binary.cc
index d3e3548..a6a6aa7 100644
--- a/src/tint/ir/binary.cc
+++ b/src/tint/ir/binary.cc
@@ -19,8 +19,8 @@
 
 namespace tint::ir {
 
-Binary::Binary(uint32_t id, Kind kind, const type::Type* ty, Value* lhs, Value* rhs)
-    : Base(id, ty), kind_(kind), lhs_(lhs), rhs_(rhs) {
+Binary::Binary(uint32_t identifier, Kind kind, const type::Type* ty, Value* lhs, Value* rhs)
+    : Base(identifier, ty), kind_(kind), lhs_(lhs), rhs_(rhs) {
     TINT_ASSERT(IR, lhs_);
     TINT_ASSERT(IR, rhs_);
     lhs_->AddUsage(this);
@@ -29,69 +29,4 @@
 
 Binary::~Binary() = default;
 
-utils::StringStream& Binary::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = ";
-
-    switch (GetKind()) {
-        case Binary::Kind::kAdd:
-            out << "add";
-            break;
-        case Binary::Kind::kSubtract:
-            out << "sub";
-            break;
-        case Binary::Kind::kMultiply:
-            out << "mul";
-            break;
-        case Binary::Kind::kDivide:
-            out << "div";
-            break;
-        case Binary::Kind::kModulo:
-            out << "mod";
-            break;
-        case Binary::Kind::kAnd:
-            out << "bit_and";
-            break;
-        case Binary::Kind::kOr:
-            out << "bit_or";
-            break;
-        case Binary::Kind::kXor:
-            out << "bit_xor";
-            break;
-        case Binary::Kind::kLogicalAnd:
-            out << "log_and";
-            break;
-        case Binary::Kind::kLogicalOr:
-            out << "log_or";
-            break;
-        case Binary::Kind::kEqual:
-            out << "eq";
-            break;
-        case Binary::Kind::kNotEqual:
-            out << "neq";
-            break;
-        case Binary::Kind::kLessThan:
-            out << "lt";
-            break;
-        case Binary::Kind::kGreaterThan:
-            out << "gt";
-            break;
-        case Binary::Kind::kLessThanEqual:
-            out << "lte";
-            break;
-        case Binary::Kind::kGreaterThanEqual:
-            out << "gte";
-            break;
-        case Binary::Kind::kShiftLeft:
-            out << "shiftl";
-            break;
-        case Binary::Kind::kShiftRight:
-            out << "shiftr";
-            break;
-    }
-    out << " ";
-    lhs_->ToValue(out) << ", ";
-    rhs_->ToValue(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/binary.h b/src/tint/ir/binary.h
index 92a1998..c686797 100644
--- a/src/tint/ir/binary.h
+++ b/src/tint/ir/binary.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -36,9 +35,6 @@
         kOr,
         kXor,
 
-        kLogicalAnd,
-        kLogicalOr,
-
         kEqual,
         kNotEqual,
         kLessThan,
@@ -73,11 +69,6 @@
     /// @returns the right-hand-side value for the instruction
     const Value* RHS() const { return rhs_; }
 
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
   private:
     Kind kind_;
     Value* lhs_ = nullptr;
diff --git a/src/tint/ir/binary_test.cc b/src/tint/ir/binary_test.cc
index e03a61d..6a96633 100644
--- a/src/tint/ir/binary_test.cc
+++ b/src/tint/ir/binary_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/test_helper.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 namespace {
@@ -42,10 +41,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = bit_and 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateOr) {
@@ -66,10 +61,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = bit_or 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateXor) {
@@ -90,58 +81,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = bit_xor 4i, 2i");
-}
-
-TEST_F(IR_InstructionTest, CreateLogicalAnd) {
-    auto& b = CreateEmptyBuilder();
-
-    const auto* inst = b.builder.LogicalAnd(b.builder.ir.types.Get<type::Bool>(),
-                                            b.builder.Constant(4_i), b.builder.Constant(2_i));
-
-    ASSERT_TRUE(inst->Is<Binary>());
-    EXPECT_EQ(inst->GetKind(), Binary::Kind::kLogicalAnd);
-
-    ASSERT_TRUE(inst->LHS()->Is<Constant>());
-    auto lhs = inst->LHS()->As<Constant>()->value;
-    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
-    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    ASSERT_TRUE(inst->RHS()->Is<Constant>());
-    auto rhs = inst->RHS()->As<Constant>()->value;
-    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
-    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = log_and 4i, 2i");
-}
-
-TEST_F(IR_InstructionTest, CreateLogicalOr) {
-    auto& b = CreateEmptyBuilder();
-
-    const auto* inst = b.builder.LogicalOr(b.builder.ir.types.Get<type::Bool>(),
-                                           b.builder.Constant(4_i), b.builder.Constant(2_i));
-
-    ASSERT_TRUE(inst->Is<Binary>());
-    EXPECT_EQ(inst->GetKind(), Binary::Kind::kLogicalOr);
-
-    ASSERT_TRUE(inst->LHS()->Is<Constant>());
-    auto lhs = inst->LHS()->As<Constant>()->value;
-    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
-    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    ASSERT_TRUE(inst->RHS()->Is<Constant>());
-    auto rhs = inst->RHS()->As<Constant>()->value;
-    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
-    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = log_or 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateEqual) {
@@ -162,10 +101,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = eq 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateNotEqual) {
@@ -186,10 +121,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = neq 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateLessThan) {
@@ -210,10 +141,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = lt 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateGreaterThan) {
@@ -234,10 +161,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = gt 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateLessThanEqual) {
@@ -258,10 +181,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = lte 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
@@ -282,10 +201,25 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
+}
 
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = gte 4i, 2i");
+TEST_F(IR_InstructionTest, CreateNot) {
+    auto& b = CreateEmptyBuilder();
+    const auto* inst =
+        b.builder.Not(b.builder.ir.types.Get<type::Bool>(), b.builder.Constant(true));
+
+    ASSERT_TRUE(inst->Is<Binary>());
+    EXPECT_EQ(inst->GetKind(), Binary::Kind::kEqual);
+
+    ASSERT_TRUE(inst->LHS()->Is<Constant>());
+    auto lhs = inst->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<bool>>());
+    EXPECT_TRUE(lhs->As<constant::Scalar<bool>>()->ValueAs<bool>());
+
+    ASSERT_TRUE(inst->RHS()->Is<Constant>());
+    auto rhs = inst->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<bool>>());
+    EXPECT_FALSE(rhs->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_InstructionTest, CreateShiftLeft) {
@@ -306,10 +240,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = shiftl 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateShiftRight) {
@@ -330,10 +260,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = shiftr 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateAdd) {
@@ -354,10 +280,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = add 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateSubtract) {
@@ -378,10 +300,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = sub 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateMultiply) {
@@ -402,10 +320,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = mul 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateDivide) {
@@ -426,10 +340,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = div 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, CreateModulo) {
@@ -450,10 +360,6 @@
     auto rhs = inst->RHS()->As<Constant>()->value;
     ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = mod 4i, 2i");
 }
 
 TEST_F(IR_InstructionTest, Binary_Usage) {
diff --git a/src/tint/ir/bitcast.cc b/src/tint/ir/bitcast.cc
index 70f412c..89eedca 100644
--- a/src/tint/ir/bitcast.cc
+++ b/src/tint/ir/bitcast.cc
@@ -19,15 +19,9 @@
 
 namespace tint::ir {
 
-Bitcast::Bitcast(uint32_t id, const type::Type* type, Value* val)
-    : Base(id, type, utils::Vector{val}) {}
+Bitcast::Bitcast(uint32_t identifier, const type::Type* ty, Value* val)
+    : Base(identifier, ty, utils::Vector{val}) {}
 
 Bitcast::~Bitcast() = default;
 
-utils::StringStream& Bitcast::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = bitcast ";
-    EmitArgs(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/bitcast.h b/src/tint/ir/bitcast.h
index 253ea0b..0443701 100644
--- a/src/tint/ir/bitcast.h
+++ b/src/tint/ir/bitcast.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/ir/call.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -35,11 +34,6 @@
 
     Bitcast& operator=(const Bitcast& inst) = delete;
     Bitcast& operator=(Bitcast&& inst) = delete;
-
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/bitcast_test.cc b/src/tint/ir/bitcast_test.cc
index a70fb92..dfe7461 100644
--- a/src/tint/ir/bitcast_test.cc
+++ b/src/tint/ir/bitcast_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/test_helper.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 namespace {
@@ -31,15 +30,11 @@
     ASSERT_TRUE(inst->Is<ir::Bitcast>());
     ASSERT_NE(inst->Type(), nullptr);
 
-    ASSERT_EQ(inst->Args().Length(), 1u);
-    ASSERT_TRUE(inst->Args()[0]->Is<Constant>());
-    auto val = inst->Args()[0]->As<Constant>()->value;
+    ASSERT_EQ(inst->args.Length(), 1u);
+    ASSERT_TRUE(inst->args[0]->Is<Constant>());
+    auto val = inst->args[0]->As<Constant>()->value;
     ASSERT_TRUE(val->Is<constant::Scalar<i32>>());
     EXPECT_EQ(4_i, val->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = bitcast 4i");
 }
 
 TEST_F(IR_InstructionTest, Bitcast_Usage) {
@@ -47,10 +42,10 @@
     const auto* inst =
         b.builder.Bitcast(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
 
-    ASSERT_EQ(inst->Args().Length(), 1u);
-    ASSERT_NE(inst->Args()[0], nullptr);
-    ASSERT_EQ(inst->Args()[0]->Usage().Length(), 1u);
-    EXPECT_EQ(inst->Args()[0]->Usage()[0], inst);
+    ASSERT_EQ(inst->args.Length(), 1u);
+    ASSERT_NE(inst->args[0], nullptr);
+    ASSERT_EQ(inst->args[0]->Usage().Length(), 1u);
+    EXPECT_EQ(inst->args[0]->Usage()[0], inst);
 }
 
 }  // namespace
diff --git a/src/tint/ir/branch.h b/src/tint/ir/branch.h
index 4fc8d7c..667f131 100644
--- a/src/tint/ir/branch.h
+++ b/src/tint/ir/branch.h
@@ -26,7 +26,7 @@
     FlowNode* target = nullptr;
 
     /// The arguments provided for that branch. These arguments could be the
-    /// return value in the case of a branch to the terminator, or they could
+    /// return value in the case of a branch to the function terminator, or they could
     /// be the basic block arguments passed into the block.
     utils::Vector<Value*, 2> args;
 };
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 2c97737..e826be4 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -16,6 +16,8 @@
 
 #include <utility>
 
+#include "src/tint/constant/scalar.h"
+
 namespace tint::ir {
 
 Builder::Builder() {}
@@ -29,8 +31,8 @@
         ir.root_block = CreateBlock();
 
         // Everything in the module scope must have been const-eval's, so everything will go into a
-        // single block. So, we can create the terminator for the root-block now.
-        ir.root_block->branch.target = CreateTerminator();
+        // single block. So, we can create the root terminator for the root-block now.
+        ir.root_block->branch.target = CreateRootTerminator();
     }
     return ir.root_block;
 }
@@ -39,14 +41,18 @@
     return ir.flow_nodes.Create<Block>();
 }
 
-Terminator* Builder::CreateTerminator() {
-    return ir.flow_nodes.Create<Terminator>();
+RootTerminator* Builder::CreateRootTerminator() {
+    return ir.flow_nodes.Create<RootTerminator>();
+}
+
+FunctionTerminator* Builder::CreateFunctionTerminator() {
+    return ir.flow_nodes.Create<FunctionTerminator>();
 }
 
 Function* Builder::CreateFunction() {
     auto* ir_func = ir.flow_nodes.Create<Function>();
     ir_func->start_target = CreateBlock();
-    ir_func->end_target = CreateTerminator();
+    ir_func->end_target = CreateFunctionTerminator();
 
     // Function is always branching into the start target
     ir_func->start_target->inbound_branches.Push(ir_func);
@@ -118,14 +124,6 @@
     return CreateBinary(Binary::Kind::kXor, type, lhs, rhs);
 }
 
-Binary* Builder::LogicalAnd(const type::Type* type, Value* lhs, Value* rhs) {
-    return CreateBinary(Binary::Kind::kLogicalAnd, type, lhs, rhs);
-}
-
-Binary* Builder::LogicalOr(const type::Type* type, Value* lhs, Value* rhs) {
-    return CreateBinary(Binary::Kind::kLogicalOr, type, lhs, rhs);
-}
-
 Binary* Builder::Equal(const type::Type* type, Value* lhs, Value* rhs) {
     return CreateBinary(Binary::Kind::kEqual, type, lhs, rhs);
 }
@@ -198,8 +196,8 @@
     return CreateUnary(Unary::Kind::kNegation, type, val);
 }
 
-Unary* Builder::Not(const type::Type* type, Value* val) {
-    return CreateUnary(Unary::Kind::kNot, type, val);
+Binary* Builder::Not(const type::Type* type, Value* val) {
+    return Equal(type, val, Constant(create<constant::Scalar<bool>>(type, false)));
 }
 
 ir::Bitcast* Builder::Bitcast(const type::Type* type, Value* val) {
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index d509c06..ff01e2d 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -26,12 +26,13 @@
 #include "src/tint/ir/convert.h"
 #include "src/tint/ir/discard.h"
 #include "src/tint/ir/function.h"
+#include "src/tint/ir/function_terminator.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/module.h"
+#include "src/tint/ir/root_terminator.h"
 #include "src/tint/ir/store.h"
 #include "src/tint/ir/switch.h"
-#include "src/tint/ir/terminator.h"
 #include "src/tint/ir/unary.h"
 #include "src/tint/ir/user_call.h"
 #include "src/tint/ir/value.h"
@@ -59,8 +60,11 @@
     /// @returns a new block flow node
     Block* CreateBlock();
 
-    /// @returns a new terminator flow node
-    Terminator* CreateTerminator();
+    /// @returns a new root terminator flow node
+    RootTerminator* CreateRootTerminator();
+
+    /// @returns a new function terminator flow node
+    FunctionTerminator* CreateFunctionTerminator();
 
     /// Creates a function flow node
     /// @returns the flow node
@@ -170,20 +174,6 @@
     /// @returns the operation
     Binary* Xor(const type::Type* type, Value* lhs, Value* rhs);
 
-    /// Creates an LogicalAnd operation
-    /// @param type the result type of the expression
-    /// @param lhs the lhs of the add
-    /// @param rhs the rhs of the add
-    /// @returns the operation
-    Binary* LogicalAnd(const type::Type* type, Value* lhs, Value* rhs);
-
-    /// Creates an LogicalOr operation
-    /// @param type the result type of the expression
-    /// @param lhs the lhs of the add
-    /// @param rhs the rhs of the add
-    /// @returns the operation
-    Binary* LogicalOr(const type::Type* type, Value* lhs, Value* rhs);
-
     /// Creates an Equal operation
     /// @param type the result type of the expression
     /// @param lhs the lhs of the add
@@ -310,7 +300,7 @@
     /// @param type the result type of the expression
     /// @param val the value
     /// @returns the operation
-    Unary* Not(const type::Type* type, Value* val);
+    Binary* Not(const type::Type* type, Value* val);
 
     /// Creates a bitcast instruction
     /// @param type the result type of the bitcast
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 19caf6f..ef4a94e 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -39,6 +39,7 @@
 #include "src/tint/ast/identifier_expression.h"
 #include "src/tint/ast/if_statement.h"
 #include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/ast/invariant_attribute.h"
 #include "src/tint/ast/let.h"
 #include "src/tint/ast/literal_expression.h"
 #include "src/tint/ast/loop_statement.h"
@@ -60,11 +61,11 @@
 #include "src/tint/ir/module.h"
 #include "src/tint/ir/store.h"
 #include "src/tint/ir/switch.h"
-#include "src/tint/ir/terminator.h"
 #include "src/tint/ir/value.h"
 #include "src/tint/program.h"
 #include "src/tint/sem/builtin.h"
 #include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
 #include "src/tint/sem/materialize.h"
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/switch_statement.h"
@@ -74,6 +75,7 @@
 #include "src/tint/sem/variable.h"
 #include "src/tint/switch.h"
 #include "src/tint/type/void.h"
+#include "src/tint/utils/defer.h"
 #include "src/tint/utils/scoped_assignment.h"
 
 namespace tint::ir {
@@ -209,19 +211,85 @@
 
     ast_to_flow_[ast_func] = ir_func;
 
+    const auto* sem = program_->Sem().Get(ast_func);
     if (ast_func->IsEntryPoint()) {
         builder.ir.entry_points.Push(ir_func);
+
+        switch (ast_func->PipelineStage()) {
+            case ast::PipelineStage::kVertex:
+                ir_func->pipeline_stage = Function::PipelineStage::kVertex;
+                break;
+            case ast::PipelineStage::kFragment:
+                ir_func->pipeline_stage = Function::PipelineStage::kFragment;
+                break;
+            case ast::PipelineStage::kCompute: {
+                ir_func->pipeline_stage = Function::PipelineStage::kCompute;
+
+                auto wg_size = sem->WorkgroupSize();
+                ir_func->workgroup_size = {
+                    wg_size[0].value(),
+                    wg_size[1].value_or(1),
+                    wg_size[2].value_or(1),
+                };
+                break;
+            }
+            default: {
+                TINT_ICE(IR, diagnostics_) << "Invalid pipeline stage";
+                return;
+            }
+        }
+
+        for (auto* attr : ast_func->return_type_attributes) {
+            tint::Switch(
+                attr,  //
+                [&](const ast::LocationAttribute*) {
+                    ir_func->return_attributes.Push(Function::ReturnAttribute::kLocation);
+                },
+                [&](const ast::InvariantAttribute*) {
+                    ir_func->return_attributes.Push(Function::ReturnAttribute::kInvariant);
+                },
+                [&](const ast::BuiltinAttribute* b) {
+                    if (auto* ident_sem =
+                            program_->Sem()
+                                .Get(b)
+                                ->As<sem::BuiltinEnumExpression<builtin::BuiltinValue>>()) {
+                        switch (ident_sem->Value()) {
+                            case builtin::BuiltinValue::kPosition:
+                                ir_func->return_attributes.Push(
+                                    Function::ReturnAttribute::kPosition);
+                                break;
+                            case builtin::BuiltinValue::kFragDepth:
+                                ir_func->return_attributes.Push(
+                                    Function::ReturnAttribute::kFragDepth);
+                                break;
+                            case builtin::BuiltinValue::kSampleMask:
+                                ir_func->return_attributes.Push(
+                                    Function::ReturnAttribute::kSampleMask);
+                                break;
+                            default:
+                                TINT_ICE(IR, diagnostics_)
+                                    << "Unknown builtin value in return attributes "
+                                    << ident_sem->Value();
+                                return;
+                        }
+                    } else {
+                        TINT_ICE(IR, diagnostics_) << "Builtin attribute sem invalid";
+                        return;
+                    }
+                });
+        }
     }
+    ir_func->return_type = sem->ReturnType()->Clone(clone_ctx_.type_ctx);
+    ir_func->return_location = sem->ReturnLocation();
 
     {
         FlowStackScope scope(this, ir_func);
 
         current_flow_block = ir_func->start_target;
-        EmitStatements(ast_func->body->statements);
+        EmitBlock(ast_func->body);
 
         // TODO(dsinclair): Store return type and attributes
         // TODO(dsinclair): Store parameters
-        // TODO(dsinclair): Store attributes
 
         // If the branch target has already been set then a `return` was called. Only set in the
         // case where `return` wasn't called.
@@ -309,30 +377,6 @@
         case ast::BinaryOp::kXor:
             inst = builder.Xor(ty, lhs.Get(), rhs.Get());
             break;
-        case ast::BinaryOp::kLogicalAnd:
-            inst = builder.LogicalAnd(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kLogicalOr:
-            inst = builder.LogicalOr(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kEqual:
-            inst = builder.Equal(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kNotEqual:
-            inst = builder.NotEqual(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kLessThan:
-            inst = builder.LessThan(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kGreaterThan:
-            inst = builder.GreaterThan(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kLessThanEqual:
-            inst = builder.LessThanEqual(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kGreaterThanEqual:
-            inst = builder.GreaterThanEqual(ty, lhs.Get(), rhs.Get());
-            break;
         case ast::BinaryOp::kShiftLeft:
             inst = builder.ShiftLeft(ty, lhs.Get(), rhs.Get());
             break;
@@ -354,6 +398,16 @@
         case ast::BinaryOp::kModulo:
             inst = builder.Modulo(ty, lhs.Get(), rhs.Get());
             break;
+        case ast::BinaryOp::kLessThanEqual:
+        case ast::BinaryOp::kGreaterThanEqual:
+        case ast::BinaryOp::kGreaterThan:
+        case ast::BinaryOp::kLessThan:
+        case ast::BinaryOp::kNotEqual:
+        case ast::BinaryOp::kEqual:
+        case ast::BinaryOp::kLogicalAnd:
+        case ast::BinaryOp::kLogicalOr:
+            TINT_ICE(IR, diagnostics_) << "invalid compound assignment";
+            return;
         case ast::BinaryOp::kNone:
             TINT_ICE(IR, diagnostics_) << "missing binary operand type";
             return;
@@ -365,9 +419,12 @@
 }
 
 void BuilderImpl::EmitBlock(const ast::BlockStatement* block) {
-    // Note, this doesn't need to emit a Block as the current block flow node should be
-    // sufficient as the blocks all get flattened. Each flow control node will inject the basic
-    // blocks it requires.
+    scopes_.Push();
+    TINT_DEFER(scopes_.Pop());
+
+    // Note, this doesn't need to emit a Block as the current block flow node should be sufficient
+    // as the blocks all get flattened. Each flow control node will inject the basic blocks it
+    // requires.
     EmitStatements(block->statements);
 }
 
@@ -389,7 +446,7 @@
         FlowStackScope scope(this, if_node);
 
         current_flow_block = if_node->true_.target->As<Block>();
-        EmitStatement(stmt->body);
+        EmitBlock(stmt->body);
 
         // If the true branch did not execute control flow, then go to the merge target
         BranchToIfNeeded(if_node->merge.target);
@@ -404,9 +461,8 @@
     }
     current_flow_block = nullptr;
 
-    // If both branches went somewhere, then they both returned, continued or broke. So,
-    // there is no need for the if merge-block and there is nothing to branch to the merge
-    // block anyway.
+    // If both branches went somewhere, then they both returned, continued or broke. So, there is no
+    // need for the if merge-block and there is nothing to branch to the merge block anyway.
     if (IsConnected(if_node->merge.target)) {
         current_flow_block = if_node->merge.target->As<Block>();
     }
@@ -423,22 +479,22 @@
         FlowStackScope scope(this, loop_node);
 
         current_flow_block = loop_node->start.target->As<Block>();
-        EmitStatement(stmt->body);
+        EmitBlock(stmt->body);
 
         // The current block didn't `break`, `return` or `continue`, go to the continuing block.
         BranchToIfNeeded(loop_node->continuing.target);
 
         current_flow_block = loop_node->continuing.target->As<Block>();
         if (stmt->continuing) {
-            EmitStatement(stmt->continuing);
+            EmitBlock(stmt->continuing);
         }
 
         // Branch back to the start node if the continue target didn't branch out already
         BranchToIfNeeded(loop_node->start.target);
     }
 
-    // The loop merge can get disconnected if the loop returns directly, or the continuing
-    // target branches, eventually, to the merge, but nothing branched to the continuing target.
+    // The loop merge can get disconnected if the loop returns directly, or the continuing target
+    // branches, eventually, to the merge, but nothing branched to the continuing target.
     current_flow_block = loop_node->merge.target->As<Block>();
     if (!IsConnected(loop_node->merge.target)) {
         current_flow_block = nullptr;
@@ -479,7 +535,7 @@
         BranchTo(if_node);
 
         current_flow_block = if_node->merge.target->As<Block>();
-        EmitStatement(stmt->body);
+        EmitBlock(stmt->body);
 
         BranchToIfNeeded(loop_node->continuing.target);
     }
@@ -494,6 +550,10 @@
     builder.Branch(loop_node->continuing.target->As<Block>(), loop_node->start.target,
                    utils::Empty);
 
+    // Make sure the initializer ends up in a contained scope
+    scopes_.Push();
+    TINT_DEFER(scopes_.Pop());
+
     if (stmt->initializer) {
         // Emit the for initializer before branching to the loop
         EmitStatement(stmt->initializer);
@@ -529,7 +589,7 @@
             current_flow_block = if_node->merge.target->As<Block>();
         }
 
-        EmitStatement(stmt->body);
+        EmitBlock(stmt->body);
         BranchToIfNeeded(loop_node->continuing.target);
 
         if (stmt->continuing) {
@@ -537,6 +597,7 @@
             EmitStatement(stmt->continuing);
         }
     }
+
     // The while loop always has a path to the merge target as the break statement comes before
     // anything inside the loop.
     current_flow_block = loop_node->merge.target->As<Block>();
@@ -571,7 +632,8 @@
             }
 
             current_flow_block = builder.CreateCase(switch_node, selectors);
-            EmitStatement(c->Body()->Declaration());
+            EmitBlock(c->Body()->Declaration());
+
             BranchToIfNeeded(switch_node->merge.target);
         }
     }
@@ -620,9 +682,9 @@
 }
 
 // Discard is being treated as an instruction. The semantics in WGSL is demote_to_helper, so the
-// code has to continue as before it just predicates writes. If WGSL grows some kind of
-// terminating discard that would probably make sense as a FlowNode but would then require
-// figuring out the multi-level exit that is triggered.
+// code has to continue as before it just predicates writes. If WGSL grows some kind of terminating
+// discard that would probably make sense as a FlowNode but would then require figuring out the
+// multi-level exit that is triggered.
 void BuilderImpl::EmitDiscard(const ast::DiscardStatement*) {
     auto* inst = builder.Discard();
     current_flow_block->instructions.Push(inst);
@@ -679,9 +741,10 @@
         [&](const ast::BinaryExpression* b) { return EmitBinary(b); },
         [&](const ast::BitcastExpression* b) { return EmitBitcast(b); },
         [&](const ast::CallExpression* c) { return EmitCall(c); },
-        // [&](const ast::IdentifierExpression* i) {
-        // TODO(dsinclair): Implement
-        // },
+        [&](const ast::IdentifierExpression* i) {
+            auto* v = scopes_.Get(i->identifier->symbol);
+            return utils::Result<Value*>{v};
+        },
         [&](const ast::LiteralExpression* l) { return EmitLiteral(l); },
         // [&](const ast::MemberAccessorExpression* m) {
         // TODO(dsinclair): Implement
@@ -716,7 +779,8 @@
                 auto* store = builder.Store(val, init.Get());
                 current_flow_block->instructions.Push(store);
             }
-            // TODO(dsinclair): Store the mapping from the var name to the `Declare` value
+            // Store the declaration so we can get the instruction to store too
+            scopes_.Set(v->name->symbol, val);
         },
         [&](const ast::Let* l) {
             // A `let` doesn't exist as a standalone item in the IR, it's just the result of the
@@ -725,7 +789,9 @@
             if (!init) {
                 return;
             }
-            // TODO(dsinclair): Store the mapping from the let name to the `init` value
+
+            // Store the results of the initialization
+            scopes_.Set(l->name->symbol, init.Get());
         },
         [&](const ast::Override*) {
             add_error(var->source,
@@ -738,8 +804,8 @@
             // should never be used.
             //
             // TODO(dsinclair): Probably want to store the const variable somewhere and then in
-            // identifier expression log an error if we ever see a const identifier. Add this
-            // when identifiers and variables are supported.
+            // identifier expression log an error if we ever see a const identifier. Add this when
+            // identifiers and variables are supported.
         },
         [&](Default) {
             add_error(var->source, "unknown variable: " + std::string(var->TypeInfo().name));
@@ -755,7 +821,7 @@
     auto* sem = program_->Sem().Get(expr);
     auto* ty = sem->Type()->Clone(clone_ctx_.type_ctx);
 
-    Unary* inst = nullptr;
+    Instruction* inst = nullptr;
     switch (expr->op) {
         case ast::UnaryOp::kAddressOf:
             inst = builder.AddressOf(ty, val.Get());
@@ -778,7 +844,68 @@
     return inst;
 }
 
+// A short-circut needs special treatment. The short-circuit is decomposed into the relevant if
+// statements and declarations.
+utils::Result<Value*> BuilderImpl::EmitShortCircuit(const ast::BinaryExpression* expr) {
+    switch (expr->op) {
+        case ast::BinaryOp::kLogicalAnd:
+        case ast::BinaryOp::kLogicalOr:
+            break;
+        default:
+            TINT_ICE(IR, diagnostics_) << "invalid operation type for short-circut decomposition";
+            return utils::Failure;
+    }
+
+    // Evaluate the LHS of the short-circuit
+    auto lhs = EmitExpression(expr->lhs);
+    if (!lhs) {
+        return utils::Failure;
+    }
+
+    // Generate a variable to store the short-circut into
+    auto* ty = builder.ir.types.Get<type::Bool>();
+    auto* result_var =
+        builder.Declare(ty, builtin::AddressSpace::kFunction, builtin::Access::kReadWrite);
+    current_flow_block->instructions.Push(result_var);
+
+    auto* lhs_store = builder.Store(result_var, lhs.Get());
+    current_flow_block->instructions.Push(lhs_store);
+
+    auto* if_node = builder.CreateIf();
+    if_node->condition = lhs.Get();
+    BranchTo(if_node);
+
+    utils::Result<Value*> rhs;
+    {
+        FlowStackScope scope(this, if_node);
+
+        // If this is an `&&` then we only evaluate the RHS expression in the true block.
+        // If this is an `||` then we only evaluate the RHS expression in the false block.
+        if (expr->op == ast::BinaryOp::kLogicalAnd) {
+            current_flow_block = if_node->true_.target->As<Block>();
+        } else {
+            current_flow_block = if_node->false_.target->As<Block>();
+        }
+
+        rhs = EmitExpression(expr->rhs);
+        if (!rhs) {
+            return utils::Failure;
+        }
+        auto* rhs_store = builder.Store(result_var, rhs.Get());
+        current_flow_block->instructions.Push(rhs_store);
+
+        BranchTo(if_node->merge.target);
+    }
+    current_flow_block = if_node->merge.target->As<Block>();
+
+    return result_var;
+}
+
 utils::Result<Value*> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
+    if (expr->op == ast::BinaryOp::kLogicalAnd || expr->op == ast::BinaryOp::kLogicalOr) {
+        return EmitShortCircuit(expr);
+    }
+
     auto lhs = EmitExpression(expr->lhs);
     if (!lhs) {
         return utils::Failure;
@@ -803,12 +930,6 @@
         case ast::BinaryOp::kXor:
             inst = builder.Xor(ty, lhs.Get(), rhs.Get());
             break;
-        case ast::BinaryOp::kLogicalAnd:
-            inst = builder.LogicalAnd(ty, lhs.Get(), rhs.Get());
-            break;
-        case ast::BinaryOp::kLogicalOr:
-            inst = builder.LogicalOr(ty, lhs.Get(), rhs.Get());
-            break;
         case ast::BinaryOp::kEqual:
             inst = builder.Equal(ty, lhs.Get(), rhs.Get());
             break;
@@ -848,6 +969,10 @@
         case ast::BinaryOp::kModulo:
             inst = builder.Modulo(ty, lhs.Get(), rhs.Get());
             break;
+        case ast::BinaryOp::kLogicalAnd:
+        case ast::BinaryOp::kLogicalOr:
+            TINT_ICE(IR, diagnostics_) << "short circuit op should have already been handled";
+            return utils::Failure;
         case ast::BinaryOp::kNone:
             TINT_ICE(IR, diagnostics_) << "missing binary operand type";
             return utils::Failure;
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 7b18492..f6bed12 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -26,6 +26,7 @@
 #include "src/tint/ir/flow_node.h"
 #include "src/tint/ir/module.h"
 #include "src/tint/ir/value.h"
+#include "src/tint/scope_stack.h"
 #include "src/tint/utils/result.h"
 
 // Forward Declarations
@@ -59,14 +60,6 @@
 class WhileStatement;
 class Variable;
 }  // namespace tint::ast
-namespace tint::ir {
-class Block;
-class If;
-class Function;
-class Loop;
-class Switch;
-class Terminator;
-}  // namespace tint::ir
 namespace tint::sem {
 class Builtin;
 }  // namespace tint::sem
@@ -166,6 +159,11 @@
     /// @returns the value storing the result if successful, utils::Failure otherwise
     utils::Result<Value*> EmitUnary(const ast::UnaryOpExpression* expr);
 
+    /// Emits a short-circult binary expression
+    /// @param expr the binary expression
+    /// @returns the value storing the result if successful, utils::Failure otherwise
+    utils::Result<Value*> EmitShortCircuit(const ast::BinaryExpression* expr);
+
     /// Emits a binary expression
     /// @param expr the binary expression
     /// @returns the value storing the result if successful, utils::Failure otherwise
@@ -227,19 +225,17 @@
 
     void add_error(const Source& s, const std::string& err);
 
-    const Program* program_ = nullptr;
-
     Symbol CloneSymbol(Symbol sym) const;
 
-    diag::List diagnostics_;
-
+    const Program* program_ = nullptr;
     Function* current_function_ = nullptr;
+    ScopeStack<Symbol, Value*> scopes_;
+    constant::CloneContext clone_ctx_;
+    diag::List diagnostics_;
 
     /// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node.
     /// Used for testing purposes.
     std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_;
-
-    constant::CloneContext clone_ctx_;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_binary_test.cc b/src/tint/ir/builder_impl_binary_test.cc
new file mode 100644
index 0000000..a9a8978
--- /dev/null
+++ b/src/tint/ir/builder_impl_binary_test.cc
@@ -0,0 +1,696 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Add) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Add(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = add %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundAdd) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kAdd);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = add %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Subtract) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Sub(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = sub %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundSubtract) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kSubtract);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = sub %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Multiply) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Mul(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = mul %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundMultiply) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kMultiply);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = mul %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Div) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Div(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = div %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundDiv) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kDivide);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = div %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Modulo) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Mod(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = mod %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundModulo) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kModulo);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = mod %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_And) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = And(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = and %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundAnd) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.bool_());
+    auto* expr = CompoundAssign("v1", false, ast::BinaryOp::kAnd);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, bool, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, bool, read_write> = and %1:ref<private, bool, read_write>, false
+  store %1:ref<private, bool, read_write>, %2:ref<private, bool, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Or) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Or(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = or %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundOr) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.bool_());
+    auto* expr = CompoundAssign("v1", false, ast::BinaryOp::kOr);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, bool, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, bool, read_write> = or %1:ref<private, bool, read_write>, false
+  store %1:ref<private, bool, read_write>, %2:ref<private, bool, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Xor) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Xor(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = xor %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundXor) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kXor);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = xor %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalAnd) {
+    Func("my_func", utils::Empty, ty.bool_(), utils::Vector{Return(true)});
+    auto* expr = LogicalAnd(Call("my_func"), false);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():bool
+  %fn1 = block
+  ret true
+func_end
+
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
+  %1:bool = call my_func
+  %2:bool = var function read_write
+  store %2:bool, %1:bool
+  branch %fn4
+
+  %fn4 = if %1:bool [t: %fn5, f: %fn6, m: %fn7]
+    # true branch
+    %fn5 = block
+    store %2:bool, false
+    branch %fn7
+
+  # if merge
+  %fn7 = block
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalOr) {
+    Func("my_func", utils::Empty, ty.bool_(), utils::Vector{Return(true)});
+    auto* expr = LogicalOr(Call("my_func"), true);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():bool
+  %fn1 = block
+  ret true
+func_end
+
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
+  %1:bool = call my_func
+  %2:bool = var function read_write
+  store %2:bool, %1:bool
+  branch %fn4
+
+  %fn4 = if %1:bool [t: %fn5, f: %fn6, m: %fn7]
+    # true branch
+    # false branch
+    %fn6 = block
+    store %2:bool, true
+    branch %fn7
+
+  # if merge
+  %fn7 = block
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Equal) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Equal(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:bool = eq %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_NotEqual) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = NotEqual(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:bool = neq %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThan) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = LessThan(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:bool = lt %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThan) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = GreaterThan(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:bool = gt %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThanEqual) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = LessThanEqual(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:bool = lte %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThanEqual) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = GreaterThanEqual(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:bool = gte %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftLeft) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Shl(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = shiftl %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundShiftLeft) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kShiftLeft);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = shiftl %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftRight) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
+    auto* expr = Shr(Call("my_func"), 4_u);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = shiftr %1:u32, 4u
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_CompoundShiftRight) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.u32());
+    auto* expr = CompoundAssign("v1", 1_u, ast::BinaryOp::kShiftRight);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ref<private, u32, read_write> = shiftr %1:ref<private, u32, read_write>, 1u
+  store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Compound) {
+    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(0_f)});
+
+    auto* expr = LogicalAnd(LessThan(Call("my_func"), 2_f),
+                            GreaterThan(2.5_f, Div(Call("my_func"), Mul(2.3_f, Call("my_func")))));
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():f32
+  %fn1 = block
+  ret 0.0f
+func_end
+
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
+  %1:f32 = call my_func
+  %2:bool = lt %1:f32, 2.0f
+  %3:bool = var function read_write
+  store %3:bool, %2:bool
+  branch %fn4
+
+  %fn4 = if %2:bool [t: %fn5, f: %fn6, m: %fn7]
+    # true branch
+    %fn5 = block
+    %4:f32 = call my_func
+    %5:f32 = call my_func
+    %6:f32 = mul 2.29999995231628417969f, %5:f32
+    %7:f32 = div %4:f32, %6:f32
+    %8:bool = gt 2.5f, %7:f32
+    store %3:bool, %8:bool
+    branch %fn7
+
+  # if merge
+  %fn7 = block
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Compound_WithConstEval) {
+    Func("my_func", utils::Vector{Param("p", ty.bool_())}, ty.bool_(), utils::Vector{Return(true)});
+    auto* expr = Call("my_func", LogicalAnd(LessThan(2.4_f, 2_f),
+                                            GreaterThan(2.5_f, Div(10_f, Mul(2.3_f, 9.4_f)))));
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():bool
+  %fn1 = block
+  ret true
+func_end
+
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
+  %1:bool = call my_func, false
+  ret
+func_end
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_call_test.cc b/src/tint/ir/builder_impl_call_test.cc
new file mode 100644
index 0000000..4564880
--- /dev/null
+++ b/src/tint/ir/builder_impl_call_test.cc
@@ -0,0 +1,153 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Bitcast) {
+    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(0_f)});
+
+    auto* expr = Bitcast<f32>(Call("my_func"));
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:f32 = call my_func
+%2:f32 = bitcast %1:f32
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitStatement_Discard) {
+    auto* expr = Discard();
+    Func("test_function", {}, ty.void_(), expr,
+         utils::Vector{
+             create<ast::StageAttribute>(ast::PipelineStage::kFragment),
+         });
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    b.EmitStatement(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(discard
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitStatement_UserFunction) {
+    Func("my_func", utils::Vector{Param("p", ty.f32())}, ty.void_(), utils::Empty);
+
+    auto* stmt = CallStmt(Call("my_func", Mul(2_a, 3_a)));
+    WrapInFunction(stmt);
+
+    auto& b = CreateBuilder();
+
+    InjectFlowBlock();
+    b.EmitStatement(stmt);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:void = call my_func, 6.0f
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Convert) {
+    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_i));
+    auto* expr = Call(ty.f32(), i);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+    ASSERT_TRUE(r);
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, i32, read_write> = var private read_write
+store %1:ref<private, i32, read_write>, 1i
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:f32 = convert i32, %1:ref<private, i32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_ConstructEmpty) {
+    auto* expr = vec3(ty.f32());
+    GlobalVar("i", builtin::AddressSpace::kPrivate, expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+    ASSERT_TRUE(r);
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, vec3<f32>, read_write> = var private read_write
+store %1:ref<private, vec3<f32>, read_write>, vec3<f32> 0.0f
+
+
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Construct) {
+    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
+    auto* expr = vec3(ty.f32(), 2_f, 3_f, i);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+    ASSERT_TRUE(r);
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, f32, read_write> = var private read_write
+store %1:ref<private, f32, read_write>, 1.0f
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:vec3<f32> = construct 2.0f, 3.0f, %1:ref<private, f32, read_write>
+  ret
+func_end
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_literal_test.cc b/src/tint/ir/builder_impl_literal_test.cc
new file mode 100644
index 0000000..090f3f8
--- /dev/null
+++ b/src/tint/ir/builder_impl_literal_test.cc
@@ -0,0 +1,115 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_True) {
+    auto* expr = Expr(true);
+    GlobalVar("a", ty.bool_(), builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<bool>>());
+    EXPECT_TRUE(val->As<constant::Scalar<bool>>()->ValueAs<bool>());
+}
+
+TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_False) {
+    auto* expr = Expr(false);
+    GlobalVar("a", ty.bool_(), builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<bool>>());
+    EXPECT_FALSE(val->As<constant::Scalar<bool>>()->ValueAs<bool>());
+}
+
+TEST_F(IR_BuilderImplTest, EmitLiteral_F32) {
+    auto* expr = Expr(1.2_f);
+    GlobalVar("a", ty.f32(), builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<f32>>());
+    EXPECT_EQ(1.2_f, val->As<constant::Scalar<f32>>()->ValueAs<f32>());
+}
+
+TEST_F(IR_BuilderImplTest, EmitLiteral_F16) {
+    Enable(builtin::Extension::kF16);
+    auto* expr = Expr(1.2_h);
+    GlobalVar("a", ty.f16(), builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<f16>>());
+    EXPECT_EQ(1.2_h, val->As<constant::Scalar<f16>>()->ValueAs<f32>());
+}
+
+TEST_F(IR_BuilderImplTest, EmitLiteral_I32) {
+    auto* expr = Expr(-2_i);
+    GlobalVar("a", ty.i32(), builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(-2_i, val->As<constant::Scalar<i32>>()->ValueAs<f32>());
+}
+
+TEST_F(IR_BuilderImplTest, EmitLiteral_U32) {
+    auto* expr = Expr(2_u);
+    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, val->As<constant::Scalar<u32>>()->ValueAs<f32>());
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_materialize_test.cc b/src/tint/ir/builder_impl_materialize_test.cc
new file mode 100644
index 0000000..4f917f7
--- /dev/null
+++ b/src/tint/ir/builder_impl_materialize_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, EmitExpression_MaterializedCall) {
+    auto* expr = Return(Call("trunc", 2.5_f));
+
+    Func("test_function", {}, ty.f32(), expr, utils::Empty);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function():f32
+  %fn1 = block
+  ret 2.0f
+func_end
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_store_test.cc b/src/tint/ir/builder_impl_store_test.cc
new file mode 100644
index 0000000..f7cc6ab
--- /dev/null
+++ b/src/tint/ir/builder_impl_store_test.cc
@@ -0,0 +1,54 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, EmitStatement_Assign) {
+    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate);
+
+    auto* expr = Assign("a", 4_u);
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  store %1:ref<private, u32, read_write>, 4u
+  ret
+func_end
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 1e6a26d..4a6e713 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -42,7 +42,7 @@
     EXPECT_EQ(1u, f->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, f->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func f
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = func f():void
   %fn1 = block
   ret
 func_end
@@ -88,7 +88,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -136,7 +137,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -183,7 +185,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -230,7 +233,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -273,7 +277,8 @@
     ASSERT_NE(loop_flow->continuing.target, nullptr);
     ASSERT_NE(loop_flow->merge.target, nullptr);
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -330,7 +335,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -388,7 +394,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -463,7 +470,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -538,7 +546,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -595,7 +604,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -657,7 +667,8 @@
     // This is 1 because only the loop branch happens. The subsequent if return is dead code.
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -711,7 +722,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -857,7 +869,8 @@
     EXPECT_EQ(1u, func->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1001,7 +1014,8 @@
     EXPECT_EQ(1u, if_flow->false_.target->inbound_branches.Length());
     EXPECT_EQ(1u, if_flow->merge.target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1071,7 +1085,8 @@
     EXPECT_EQ(1u, if_flow->false_.target->inbound_branches.Length());
     EXPECT_EQ(1u, if_flow->merge.target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1178,7 +1193,8 @@
     EXPECT_EQ(1u, flow->merge.target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1237,7 +1253,8 @@
     EXPECT_EQ(3u, flow->merge.target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1301,7 +1318,8 @@
     EXPECT_EQ(1u, flow->merge.target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1345,7 +1363,8 @@
     EXPECT_EQ(1u, flow->merge.target->inbound_branches.Length());
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1398,7 +1417,8 @@
     // This is 1 because the if is dead-code eliminated and the return doesn't happen.
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1457,7 +1477,8 @@
     EXPECT_EQ(0u, flow->merge.target->inbound_branches.Length());
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
   %fn1 = block
   branch %fn2
 
@@ -1473,669 +1494,5 @@
 )");
 }
 
-TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_True) {
-    auto* expr = Expr(true);
-    GlobalVar("a", ty.bool_(), builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    auto r = b.EmitLiteral(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>()->value;
-    EXPECT_TRUE(val->Is<constant::Scalar<bool>>());
-    EXPECT_TRUE(val->As<constant::Scalar<bool>>()->ValueAs<bool>());
-}
-
-TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_False) {
-    auto* expr = Expr(false);
-    GlobalVar("a", ty.bool_(), builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    auto r = b.EmitLiteral(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>()->value;
-    EXPECT_TRUE(val->Is<constant::Scalar<bool>>());
-    EXPECT_FALSE(val->As<constant::Scalar<bool>>()->ValueAs<bool>());
-}
-
-TEST_F(IR_BuilderImplTest, EmitLiteral_F32) {
-    auto* expr = Expr(1.2_f);
-    GlobalVar("a", ty.f32(), builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    auto r = b.EmitLiteral(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>()->value;
-    EXPECT_TRUE(val->Is<constant::Scalar<f32>>());
-    EXPECT_EQ(1.2_f, val->As<constant::Scalar<f32>>()->ValueAs<f32>());
-}
-
-TEST_F(IR_BuilderImplTest, EmitLiteral_F16) {
-    Enable(builtin::Extension::kF16);
-    auto* expr = Expr(1.2_h);
-    GlobalVar("a", ty.f16(), builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    auto r = b.EmitLiteral(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>()->value;
-    EXPECT_TRUE(val->Is<constant::Scalar<f16>>());
-    EXPECT_EQ(1.2_h, val->As<constant::Scalar<f16>>()->ValueAs<f32>());
-}
-
-TEST_F(IR_BuilderImplTest, EmitLiteral_I32) {
-    auto* expr = Expr(-2_i);
-    GlobalVar("a", ty.i32(), builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    auto r = b.EmitLiteral(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>()->value;
-    EXPECT_TRUE(val->Is<constant::Scalar<i32>>());
-    EXPECT_EQ(-2_i, val->As<constant::Scalar<i32>>()->ValueAs<f32>());
-}
-
-TEST_F(IR_BuilderImplTest, EmitLiteral_U32) {
-    auto* expr = Expr(2_u);
-    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    auto r = b.EmitLiteral(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>()->value;
-    EXPECT_TRUE(val->Is<constant::Scalar<u32>>());
-    EXPECT_EQ(2_u, val->As<constant::Scalar<u32>>()->ValueAs<f32>());
-}
-
-TEST_F(IR_BuilderImplTest, Emit_GlobalVar_NoInit) {
-    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate);
-
-    auto r = Build();
-    ASSERT_TRUE(r) << Error();
-    auto m = r.Move();
-
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
-%1(ref<private, u32, read_write>) = var private read_write
-ret
-
-)");
-}
-
-TEST_F(IR_BuilderImplTest, Emit_GlobalVar_Init) {
-    auto* expr = Expr(2_u);
-    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate, expr);
-
-    auto r = Build();
-    ASSERT_TRUE(r) << Error();
-    auto m = r.Move();
-
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
-%1(ref<private, u32, read_write>) = var private read_write
-store %1(ref<private, u32, read_write>), 2u
-ret
-
-)");
-}
-
-TEST_F(IR_BuilderImplTest, Emit_Var_NoInit) {
-    auto* a = Var("a", ty.u32(), builtin::AddressSpace::kFunction);
-    WrapInFunction(a);
-
-    auto r = Build();
-    ASSERT_TRUE(r) << Error();
-    auto m = r.Move();
-
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
-  %fn1 = block
-  %1(ref<function, u32, read_write>) = var function read_write
-  ret
-func_end
-
-)");
-}
-
-TEST_F(IR_BuilderImplTest, Emit_Var_Init) {
-    auto* expr = Expr(2_u);
-    auto* a = Var("a", ty.u32(), builtin::AddressSpace::kFunction, expr);
-    WrapInFunction(a);
-
-    auto r = Build();
-    ASSERT_TRUE(r) << Error();
-    auto m = r.Move();
-
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
-  %fn1 = block
-  %1(ref<function, u32, read_write>) = var function read_write
-  store %1(ref<function, u32, read_write>), 2u
-  ret
-func_end
-
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Add) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Add(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = add %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Subtract) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Sub(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = sub %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Multiply) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Mul(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = mul %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Div) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Div(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = div %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Modulo) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Mod(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = mod %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_And) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = And(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = bit_and %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Or) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Or(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = bit_or %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Xor) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Xor(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = bit_xor %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalAnd) {
-    Func("my_func", utils::Empty, ty.bool_(), utils::Vector{Return(true)});
-    auto* expr = LogicalAnd(Call("my_func"), false);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(bool) = call my_func
-%2(bool) = log_and %1(bool), false
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalOr) {
-    Func("my_func", utils::Empty, ty.bool_(), utils::Vector{Return(true)});
-    auto* expr = LogicalOr(Call("my_func"), true);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(bool) = call my_func
-%2(bool) = log_or %1(bool), true
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Equal) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Equal(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(bool) = eq %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_NotEqual) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = NotEqual(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(bool) = neq %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThan) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = LessThan(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(bool) = lt %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThan) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = GreaterThan(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(bool) = gt %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThanEqual) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = LessThanEqual(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(bool) = lte %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThanEqual) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = GreaterThanEqual(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(bool) = gte %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftLeft) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Shl(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = shiftl %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftRight) {
-    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(0_u)});
-    auto* expr = Shr(Call("my_func"), 4_u);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(u32) = call my_func
-%2(u32) = shiftr %1(u32), 4u
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Compound) {
-    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(0_f)});
-
-    auto* expr = LogicalAnd(LessThan(Call("my_func"), 2_f),
-                            GreaterThan(2.5_f, Div(Call("my_func"), Mul(2.3_f, Call("my_func")))));
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(f32) = call my_func
-%2(bool) = lt %1(f32), 2.0f
-%3(f32) = call my_func
-%4(f32) = call my_func
-%5(f32) = mul 2.29999995231628417969f, %4(f32)
-%6(f32) = div %3(f32), %5(f32)
-%7(bool) = gt 2.5f, %6(f32)
-%8(bool) = log_and %2(bool), %7(bool)
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Compound_WithConstEval) {
-    Func("my_func", utils::Vector{Param("p", ty.bool_())}, ty.bool_(), utils::Vector{Return(true)});
-    auto* expr = Call("my_func", LogicalAnd(LessThan(2.4_f, 2_f),
-                                            GreaterThan(2.5_f, Div(10_f, Mul(2.3_f, 9.4_f)))));
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(bool) = call my_func, false
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_Bitcast) {
-    Func("my_func", utils::Empty, ty.f32(), utils::Vector{Return(0_f)});
-
-    auto* expr = Bitcast<f32>(Call("my_func"));
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(f32) = call my_func
-%2(f32) = bitcast %1(f32)
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitStatement_Discard) {
-    auto* expr = Discard();
-    Func("test_function", {}, ty.void_(), expr,
-         utils::Vector{
-             create<ast::StageAttribute>(ast::PipelineStage::kFragment),
-         });
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    b.EmitStatement(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(discard
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitStatement_UserFunction) {
-    Func("my_func", utils::Vector{Param("p", ty.f32())}, ty.void_(), utils::Empty);
-
-    auto* stmt = CallStmt(Call("my_func", Mul(2_a, 3_a)));
-    WrapInFunction(stmt);
-
-    auto& b = CreateBuilder();
-
-    InjectFlowBlock();
-    b.EmitStatement(stmt);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(void) = call my_func, 6.0f
-)");
-}
-
-// TODO(dsinclair): This needs assignment in order to output correctly. The empty constructor ends
-// up materializing, so there is no expression to emit until there is a usage. When assigment is
-// implemented this can be enabled (and the output updated).
-TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_ConstructEmpty) {
-    auto* expr = vec3(ty.f32());
-    GlobalVar("i", builtin::AddressSpace::kPrivate, expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%1(vec3<f32>) = construct
-)");
-}
-
-// Requires identifier expressions
-TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_Construct) {
-    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
-    auto* expr = vec3(ty.f32(), 2_f, 3_f, i);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%2(vec3<f32>) = construct 2.0f, 3.0f, %1(void)
-)");
-}
-
-// Requires identifier expressions
-TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_Convert) {
-    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_i));
-    auto* expr = Call(ty.f32(), i);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%2(f32) = convert i32, %1(void)
-)");
-}
-
-TEST_F(IR_BuilderImplTest, EmitExpression_MaterializedCall) {
-    auto* expr = Return(Call("trunc", 2.5_f));
-
-    Func("test_function", {}, ty.f32(), expr, utils::Empty);
-
-    auto r = Build();
-    ASSERT_TRUE(r) << Error();
-    auto m = r.Move();
-
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function
-  %fn1 = block
-  ret 2.0f
-func_end
-
-)");
-}
-
-// Requires identifier expressions
-TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_Builtin) {
-    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
-    auto* expr = Call("asin", i);
-    WrapInFunction(expr);
-
-    auto& b = CreateBuilder();
-    InjectFlowBlock();
-    auto r = b.EmitExpression(expr);
-    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
-    ASSERT_TRUE(r);
-
-    Disassembler d(b.builder.ir);
-    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
-    EXPECT_EQ(d.AsString(), R"(%2(f32) = asin %1(void)
-)");
-}
-
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_unary_test.cc b/src/tint/ir/builder_impl_unary_test.cc
new file mode 100644
index 0000000..9d35c02
--- /dev/null
+++ b/src/tint/ir/builder_impl_unary_test.cc
@@ -0,0 +1,135 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Unary_Not) {
+    Func("my_func", utils::Empty, ty.bool_(), utils::Vector{Return(false)});
+    auto* expr = Not(Call("my_func"));
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:bool = call my_func
+%2:bool = eq %1:bool, false
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Unary_Complement) {
+    Func("my_func", utils::Empty, ty.u32(), utils::Vector{Return(1_u)});
+    auto* expr = Complement(Call("my_func"));
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:u32 = call my_func
+%2:u32 = complement %1:u32
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Unary_Negation) {
+    Func("my_func", utils::Empty, ty.i32(), utils::Vector{Return(1_i)});
+    auto* expr = Negation(Call("my_func"));
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_THAT(b.Diagnostics(), testing::IsEmpty());
+    ASSERT_TRUE(r);
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1:i32 = call my_func
+%2:i32 = negation %1:i32
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Unary_AddressOf) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.i32());
+
+    auto* expr = Decl(Let("v2", AddressOf("v1")));
+    WrapInFunction(expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, i32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ptr<private, i32, read_write> = addr_of %1:ref<private, i32, read_write>
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Unary_Indirection) {
+    GlobalVar("v1", builtin::AddressSpace::kPrivate, ty.i32());
+    utils::Vector stmts = {
+        Decl(Let("v3", AddressOf("v1"))),
+        Decl(Let("v2", Deref("v3"))),
+    };
+    WrapInFunction(stmts);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, i32, read_write> = var private read_write
+
+
+
+%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  %2:ptr<private, i32, read_write> = addr_of %1:ref<private, i32, read_write>
+  %3:i32 = indirection %2:ptr<private, i32, read_write>
+  ret
+func_end
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_var_test.cc b/src/tint/ir/builder_impl_var_test.cc
new file mode 100644
index 0000000..485684a
--- /dev/null
+++ b/src/tint/ir/builder_impl_var_test.cc
@@ -0,0 +1,99 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/test_helper.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/ast/case_selector.h"
+#include "src/tint/ast/int_literal_expression.h"
+#include "src/tint/constant/scalar.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_BuilderImplTest = TestHelper;
+
+TEST_F(IR_BuilderImplTest, Emit_GlobalVar_NoInit) {
+    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+
+
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, Emit_GlobalVar_Init) {
+    auto* expr = Expr(2_u);
+    GlobalVar("a", ty.u32(), builtin::AddressSpace::kPrivate, expr);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+%1:ref<private, u32, read_write> = var private read_write
+store %1:ref<private, u32, read_write>, 2u
+
+
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, Emit_Var_NoInit) {
+    auto* a = Var("a", ty.u32(), builtin::AddressSpace::kFunction);
+    WrapInFunction(a);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn1 = block
+  %1:ref<function, u32, read_write> = var function read_write
+  ret
+func_end
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, Emit_Var_Init) {
+    auto* expr = Expr(2_u);
+    auto* a = Var("a", ty.u32(), builtin::AddressSpace::kFunction, expr);
+    WrapInFunction(a);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m),
+              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn1 = block
+  %1:ref<function, u32, read_write> = var function read_write
+  store %1:ref<function, u32, read_write>, 2u
+  ret
+func_end
+
+)");
+}
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builtin.cc b/src/tint/ir/builtin.cc
index 54cf882..5e53eec 100644
--- a/src/tint/ir/builtin.cc
+++ b/src/tint/ir/builtin.cc
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "src/tint/ir/builtin.h"
+
+#include <utility>
+
 #include "src/tint/debug.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Builtin);
@@ -20,19 +23,13 @@
 // \cond DO_NOT_DOCUMENT
 namespace tint::ir {
 
-Builtin::Builtin(uint32_t id,
-                 const type::Type* type,
+Builtin::Builtin(uint32_t identifier,
+                 const type::Type* ty,
                  builtin::Function func,
-                 utils::VectorRef<Value*> args)
-    : Base(id, type, args), func_(func) {}
+                 utils::VectorRef<Value*> arguments)
+    : Base(identifier, ty, std::move(arguments)), func_(func) {}
 
 Builtin::~Builtin() = default;
 
-utils::StringStream& Builtin::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = " << builtin::str(func_) << " ";
-    EmitArgs(out);
-    return out;
-}
-
 }  // namespace tint::ir
 // \endcond
diff --git a/src/tint/ir/builtin.h b/src/tint/ir/builtin.h
index 9f6f666..2106a68 100644
--- a/src/tint/ir/builtin.h
+++ b/src/tint/ir/builtin.h
@@ -18,7 +18,6 @@
 #include "src/tint/builtin/function.h"
 #include "src/tint/ir/call.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -44,11 +43,6 @@
     /// @returns the builtin function
     builtin::Function Func() const { return func_; }
 
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
   private:
     const builtin::Function func_;
 };
diff --git a/src/tint/ir/call.cc b/src/tint/ir/call.cc
index 322f83d..9f5a8a5 100644
--- a/src/tint/ir/call.cc
+++ b/src/tint/ir/call.cc
@@ -14,14 +14,14 @@
 
 #include "src/tint/ir/call.h"
 
+#include <utility>
+
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Call);
 
 namespace tint::ir {
 
-Call::Call() : Base() {}
-
-Call::Call(uint32_t id, const type::Type* type, utils::VectorRef<Value*> args)
-    : Base(id, type), args_(args) {
+Call::Call(uint32_t identifier, const type::Type* ty, utils::VectorRef<Value*> arguments)
+    : Base(identifier, ty), args(std::move(arguments)) {
     for (auto* arg : args) {
         arg->AddUsage(this);
     }
@@ -29,15 +29,4 @@
 
 Call::~Call() = default;
 
-void Call::EmitArgs(utils::StringStream& out) const {
-    bool first = true;
-    for (const auto* arg : args_) {
-        if (!first) {
-            out << ", ";
-        }
-        first = false;
-        arg->ToValue(out);
-    }
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/call.h b/src/tint/ir/call.h
index a49e9fa..b0f2e7c 100644
--- a/src/tint/ir/call.h
+++ b/src/tint/ir/call.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -31,24 +30,17 @@
     Call& operator=(const Call& inst) = delete;
     Call& operator=(Call&& inst) = delete;
 
-    /// @returns the constructor arguments
-    utils::VectorRef<Value*> Args() const { return args_; }
-
-    /// Writes the call arguments to the given stream.
-    /// @param out the output stream
-    void EmitArgs(utils::StringStream& out) const;
+    /// The constructor arguments
+    utils::Vector<Value*, 1> args;
 
   protected:
     /// Constructor
-    Call();
+    Call() = delete;
     /// Constructor
     /// @param id the instruction id
     /// @param type the result type
     /// @param args the constructor arguments
     Call(uint32_t id, const type::Type* type, utils::VectorRef<Value*> args);
-
-  private:
-    utils::Vector<Value*, 1> args_;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/constant.cc b/src/tint/ir/constant.cc
index 0666e91..8b5260c 100644
--- a/src/tint/ir/constant.cc
+++ b/src/tint/ir/constant.cc
@@ -14,13 +14,6 @@
 
 #include "src/tint/ir/constant.h"
 
-#include <string>
-
-#include "src/tint/constant/composite.h"
-#include "src/tint/constant/scalar.h"
-#include "src/tint/constant/splat.h"
-#include "src/tint/switch.h"
-
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Constant);
 
 namespace tint::ir {
@@ -29,43 +22,4 @@
 
 Constant::~Constant() = default;
 
-utils::StringStream& Constant::ToValue(utils::StringStream& out) const {
-    std::function<void(const constant::Value*)> emit = [&](const constant::Value* c) {
-        Switch(
-            c,
-            [&](const constant::Scalar<AFloat>* scalar) { out << scalar->ValueAs<AFloat>().value; },
-            [&](const constant::Scalar<AInt>* scalar) { out << scalar->ValueAs<AInt>().value; },
-            [&](const constant::Scalar<i32>* scalar) {
-                out << scalar->ValueAs<i32>().value << "i";
-            },
-            [&](const constant::Scalar<u32>* scalar) {
-                out << scalar->ValueAs<u32>().value << "u";
-            },
-            [&](const constant::Scalar<f32>* scalar) {
-                out << scalar->ValueAs<f32>().value << "f";
-            },
-            [&](const constant::Scalar<f16>* scalar) {
-                out << scalar->ValueAs<f16>().value << "h";
-            },
-            [&](const constant::Scalar<bool>* scalar) {
-                out << (scalar->ValueAs<bool>() ? "true" : "false");
-            },
-            [&](const constant::Splat* splat) {
-                out << splat->Type()->FriendlyName() << " ";
-                emit(splat->Index(0));
-            },
-            [&](const constant::Composite* composite) {
-                out << composite->Type()->FriendlyName() << " ";
-                for (const auto* elem : composite->elements) {
-                    if (elem != composite->elements[0]) {
-                        out << ", ";
-                    }
-                    emit(elem);
-                }
-            });
-    };
-    emit(value);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/constant.h b/src/tint/ir/constant.h
index 2413721..68e0dc7 100644
--- a/src/tint/ir/constant.h
+++ b/src/tint/ir/constant.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/constant/value.h"
 #include "src/tint/ir/value.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -32,11 +31,6 @@
     /// @returns the type of the constant
     const type::Type* Type() const override { return value->Type(); }
 
-    /// Write the constant to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToValue(utils::StringStream& out) const override;
-
     /// The constants value
     const constant::Value* const value;
 };
diff --git a/src/tint/ir/constant_test.cc b/src/tint/ir/constant_test.cc
index 43d92b4..c550763 100644
--- a/src/tint/ir/constant_test.cc
+++ b/src/tint/ir/constant_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ir/test_helper.h"
 #include "src/tint/ir/value.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 namespace {
@@ -31,9 +30,6 @@
     auto* c = b.builder.Constant(1.2_f);
     EXPECT_EQ(1.2_f, c->value->As<constant::Scalar<f32>>()->ValueAs<f32>());
 
-    c->ToValue(str);
-    EXPECT_EQ("1.20000004768371582031f", str.str());
-
     EXPECT_TRUE(c->value->Is<constant::Scalar<f32>>());
     EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
     EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
@@ -49,9 +45,6 @@
     auto* c = b.builder.Constant(1.1_h);
     EXPECT_EQ(1.1_h, c->value->As<constant::Scalar<f16>>()->ValueAs<f16>());
 
-    c->ToValue(str);
-    EXPECT_EQ("1.099609375h", str.str());
-
     EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
     EXPECT_TRUE(c->value->Is<constant::Scalar<f16>>());
     EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
@@ -67,9 +60,6 @@
     auto* c = b.builder.Constant(1_i);
     EXPECT_EQ(1_i, c->value->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
-    c->ToValue(str);
-    EXPECT_EQ("1i", str.str());
-
     EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
     EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
     EXPECT_TRUE(c->value->Is<constant::Scalar<i32>>());
@@ -85,9 +75,6 @@
     auto* c = b.builder.Constant(2_u);
     EXPECT_EQ(2_u, c->value->As<constant::Scalar<u32>>()->ValueAs<u32>());
 
-    c->ToValue(str);
-    EXPECT_EQ("2u", str.str());
-
     EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
     EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
     EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
@@ -103,9 +90,6 @@
 
         auto* c = b.builder.Constant(false);
         EXPECT_FALSE(c->value->As<constant::Scalar<bool>>()->ValueAs<bool>());
-
-        c->ToValue(str);
-        EXPECT_EQ("false", str.str());
     }
 
     {
@@ -113,9 +97,6 @@
         auto c = b.builder.Constant(true);
         EXPECT_TRUE(c->value->As<constant::Scalar<bool>>()->ValueAs<bool>());
 
-        c->ToValue(str);
-        EXPECT_EQ("true", str.str());
-
         EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
         EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
         EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
diff --git a/src/tint/ir/construct.cc b/src/tint/ir/construct.cc
index 1507e8f..44c8862 100644
--- a/src/tint/ir/construct.cc
+++ b/src/tint/ir/construct.cc
@@ -13,21 +13,18 @@
 // limitations under the License.
 
 #include "src/tint/ir/construct.h"
+
+#include <utility>
+
 #include "src/tint/debug.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Construct);
 
 namespace tint::ir {
 
-Construct::Construct(uint32_t id, const type::Type* type, utils::VectorRef<Value*> args)
-    : Base(id, type, args) {}
+Construct::Construct(uint32_t identifier, const type::Type* ty, utils::VectorRef<Value*> arguments)
+    : Base(identifier, ty, std::move(arguments)) {}
 
 Construct::~Construct() = default;
 
-utils::StringStream& Construct::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = construct ";
-    EmitArgs(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/construct.h b/src/tint/ir/construct.h
index 1cc0102..b1711fb 100644
--- a/src/tint/ir/construct.h
+++ b/src/tint/ir/construct.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/ir/call.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -35,11 +34,6 @@
 
     Construct& operator=(const Construct& inst) = delete;
     Construct& operator=(Construct&& inst) = delete;
-
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/convert.cc b/src/tint/ir/convert.cc
index 7ffd6f5..969390c 100644
--- a/src/tint/ir/convert.cc
+++ b/src/tint/ir/convert.cc
@@ -19,18 +19,12 @@
 
 namespace tint::ir {
 
-Convert::Convert(uint32_t id,
+Convert::Convert(uint32_t identifier,
                  const type::Type* to_type,
                  const type::Type* from_type,
-                 utils::VectorRef<Value*> args)
-    : Base(id, to_type, args), from_type_(from_type) {}
+                 utils::VectorRef<Value*> arguments)
+    : Base(identifier, to_type, arguments), from_type_(from_type) {}
 
 Convert::~Convert() = default;
 
-utils::StringStream& Convert::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = convert " << from_type_->FriendlyName() << ", ";
-    EmitArgs(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/convert.h b/src/tint/ir/convert.h
index c164a45..953a5fa 100644
--- a/src/tint/ir/convert.h
+++ b/src/tint/ir/convert.h
@@ -18,7 +18,6 @@
 #include "src/tint/ir/call.h"
 #include "src/tint/type/type.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -46,11 +45,6 @@
     /// @returns the to type
     const type::Type* ToType() const { return Type(); }
 
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
   private:
     const type::Type* from_type_ = nullptr;
 };
diff --git a/src/tint/ir/converter.h b/src/tint/ir/converter.h
deleted file mode 100644
index 8defd83..0000000
--- a/src/tint/ir/converter.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2023 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_IR_CONVERTER_H_
-#define SRC_TINT_IR_CONVERTER_H_
-
-#include <string>
-
-#include "src/tint/ir/module.h"
-#include "src/tint/utils/result.h"
-
-// Forward Declarations
-namespace tint {
-class Program;
-}  // namespace tint
-
-namespace tint::ir {
-
-/// Class for converting into and out of IR.
-class Converter {
-  public:
-    /// The result type for the FromProgram method.
-    using Result = utils::Result<Module, std::string>;
-
-    /// Builds an ir::Module from the given Program
-    /// @param program the Program to use.
-    /// @returns the `utiils::Result` of generating the IR. The result will contain the `ir::Module`
-    /// on success, otherwise the `std::string` error.
-    ///
-    /// @note this assumes the program |IsValid|, and has had const-eval done so
-    /// any abstract values have been calculated and converted into the relevant
-    /// concrete types.
-    static Result FromProgram(const Program* program);
-
-    /// Converts the module back to a Program
-    /// @returns the resulting program, or nullptr on error
-    ///  (Note, this will probably turn into a utils::Result, just stubbing for now)
-    static const Program* ToProgram();
-};
-
-}  // namespace tint::ir
-
-#endif  // SRC_TINT_IR_CONVERTER_H_
diff --git a/src/tint/ir/debug.cc b/src/tint/ir/debug.cc
index f69805e..655c455 100644
--- a/src/tint/ir/debug.cc
+++ b/src/tint/ir/debug.cc
@@ -18,10 +18,10 @@
 #include <unordered_set>
 
 #include "src/tint/ir/block.h"
+#include "src/tint/ir/function_terminator.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/switch.h"
-#include "src/tint/ir/terminator.h"
 #include "src/tint/switch.h"
 #include "src/tint/utils/string_stream.h"
 
@@ -136,7 +136,7 @@
                 Graph(l->continuing.target);
                 Graph(l->merge.target);
             },
-            [&](const ir::Terminator*) {
+            [&](const ir::FunctionTerminator*) {
                 // Already done
             });
     };
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 2b8de0b..58fb1a0 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -14,12 +14,27 @@
 
 #include "src/tint/ir/disassembler.h"
 
+#include "src//tint/ir/unary.h"
+#include "src/tint/constant/composite.h"
+#include "src/tint/constant/scalar.h"
+#include "src/tint/constant/splat.h"
+#include "src/tint/ir/binary.h"
+#include "src/tint/ir/bitcast.h"
 #include "src/tint/ir/block.h"
+#include "src/tint/ir/builtin.h"
+#include "src/tint/ir/construct.h"
+#include "src/tint/ir/convert.h"
+#include "src/tint/ir/discard.h"
+#include "src/tint/ir/function_terminator.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/loop.h"
+#include "src/tint/ir/root_terminator.h"
+#include "src/tint/ir/store.h"
 #include "src/tint/ir/switch.h"
-#include "src/tint/ir/terminator.h"
+#include "src/tint/ir/user_call.h"
+#include "src/tint/ir/var.h"
 #include "src/tint/switch.h"
+#include "src/tint/type/type.h"
 #include "src/tint/utils/scoped_assignment.h"
 
 namespace tint::ir {
@@ -65,7 +80,8 @@
 void Disassembler::EmitBlockInstructions(const Block* b) {
     for (const auto* inst : b->instructions) {
         Indent();
-        inst->ToInstruction(out_) << std::endl;
+        EmitInstruction(inst);
+        out_ << std::endl;
     }
 }
 
@@ -92,7 +108,32 @@
         [&](const ir::Function* f) {
             TINT_SCOPED_ASSIGNMENT(in_function_, true);
 
-            Indent() << "%fn" << GetIdForNode(f) << " = func " << f->name.Name() << std::endl;
+            Indent() << "%fn" << GetIdForNode(f) << " = func " << f->name.Name()
+                     << "():" << f->return_type->FriendlyName();
+
+            if (f->pipeline_stage != Function::PipelineStage::kUndefined) {
+                out_ << " [@" << f->pipeline_stage;
+
+                if (f->workgroup_size) {
+                    auto arr = f->workgroup_size.value();
+                    out_ << " @workgroup_size(" << arr[0] << ", " << arr[1] << ", " << arr[2]
+                         << ")";
+                }
+
+                if (!f->return_attributes.IsEmpty()) {
+                    out_ << " ra:";
+
+                    for (auto attr : f->return_attributes) {
+                        out_ << " @" << attr;
+                        if (attr == Function::ReturnAttribute::kLocation) {
+                            out_ << "(" << f->return_location.value() << ")";
+                        }
+                    }
+                }
+
+                out_ << "]";
+            }
+            out_ << std::endl;
 
             {
                 ScopedIndent func_indent(&indent_size_);
@@ -110,8 +151,10 @@
             Indent() << "%fn" << GetIdForNode(b) << " = block" << std::endl;
             EmitBlockInstructions(b);
 
-            if (b->branch.target->Is<Terminator>()) {
+            if (b->branch.target->Is<FunctionTerminator>()) {
                 Indent() << "ret";
+            } else if (b->branch.target->Is<RootTerminator>()) {
+                // Nothing to do
             } else {
                 Indent() << "branch "
                          << "%fn" << GetIdForNode(b->branch.target);
@@ -122,12 +165,12 @@
                     if (v != b->branch.args.Front()) {
                         out_ << ", ";
                     }
-                    v->ToValue(out_);
+                    EmitValue(v);
                 }
             }
             out_ << std::endl;
 
-            if (!b->branch.target->Is<Terminator>()) {
+            if (!b->branch.target->Is<FunctionTerminator>()) {
                 out_ << std::endl;
             }
 
@@ -135,7 +178,7 @@
         },
         [&](const ir::Switch* s) {
             Indent() << "%fn" << GetIdForNode(s) << " = switch ";
-            s->condition->ToValue(out_);
+            EmitValue(s->condition);
             out_ << " [";
             for (const auto& c : s->cases) {
                 if (&c != &s->cases.Front()) {
@@ -150,7 +193,7 @@
                     if (selector.IsDefault()) {
                         out_ << "default";
                     } else {
-                        selector.val->ToValue(out_);
+                        EmitValue(selector.val);
                     }
                 }
                 out_ << ", %fn" << GetIdForNode(c.start.target) << ")";
@@ -173,7 +216,7 @@
                         if (selector.IsDefault()) {
                             out_ << "default";
                         } else {
-                            selector.val->ToValue(out_);
+                            EmitValue(selector.val);
                         }
                     }
                     out_ << std::endl;
@@ -188,7 +231,7 @@
         },
         [&](const ir::If* i) {
             Indent() << "%fn" << GetIdForNode(i) << " = if ";
-            i->condition->ToValue(out_);
+            EmitValue(i->condition);
             out_ << " [t: %fn" << GetIdForNode(i->true_.target) << ", f: %fn"
                  << GetIdForNode(i->false_.target);
             if (i->merge.target->IsConnected()) {
@@ -203,8 +246,10 @@
                 Indent() << "# true branch" << std::endl;
                 Walk(i->true_.target);
 
-                Indent() << "# false branch" << std::endl;
-                Walk(i->false_.target);
+                if (!i->false_.target->IsDead()) {
+                    Indent() << "# false branch" << std::endl;
+                    Walk(i->false_.target);
+                }
             }
 
             if (i->merge.target->IsConnected()) {
@@ -244,10 +289,12 @@
                 Walk(l->merge.target);
             }
         },
-        [&](const ir::Terminator*) {
-            if (in_function_) {
-                Indent() << "func_end" << std::endl;
-            }
+        [&](const ir::FunctionTerminator*) {
+            TINT_ASSERT(IR, in_function_);
+            Indent() << "func_end" << std::endl << std::endl;
+        },
+        [&](const ir::RootTerminator*) {
+            TINT_ASSERT(IR, !in_function_);
             out_ << std::endl;
         });
 }
@@ -263,4 +310,196 @@
     return out_.str();
 }
 
+void Disassembler::EmitValue(const Value* val) {
+    tint::Switch(
+        val,
+        [&](const ir::Constant* constant) {
+            std::function<void(const constant::Value*)> emit = [&](const constant::Value* c) {
+                tint::Switch(
+                    c,
+                    [&](const constant::Scalar<AFloat>* scalar) {
+                        out_ << scalar->ValueAs<AFloat>().value;
+                    },
+                    [&](const constant::Scalar<AInt>* scalar) {
+                        out_ << scalar->ValueAs<AInt>().value;
+                    },
+                    [&](const constant::Scalar<i32>* scalar) {
+                        out_ << scalar->ValueAs<i32>().value << "i";
+                    },
+                    [&](const constant::Scalar<u32>* scalar) {
+                        out_ << scalar->ValueAs<u32>().value << "u";
+                    },
+                    [&](const constant::Scalar<f32>* scalar) {
+                        out_ << scalar->ValueAs<f32>().value << "f";
+                    },
+                    [&](const constant::Scalar<f16>* scalar) {
+                        out_ << scalar->ValueAs<f16>().value << "h";
+                    },
+                    [&](const constant::Scalar<bool>* scalar) {
+                        out_ << (scalar->ValueAs<bool>() ? "true" : "false");
+                    },
+                    [&](const constant::Splat* splat) {
+                        out_ << splat->Type()->FriendlyName() << " ";
+                        emit(splat->Index(0));
+                    },
+                    [&](const constant::Composite* composite) {
+                        out_ << composite->Type()->FriendlyName() << " ";
+                        for (const auto* elem : composite->elements) {
+                            if (elem != composite->elements[0]) {
+                                out_ << ", ";
+                            }
+                            emit(elem);
+                        }
+                    });
+            };
+            emit(constant->value);
+        },
+        [&](const ir::Instruction* i) {
+            if (i->id == ir::Instruction::kNoID) {
+                out_ << "<no-id>";
+            } else {
+                out_ << "%" << i->id;
+            }
+            if (i->Type() != nullptr) {
+                out_ << ":" << i->Type()->FriendlyName();
+            }
+        });
+}
+
+void Disassembler::EmitInstruction(const Instruction* inst) {
+    tint::Switch(
+        inst,  //
+        [&](const ir::Binary* b) { EmitBinary(b); }, [&](const ir::Unary* u) { EmitUnary(u); },
+        [&](const ir::Bitcast* b) {
+            EmitValue(b);
+            out_ << " = bitcast ";
+            EmitArgs(b);
+        },
+        [&](const ir::Discard*) { out_ << "discard"; },
+        [&](const ir::Builtin* b) {
+            EmitValue(b);
+            out_ << " = " << builtin::str(b->Func()) << " ";
+            EmitArgs(b);
+        },
+        [&](const ir::Construct* c) {
+            EmitValue(c);
+            out_ << " = construct ";
+            EmitArgs(c);
+        },
+        [&](const ir::Convert* c) {
+            EmitValue(c);
+            out_ << " = convert " << c->FromType()->FriendlyName() << ", ";
+            EmitArgs(c);
+        },
+        [&](const ir::Store* s) {
+            out_ << "store ";
+            EmitValue(s->to);
+            out_ << ", ";
+            EmitValue(s->from);
+        },
+        [&](const ir::UserCall* uc) {
+            EmitValue(uc);
+            out_ << " = call " << uc->name.Name();
+            if (uc->args.Length() > 0) {
+                out_ << ", ";
+            }
+            EmitArgs(uc);
+        },
+        [&](const ir::Var* v) {
+            EmitValue(v);
+            out_ << " = var " << v->address_space << " " << v->access;
+        });
+}
+
+void Disassembler::EmitArgs(const Call* call) {
+    bool first = true;
+    for (const auto* arg : call->args) {
+        if (!first) {
+            out_ << ", ";
+        }
+        first = false;
+        EmitValue(arg);
+    }
+}
+
+void Disassembler::EmitBinary(const Binary* b) {
+    EmitValue(b);
+    out_ << " = ";
+    switch (b->GetKind()) {
+        case Binary::Kind::kAdd:
+            out_ << "add";
+            break;
+        case Binary::Kind::kSubtract:
+            out_ << "sub";
+            break;
+        case Binary::Kind::kMultiply:
+            out_ << "mul";
+            break;
+        case Binary::Kind::kDivide:
+            out_ << "div";
+            break;
+        case Binary::Kind::kModulo:
+            out_ << "mod";
+            break;
+        case Binary::Kind::kAnd:
+            out_ << "and";
+            break;
+        case Binary::Kind::kOr:
+            out_ << "or";
+            break;
+        case Binary::Kind::kXor:
+            out_ << "xor";
+            break;
+        case Binary::Kind::kEqual:
+            out_ << "eq";
+            break;
+        case Binary::Kind::kNotEqual:
+            out_ << "neq";
+            break;
+        case Binary::Kind::kLessThan:
+            out_ << "lt";
+            break;
+        case Binary::Kind::kGreaterThan:
+            out_ << "gt";
+            break;
+        case Binary::Kind::kLessThanEqual:
+            out_ << "lte";
+            break;
+        case Binary::Kind::kGreaterThanEqual:
+            out_ << "gte";
+            break;
+        case Binary::Kind::kShiftLeft:
+            out_ << "shiftl";
+            break;
+        case Binary::Kind::kShiftRight:
+            out_ << "shiftr";
+            break;
+    }
+    out_ << " ";
+    EmitValue(b->LHS());
+    out_ << ", ";
+    EmitValue(b->RHS());
+}
+
+void Disassembler::EmitUnary(const Unary* u) {
+    EmitValue(u);
+    out_ << " = ";
+    switch (u->GetKind()) {
+        case Unary::Kind::kAddressOf:
+            out_ << "addr_of";
+            break;
+        case Unary::Kind::kComplement:
+            out_ << "complement";
+            break;
+        case Unary::Kind::kIndirection:
+            out_ << "indirection";
+            break;
+        case Unary::Kind::kNegation:
+            out_ << "negation";
+            break;
+    }
+    out_ << " ";
+    EmitValue(u->Val());
+}
+
 }  // namespace tint::ir
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index 04b3997..d9182d3 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -19,8 +19,11 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include "src/tint/ir/binary.h"
+#include "src/tint/ir/call.h"
 #include "src/tint/ir/flow_node.h"
 #include "src/tint/ir/module.h"
+#include "src/tint/ir/unary.h"
 #include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
@@ -46,9 +49,15 @@
 
   private:
     utils::StringStream& Indent();
-    void Walk(const FlowNode* node);
     size_t GetIdForNode(const FlowNode* node);
 
+    void Walk(const FlowNode* node);
+    void EmitInstruction(const Instruction* inst);
+    void EmitValue(const Value* val);
+    void EmitArgs(const Call* call);
+    void EmitBinary(const Binary* b);
+    void EmitUnary(const Unary* b);
+
     const Module& mod_;
     utils::StringStream out_;
     std::unordered_set<const FlowNode*> visited_;
diff --git a/src/tint/ir/discard.cc b/src/tint/ir/discard.cc
index bc7bdc3..6c7ded7 100644
--- a/src/tint/ir/discard.cc
+++ b/src/tint/ir/discard.cc
@@ -19,13 +19,8 @@
 
 namespace tint::ir {
 
-Discard::Discard() : Base() {}
+Discard::Discard() : Base(kNoID, nullptr, utils::Empty) {}
 
 Discard::~Discard() = default;
 
-utils::StringStream& Discard::ToInstruction(utils::StringStream& out) const {
-    out << "discard";
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/discard.h b/src/tint/ir/discard.h
index 456c8d5..2789e57 100644
--- a/src/tint/ir/discard.h
+++ b/src/tint/ir/discard.h
@@ -18,7 +18,6 @@
 #include "src/tint/debug.h"
 #include "src/tint/ir/call.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -33,11 +32,6 @@
 
     Discard& operator=(const Discard& inst) = delete;
     Discard& operator=(Discard&& inst) = delete;
-
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/discard_test.cc b/src/tint/ir/discard_test.cc
index 32b5c1e..b1bf8c2 100644
--- a/src/tint/ir/discard_test.cc
+++ b/src/tint/ir/discard_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/test_helper.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 namespace {
@@ -26,10 +25,6 @@
 
     const auto* inst = b.builder.Discard();
     ASSERT_TRUE(inst->Is<ir::Discard>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "discard");
 }
 
 }  // namespace
diff --git a/src/tint/ir/converter.cc b/src/tint/ir/from_program.cc
similarity index 75%
rename from src/tint/ir/converter.cc
rename to src/tint/ir/from_program.cc
index e89be3a..d093103 100644
--- a/src/tint/ir/converter.cc
+++ b/src/tint/ir/from_program.cc
@@ -12,17 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ir/converter.h"
+#include "src/tint/ir/from_program.h"
 
 #include "src/tint/ir/builder_impl.h"
 #include "src/tint/program.h"
 
 namespace tint::ir {
 
-// static
-Converter::Result Converter::FromProgram(const Program* program) {
+utils::Result<Module, std::string> FromProgram(const Program* program) {
     if (!program->IsValid()) {
-        return Result{std::string("input program is not valid")};
+        return std::string("input program is not valid");
     }
 
     BuilderImpl b(program);
@@ -31,12 +30,7 @@
         return b.Diagnostics().str();
     }
 
-    return Result{r.Move()};
-}
-
-// static
-const Program* Converter::ToProgram() {
-    return nullptr;
+    return r.Move();
 }
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/from_program.h b/src/tint/ir/from_program.h
new file mode 100644
index 0000000..1853a9e
--- /dev/null
+++ b/src/tint/ir/from_program.h
@@ -0,0 +1,42 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_FROM_PROGRAM_H_
+#define SRC_TINT_IR_FROM_PROGRAM_H_
+
+#include <string>
+
+#include "src/tint/ir/module.h"
+#include "src/tint/utils/result.h"
+
+// Forward Declarations
+namespace tint {
+class Program;
+}  // namespace tint
+
+namespace tint::ir {
+
+/// Builds an ir::Module from the given Program
+/// @param program the Program to use.
+/// @returns the `utiils::Result` of generating the IR. The result will contain the `ir::Module` on
+/// success, otherwise the `std::string` error.
+///
+/// @note this assumes the `program.IsValid()`, and has had const-eval done so
+/// any abstract values have been calculated and converted into the relevant
+/// concrete types.
+utils::Result<Module, std::string> FromProgram(const Program* program);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_FROM_PROGRAM_H_
diff --git a/src/tint/ir/function.cc b/src/tint/ir/function.cc
index 167664a..a03812e 100644
--- a/src/tint/ir/function.cc
+++ b/src/tint/ir/function.cc
@@ -22,4 +22,36 @@
 
 Function::~Function() = default;
 
+utils::StringStream& operator<<(utils::StringStream& out, Function::PipelineStage value) {
+    switch (value) {
+        case Function::PipelineStage::kVertex:
+            return out << "vertex";
+        case Function::PipelineStage::kFragment:
+            return out << "fragment";
+        case Function::PipelineStage::kCompute:
+            return out << "compute";
+        default:
+            break;
+    }
+    return out << "<unknown>";
+}
+
+utils::StringStream& operator<<(utils::StringStream& out, Function::ReturnAttribute value) {
+    switch (value) {
+        case Function::ReturnAttribute::kLocation:
+            return out << "location";
+        case Function::ReturnAttribute::kFragDepth:
+            return out << "frag_depth";
+        case Function::ReturnAttribute::kSampleMask:
+            return out << "sample_mask";
+        case Function::ReturnAttribute::kPosition:
+            return out << "position";
+        case Function::ReturnAttribute::kInvariant:
+            return out << "invariant";
+        default:
+            break;
+    }
+    return out << "<unknown>";
+}
+
 }  // namespace tint::ir
diff --git a/src/tint/ir/function.h b/src/tint/ir/function.h
index ab3c6eb..e3d5529 100644
--- a/src/tint/ir/function.h
+++ b/src/tint/ir/function.h
@@ -15,13 +15,17 @@
 #ifndef SRC_TINT_IR_FUNCTION_H_
 #define SRC_TINT_IR_FUNCTION_H_
 
+#include <array>
+#include <optional>
+
 #include "src/tint/ir/flow_node.h"
 #include "src/tint/symbol.h"
+#include "src/tint/type/type.h"
 
 // Forward declarations
 namespace tint::ir {
 class Block;
-class Terminator;
+class FunctionTerminator;
 }  // namespace tint::ir
 
 namespace tint::ir {
@@ -29,6 +33,34 @@
 /// An IR representation of a function
 class Function : public utils::Castable<Function, FlowNode> {
   public:
+    /// The pipeline stage for an entry point
+    enum class PipelineStage {
+        /// Not a pipeline entry point
+        kUndefined,
+        /// Vertex
+        kCompute,
+        /// Fragment
+        kFragment,
+        /// Vertex
+        kVertex,
+    };
+
+    /// Attributes attached to return types
+    enum class ReturnAttribute {
+        /// No return attribute
+        kNone,
+        /// Location attribute
+        kLocation,
+        /// Builtin Position attribute
+        kPosition,
+        /// Builtin FragDepth attribute
+        kFragDepth,
+        /// Builtin SampleMask
+        kSampleMask,
+        /// Invariant attribute
+        kInvariant,
+    };
+
     /// Constructor
     Function();
     ~Function() override;
@@ -36,13 +68,29 @@
     /// The function name
     Symbol name;
 
+    /// The pipeline stage for the function, `kUndefined` if the function is not an entry point
+    PipelineStage pipeline_stage = PipelineStage::kUndefined;
+
+    /// If this is a `compute` entry point, holds the workgroup size information
+    std::optional<std::array<uint32_t, 3>> workgroup_size;
+
+    /// The function return type
+    const type::Type* return_type = nullptr;
+    /// The function return attributes if any
+    utils::Vector<ReturnAttribute, 1> return_attributes;
+    /// If the return attribute is `kLocation` this stores the location value.
+    std::optional<uint32_t> return_location;
+
     /// The start target is the first block in a function.
     Block* start_target = nullptr;
     /// The end target is the end of the function. It is used as the branch target if a return is
     /// encountered in the function.
-    Terminator* end_target = nullptr;
+    FunctionTerminator* end_target = nullptr;
 };
 
+utils::StringStream& operator<<(utils::StringStream& out, Function::PipelineStage value);
+utils::StringStream& operator<<(utils::StringStream& out, Function::ReturnAttribute value);
+
 }  // namespace tint::ir
 
 #endif  // SRC_TINT_IR_FUNCTION_H_
diff --git a/src/tint/ir/terminator.cc b/src/tint/ir/function_terminator.cc
similarity index 75%
copy from src/tint/ir/terminator.cc
copy to src/tint/ir/function_terminator.cc
index 6d7678d..5989311 100644
--- a/src/tint/ir/terminator.cc
+++ b/src/tint/ir/function_terminator.cc
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ir/terminator.h"
+#include "src/tint/ir/function_terminator.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::ir::Terminator);
+TINT_INSTANTIATE_TYPEINFO(tint::ir::FunctionTerminator);
 
 namespace tint::ir {
 
-Terminator::Terminator() : Base() {}
+FunctionTerminator::FunctionTerminator() : Base() {}
 
-Terminator::~Terminator() = default;
+FunctionTerminator::~FunctionTerminator() = default;
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/terminator.h b/src/tint/ir/function_terminator.h
similarity index 76%
copy from src/tint/ir/terminator.h
copy to src/tint/ir/function_terminator.h
index 35fbcee..668eab6 100644
--- a/src/tint/ir/terminator.h
+++ b/src/tint/ir/function_terminator.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_IR_TERMINATOR_H_
-#define SRC_TINT_IR_TERMINATOR_H_
+#ifndef SRC_TINT_IR_FUNCTION_TERMINATOR_H_
+#define SRC_TINT_IR_FUNCTION_TERMINATOR_H_
 
 #include "src/tint/ir/flow_node.h"
 
@@ -21,13 +21,13 @@
 
 /// Flow node used as the end of a function. Must only be used as the `end_target` in a function
 /// flow node. There are no instructions and no branches from this node.
-class Terminator : public utils::Castable<Terminator, FlowNode> {
+class FunctionTerminator : public utils::Castable<FunctionTerminator, FlowNode> {
   public:
     /// Constructor
-    Terminator();
-    ~Terminator() override;
+    FunctionTerminator();
+    ~FunctionTerminator() override;
 };
 
 }  // namespace tint::ir
 
-#endif  // SRC_TINT_IR_TERMINATOR_H_
+#endif  // SRC_TINT_IR_FUNCTION_TERMINATOR_H_
diff --git a/src/tint/ir/instruction.cc b/src/tint/ir/instruction.cc
index 3e3192f..0cf4966 100644
--- a/src/tint/ir/instruction.cc
+++ b/src/tint/ir/instruction.cc
@@ -20,7 +20,7 @@
 
 Instruction::Instruction() = default;
 
-Instruction::Instruction(uint32_t id, const type::Type* ty) : id_(id), type_(ty) {}
+Instruction::Instruction(uint32_t identifier, const type::Type* ty) : id(identifier), type(ty) {}
 
 Instruction::~Instruction() = default;
 
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index f2dac09..1d4cafb 100644
--- a/src/tint/ir/instruction.h
+++ b/src/tint/ir/instruction.h
@@ -17,13 +17,15 @@
 
 #include "src/tint/ir/value.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
 /// An instruction in the IR.
 class Instruction : public utils::Castable<Instruction, Value> {
   public:
+    /// The identifier used by instructions that have no value.
+    static constexpr uint32_t kNoID = 0;
+
     Instruction(const Instruction& inst) = delete;
     Instruction(Instruction&& inst) = delete;
     /// Destructor
@@ -33,23 +35,13 @@
     Instruction& operator=(Instruction&& inst) = delete;
 
     /// @returns the type of the value
-    const type::Type* Type() const override { return type_; }
+    const type::Type* Type() const override { return type; }
 
-    /// Write the value to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToValue(utils::StringStream& out) const override {
-        out << "%" << std::to_string(id_);
-        if (type_ != nullptr) {
-            out << "(" << Type()->FriendlyName() << ")";
-        }
-        return out;
-    }
+    /// The instruction identifier
+    const uint32_t id = kNoID;
 
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    virtual utils::StringStream& ToInstruction(utils::StringStream& out) const = 0;
+    /// The instruction type
+    const type::Type* type = nullptr;
 
   protected:
     /// Constructor
@@ -58,10 +50,6 @@
     /// @param id the instruction id
     /// @param type the result type
     Instruction(uint32_t id, const type::Type* type);
-
-  private:
-    uint32_t id_ = 0;
-    const type::Type* type_ = nullptr;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/terminator.cc b/src/tint/ir/root_terminator.cc
similarity index 77%
rename from src/tint/ir/terminator.cc
rename to src/tint/ir/root_terminator.cc
index 6d7678d..bfccf46 100644
--- a/src/tint/ir/terminator.cc
+++ b/src/tint/ir/root_terminator.cc
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ir/terminator.h"
+#include "src/tint/ir/root_terminator.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::ir::Terminator);
+TINT_INSTANTIATE_TYPEINFO(tint::ir::RootTerminator);
 
 namespace tint::ir {
 
-Terminator::Terminator() : Base() {}
+RootTerminator::RootTerminator() : Base() {}
 
-Terminator::~Terminator() = default;
+RootTerminator::~RootTerminator() = default;
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/terminator.h b/src/tint/ir/root_terminator.h
similarity index 78%
rename from src/tint/ir/terminator.h
rename to src/tint/ir/root_terminator.h
index 35fbcee..361aa6d 100644
--- a/src/tint/ir/terminator.h
+++ b/src/tint/ir/root_terminator.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_IR_TERMINATOR_H_
-#define SRC_TINT_IR_TERMINATOR_H_
+#ifndef SRC_TINT_IR_ROOT_TERMINATOR_H_
+#define SRC_TINT_IR_ROOT_TERMINATOR_H_
 
 #include "src/tint/ir/flow_node.h"
 
@@ -21,13 +21,13 @@
 
 /// Flow node used as the end of a function. Must only be used as the `end_target` in a function
 /// flow node. There are no instructions and no branches from this node.
-class Terminator : public utils::Castable<Terminator, FlowNode> {
+class RootTerminator : public utils::Castable<RootTerminator, FlowNode> {
   public:
     /// Constructor
-    Terminator();
-    ~Terminator() override;
+    RootTerminator();
+    ~RootTerminator() override;
 };
 
 }  // namespace tint::ir
 
-#endif  // SRC_TINT_IR_TERMINATOR_H_
+#endif  // SRC_TINT_IR_ROOT_TERMINATOR_H_
diff --git a/src/tint/ir/store.cc b/src/tint/ir/store.cc
index 8d35214..87f3620 100644
--- a/src/tint/ir/store.cc
+++ b/src/tint/ir/store.cc
@@ -19,20 +19,13 @@
 
 namespace tint::ir {
 
-Store::Store(Value* to, Value* from) : Base(), to_(to), from_(from) {
-    TINT_ASSERT(IR, to_);
-    TINT_ASSERT(IR, from_);
-    to_->AddUsage(this);
-    from_->AddUsage(this);
+Store::Store(Value* t, Value* f) : Base(), to(t), from(f) {
+    TINT_ASSERT(IR, to);
+    TINT_ASSERT(IR, from);
+    to->AddUsage(this);
+    from->AddUsage(this);
 }
 
 Store::~Store() = default;
 
-utils::StringStream& Store::ToInstruction(utils::StringStream& out) const {
-    out << "store ";
-    to_->ToValue(out) << ", ";
-    from_->ToValue(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/store.h b/src/tint/ir/store.h
index ea5550d..9095c41 100644
--- a/src/tint/ir/store.h
+++ b/src/tint/ir/store.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -35,19 +34,10 @@
     Store& operator=(const Store& inst) = delete;
     Store& operator=(Store&& inst) = delete;
 
-    /// @returns the value being stored too
-    const Value* to() const { return to_; }
-    /// @returns the value being stored
-    const Value* from() const { return from_; }
-
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
-  private:
-    Value* to_ = nullptr;
-    Value* from_ = nullptr;
+    /// the value being stored to
+    Value* to = nullptr;
+    /// the value being stored
+    Value* from = nullptr;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/store_test.cc b/src/tint/ir/store_test.cc
index 82347dc..0005c80 100644
--- a/src/tint/ir/store_test.cc
+++ b/src/tint/ir/store_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/test_helper.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 namespace {
@@ -32,16 +31,12 @@
     const auto* inst = b.builder.Store(to, b.builder.Constant(4_i));
 
     ASSERT_TRUE(inst->Is<Store>());
-    ASSERT_EQ(inst->to(), to);
+    ASSERT_EQ(inst->to, to);
 
-    ASSERT_TRUE(inst->from()->Is<Constant>());
-    auto lhs = inst->from()->As<Constant>()->value;
+    ASSERT_TRUE(inst->from->Is<Constant>());
+    auto lhs = inst->from->As<Constant>()->value;
     ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "store %0, 4i");
 }
 
 TEST_F(IR_InstructionTest, Store_Usage) {
@@ -50,13 +45,13 @@
     auto* to = b.builder.Discard();
     const auto* inst = b.builder.Store(to, b.builder.Constant(4_i));
 
-    ASSERT_NE(inst->to(), nullptr);
-    ASSERT_EQ(inst->to()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->to()->Usage()[0], inst);
+    ASSERT_NE(inst->to, nullptr);
+    ASSERT_EQ(inst->to->Usage().Length(), 1u);
+    EXPECT_EQ(inst->to->Usage()[0], inst);
 
-    ASSERT_NE(inst->from(), nullptr);
-    ASSERT_EQ(inst->from()->Usage().Length(), 1u);
-    EXPECT_EQ(inst->from()->Usage()[0], inst);
+    ASSERT_NE(inst->from, nullptr);
+    ASSERT_EQ(inst->from->Usage().Length(), 1u);
+    EXPECT_EQ(inst->from->Usage()[0], inst);
 }
 
 }  // namespace
diff --git a/src/tint/ir/test_helper.h b/src/tint/ir/test_helper.h
index 47f8821..d8568c2 100644
--- a/src/tint/ir/test_helper.h
+++ b/src/tint/ir/test_helper.h
@@ -24,6 +24,7 @@
 #include "src/tint/ir/disassembler.h"
 #include "src/tint/number.h"
 #include "src/tint/program_builder.h"
+#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
diff --git a/src/tint/ir/unary.cc b/src/tint/ir/unary.cc
index f3c4f24..e3f20eb 100644
--- a/src/tint/ir/unary.cc
+++ b/src/tint/ir/unary.cc
@@ -19,36 +19,12 @@
 
 namespace tint::ir {
 
-Unary::Unary(uint32_t id, Kind kind, const type::Type* type, Value* val)
-    : Base(id, type), kind_(kind), val_(val) {
+Unary::Unary(uint32_t identifier, Kind kind, const type::Type* ty, Value* val)
+    : Base(identifier, ty), kind_(kind), val_(val) {
     TINT_ASSERT(IR, val_);
     val_->AddUsage(this);
 }
 
 Unary::~Unary() = default;
 
-utils::StringStream& Unary::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = ";
-    switch (GetKind()) {
-        case Unary::Kind::kAddressOf:
-            out << "addr_of";
-            break;
-        case Unary::Kind::kComplement:
-            out << "bit_complement";
-            break;
-        case Unary::Kind::kIndirection:
-            out << "indirection";
-            break;
-        case Unary::Kind::kNegation:
-            out << "negation";
-            break;
-        case Unary::Kind::kNot:
-            out << "log_not";
-            break;
-    }
-    out << " ";
-    val_->ToValue(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/unary.h b/src/tint/ir/unary.h
index ce3e5d7..64301d4 100644
--- a/src/tint/ir/unary.h
+++ b/src/tint/ir/unary.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -30,7 +29,6 @@
         kComplement,
         kIndirection,
         kNegation,
-        kNot,
     };
 
     /// Constructor
@@ -52,11 +50,6 @@
     /// @returns the value for the instruction
     const Value* Val() const { return val_; }
 
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
   private:
     Kind kind_;
     Value* val_ = nullptr;
diff --git a/src/tint/ir/unary_test.cc b/src/tint/ir/unary_test.cc
index ff247a2..e205d8f 100644
--- a/src/tint/ir/unary_test.cc
+++ b/src/tint/ir/unary_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/test_helper.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 namespace {
@@ -42,10 +41,6 @@
     auto lhs = inst->Val()->As<Constant>()->value;
     ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(ptr<private, i32, read_write>) = addr_of 4i");
 }
 
 TEST_F(IR_InstructionTest, CreateComplement) {
@@ -60,10 +55,6 @@
     auto lhs = inst->Val()->As<Constant>()->value;
     ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = bit_complement 4i");
 }
 
 TEST_F(IR_InstructionTest, CreateIndirection) {
@@ -80,10 +71,6 @@
     auto lhs = inst->Val()->As<Constant>()->value;
     ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = indirection 4i");
 }
 
 TEST_F(IR_InstructionTest, CreateNegation) {
@@ -98,28 +85,6 @@
     auto lhs = inst->Val()->As<Constant>()->value;
     ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
     EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(i32) = negation 4i");
-}
-
-TEST_F(IR_InstructionTest, CreateNot) {
-    auto& b = CreateEmptyBuilder();
-    const auto* inst =
-        b.builder.Not(b.builder.ir.types.Get<type::Bool>(), b.builder.Constant(true));
-
-    ASSERT_TRUE(inst->Is<Unary>());
-    EXPECT_EQ(inst->GetKind(), Unary::Kind::kNot);
-
-    ASSERT_TRUE(inst->Val()->Is<Constant>());
-    auto lhs = inst->Val()->As<Constant>()->value;
-    ASSERT_TRUE(lhs->Is<constant::Scalar<bool>>());
-    EXPECT_TRUE(lhs->As<constant::Scalar<bool>>()->ValueAs<bool>());
-
-    utils::StringStream str;
-    inst->ToInstruction(str);
-    EXPECT_EQ(str.str(), "%1(bool) = log_not true");
 }
 
 TEST_F(IR_InstructionTest, Unary_Usage) {
diff --git a/src/tint/ir/user_call.cc b/src/tint/ir/user_call.cc
index f23a2ca..e7e2e26 100644
--- a/src/tint/ir/user_call.cc
+++ b/src/tint/ir/user_call.cc
@@ -13,24 +13,21 @@
 // limitations under the License.
 
 #include "src/tint/ir/user_call.h"
+
+#include <utility>
+
 #include "src/tint/debug.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::UserCall);
 
 namespace tint::ir {
 
-UserCall::UserCall(uint32_t id, const type::Type* type, Symbol name, utils::VectorRef<Value*> args)
-    : Base(id, type, args), name_(name) {}
+UserCall::UserCall(uint32_t identifier,
+                   const type::Type* ty,
+                   Symbol n,
+                   utils::VectorRef<Value*> arguments)
+    : Base(identifier, ty, std::move(arguments)), name(n) {}
 
 UserCall::~UserCall() = default;
 
-utils::StringStream& UserCall::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = call " << name_.Name();
-    if (Args().Length() > 0) {
-        out << ", ";
-    }
-    EmitArgs(out);
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/user_call.h b/src/tint/ir/user_call.h
index 1e2b070..1edfa8b 100644
--- a/src/tint/ir/user_call.h
+++ b/src/tint/ir/user_call.h
@@ -18,7 +18,6 @@
 #include "src/tint/ir/call.h"
 #include "src/tint/symbol.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -38,16 +37,8 @@
     UserCall& operator=(const UserCall& inst) = delete;
     UserCall& operator=(UserCall&& inst) = delete;
 
-    /// @returns the function name
-    Symbol Name() const { return name_; }
-
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
-  private:
-    Symbol name_{};
+    /// The function name
+    Symbol name;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index 7f3f4b7..e91e4c4 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -17,7 +17,6 @@
 
 #include "src/tint/type/type.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 #include "src/tint/utils/unique_vector.h"
 
 // Forward declarations
@@ -50,11 +49,6 @@
     /// @returns the type of the value
     virtual const type::Type* Type() const = 0;
 
-    /// Write the value to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    virtual utils::StringStream& ToValue(utils::StringStream& out) const = 0;
-
   protected:
     /// Constructor
     Value();
diff --git a/src/tint/ir/var.cc b/src/tint/ir/var.cc
index 740a35e..b7ede1d 100644
--- a/src/tint/ir/var.cc
+++ b/src/tint/ir/var.cc
@@ -19,17 +19,12 @@
 
 namespace tint::ir {
 
-Var::Var(uint32_t id,
+Var::Var(uint32_t identifier,
          const type::Type* ty,
-         builtin::AddressSpace address_space,
-         builtin::Access access)
-    : Base(id, ty), address_space_(address_space), access_(access) {}
+         builtin::AddressSpace addr_space,
+         builtin::Access acc)
+    : Base(identifier, ty), address_space(addr_space), access(acc) {}
 
 Var::~Var() = default;
 
-utils::StringStream& Var::ToInstruction(utils::StringStream& out) const {
-    ToValue(out) << " = var " << address_space_ << " " << access_;
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/var.h b/src/tint/ir/var.h
index 456e7d3..dd46f54 100644
--- a/src/tint/ir/var.h
+++ b/src/tint/ir/var.h
@@ -19,7 +19,6 @@
 #include "src/tint/builtin/address_space.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/utils/castable.h"
-#include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
 
@@ -42,20 +41,11 @@
     Var& operator=(const Var& inst) = delete;
     Var& operator=(Var&& inst) = delete;
 
-    /// @returns the address space
-    builtin::AddressSpace AddressSpace() const { return address_space_; }
+    /// The variable address space
+    builtin::AddressSpace address_space = builtin::AddressSpace::kUndefined;
 
-    /// @returns the access mode
-    builtin::Access Access() const { return access_; }
-
-    /// Write the instruction to the given stream
-    /// @param out the stream to write to
-    /// @returns the stream
-    utils::StringStream& ToInstruction(utils::StringStream& out) const override;
-
-  private:
-    builtin::AddressSpace address_space_;
-    builtin::Access access_;
+    /// The variable access mode
+    builtin::Access access = builtin::Access::kUndefined;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/program.cc b/src/tint/program.cc
index c8466f5..a1cf5c8 100644
--- a/src/tint/program.cc
+++ b/src/tint/program.cc
@@ -134,19 +134,6 @@
     return Sem().Get(type_decl);
 }
 
-std::string Program::FriendlyName(ast::Type type) const {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL(Program, type, ID());
-    return type ? type->identifier->symbol.Name() : "<null>";
-}
-
-std::string Program::FriendlyName(const type::Type* type) const {
-    return type ? type->FriendlyName() : "<null>";
-}
-
-std::string Program::FriendlyName(std::nullptr_t) const {
-    return "<null>";
-}
-
 void Program::AssertNotMoved() const {
     TINT_ASSERT(Program, !moved_);
 }
diff --git a/src/tint/program.h b/src/tint/program.h
index 1c57fe0..ae46acd 100644
--- a/src/tint/program.h
+++ b/src/tint/program.h
@@ -151,19 +151,6 @@
     /// the type declaration has no resolved type.
     const type::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
-    /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
-    std::string FriendlyName(ast::Type type) const;
-
-    /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
-    std::string FriendlyName(const type::Type* type) const;
-
-    /// Overload of FriendlyName, which removes an ambiguity when passing nullptr.
-    /// Simplifies test code.
-    /// @returns "<null>"
-    std::string FriendlyName(std::nullptr_t) const;
-
     /// A function that can be used to print a program
     using Printer = std::string (*)(const Program*);
 
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index 4181f96..fc2e795 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -113,19 +113,6 @@
     return Sem().Get(type_decl);
 }
 
-std::string ProgramBuilder::FriendlyName(ast::Type type) const {
-    TINT_ASSERT_PROGRAM_IDS_EQUAL(ProgramBuilder, type, ID());
-    return type.expr ? type->identifier->symbol.Name() : "<null>";
-}
-
-std::string ProgramBuilder::FriendlyName(const type::Type* type) const {
-    return type ? type->FriendlyName() : "<null>";
-}
-
-std::string ProgramBuilder::FriendlyName(std::nullptr_t) const {
-    return "<null>";
-}
-
 ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {}
 
 const ast::Statement* ProgramBuilder::WrapInStatement(const ast::Expression* expr) {
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 49b14fc..b944e21 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -3919,19 +3919,6 @@
     /// the type declaration has no resolved type.
     const type::Type* TypeOf(const ast::TypeDecl* type_decl) const;
 
-    /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
-    std::string FriendlyName(ast::Type type) const;
-
-    /// @param type a type
-    /// @returns the name for `type` that closely resembles how it would be declared in WGSL.
-    std::string FriendlyName(const type::Type* type) const;
-
-    /// Overload of FriendlyName, which removes an ambiguity when passing nullptr.
-    /// Simplifies test code.
-    /// @returns "<null>"
-    std::string FriendlyName(std::nullptr_t) const;
-
     /// Wraps the ast::Expression in a statement. This is used by tests that
     /// construct a partial AST and require the Resolver to reach these
     /// nodes.
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 8061f74..c3c12f2 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -4075,6 +4075,12 @@
     // All parameters to GLSL.std.450 extended instructions are IDs.
     for (uint32_t iarg = 2; iarg < inst.NumInOperands(); ++iarg) {
         TypedExpression operand = MakeOperand(inst, iarg);
+        if (!operand.expr) {
+            if (!failed()) {
+                Fail() << "unexpected failure to make an operand";
+            }
+            return {};
+        }
         if (first_operand_type == nullptr) {
             first_operand_type = operand.type;
         }
@@ -5183,6 +5189,9 @@
             const auto usage = parser_impl_.GetHandleUsage(arg_id);
             const auto* mem_obj_decl =
                 parser_impl_.GetMemoryObjectDeclarationForHandle(arg_id, usage.IsTexture());
+            if (!mem_obj_decl) {
+                return Fail() << "invalid handle object passed as function parameter";
+            }
             expr = MakeExpression(mem_obj_decl->result_id());
             // Pass the handle through instead of a pointer to the handle.
             expr.type = parser_impl_.GetHandleTypeForSpirvHandle(*mem_obj_decl);
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index 6f6bf1e..220ccb9 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -1891,8 +1891,6 @@
     }
     auto source = GetSourceForInst(inst);
 
-    // TODO(dneto): Handle spec constants too?
-
     auto* original_ast_type = ConvertType(inst->type_id());
     if (original_ast_type == nullptr) {
         return {};
@@ -1952,6 +1950,12 @@
             declared_constant_composites_.insert({id, decl->name->symbol});
             return {original_ast_type, builder_.Expr(name)};
         }
+        case spv::Op::OpSpecConstantComposite:
+        case spv::Op::OpSpecConstantOp: {
+            // TODO(crbug.com/tint/111): Handle OpSpecConstantOp and OpSpecConstantComposite here.
+            Fail() << "unimplemented: OpSpecConstantOp and OpSpecConstantComposite";
+            return {};
+        }
         default:
             break;
     }
diff --git a/src/tint/reader/spirv/parser_impl_handle_test.cc b/src/tint/reader/spirv/parser_impl_handle_test.cc
index dd572b6..15a62a6 100644
--- a/src/tint/reader/spirv/parser_impl_handle_test.cc
+++ b/src/tint/reader/spirv/parser_impl_handle_test.cc
@@ -4236,5 +4236,30 @@
     ASSERT_EQ(expect, got);
 }
 
+TEST_F(SpvParserHandleTest, OpUndef_FunctionParam) {
+    // We can't generate reasonable WGSL when an OpUndef is passed as an argument to a function
+    // parameter that is expecting an image object, so just make sure that we do not crash.
+    const auto assembly = Preamble() + FragMain() + " " + CommonTypes() + R"(
+     %f_ty = OpTypeFunction %void %f_storage_1d
+     %20 = OpUndef %f_storage_1d
+
+     %func = OpFunction %void None %f_ty
+     %im = OpFunctionParameter %f_storage_1d
+     %func_entry = OpLabel
+     OpImageWrite %im %uint_1 %v4float_null
+     OpReturn
+     OpFunctionEnd
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %foo = OpFunctionCall %void %func %20
+     OpReturn
+     OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    EXPECT_FALSE(p->BuildAndParseInternalModule());
+    EXPECT_EQ(p->error(), "invalid handle object passed as function parameter");
+}
+
 }  // namespace
 }  // namespace tint::reader::spirv
diff --git a/src/tint/resolver/builtin_structs.cc b/src/tint/resolver/builtin_structs.cc
index 4e45c16..2b8c117 100644
--- a/src/tint/resolver/builtin_structs.cc
+++ b/src/tint/resolver/builtin_structs.cc
@@ -128,12 +128,12 @@
                 },
                 [&](Default) {
                     TINT_ICE(Resolver, b.Diagnostics())
-                        << "unhandled modf type: " << b.FriendlyName(ty);
+                        << "unhandled modf type: " << ty->FriendlyName();
                     return nullptr;
                 });
         },
         [&](Default) {
-            TINT_ICE(Resolver, b.Diagnostics()) << "unhandled modf type: " << b.FriendlyName(ty);
+            TINT_ICE(Resolver, b.Diagnostics()) << "unhandled modf type: " << ty->FriendlyName();
             return nullptr;
         });
 }
@@ -208,12 +208,12 @@
                 },
                 [&](Default) {
                     TINT_ICE(Resolver, b.Diagnostics())
-                        << "unhandled frexp type: " << b.FriendlyName(ty);
+                        << "unhandled frexp type: " << ty->FriendlyName();
                     return nullptr;
                 });
         },
         [&](Default) {
-            TINT_ICE(Resolver, b.Diagnostics()) << "unhandled frexp type: " << b.FriendlyName(ty);
+            TINT_ICE(Resolver, b.Diagnostics()) << "unhandled frexp type: " << ty->FriendlyName();
             return nullptr;
         });
 }
@@ -231,7 +231,7 @@
         },
         [&](Default) {
             TINT_ICE(Resolver, b.Diagnostics())
-                << "unhandled atomic_compare_exchange type: " << b.FriendlyName(ty);
+                << "unhandled atomic_compare_exchange type: " << ty->FriendlyName();
             return nullptr;
         });
 }
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index 2aa755a..35ea8bb 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -271,7 +271,7 @@
             // --- Below this point are the failure cases ---
         } else if constexpr (IsAbstract<FROM>) {
             // [abstract-numeric -> x] - materialization failure
-            auto msg = OverflowErrorMessage(scalar->value, builder.FriendlyName(target_ty));
+            auto msg = OverflowErrorMessage(scalar->value, target_ty->FriendlyName());
             if (use_runtime_semantics) {
                 builder.Diagnostics().add_warning(tint::diag::System::Resolver, msg, source);
                 switch (conv.Failure()) {
@@ -287,7 +287,7 @@
         } else if constexpr (IsFloatingPoint<TO>) {
             // [x -> floating-point] - number not exactly representable
             // https://www.w3.org/TR/WGSL/#floating-point-conversion
-            auto msg = OverflowErrorMessage(scalar->value, builder.FriendlyName(target_ty));
+            auto msg = OverflowErrorMessage(scalar->value, target_ty->FriendlyName());
             if (use_runtime_semantics) {
                 builder.Diagnostics().add_warning(tint::diag::System::Resolver, msg, source);
                 switch (conv.Failure()) {
@@ -534,7 +534,7 @@
 
     if constexpr (IsFloatingPoint<T>) {
         if (!std::isfinite(v.value)) {
-            AddError(OverflowErrorMessage(v, builder.FriendlyName(t)), source);
+            AddError(OverflowErrorMessage(v, t->FriendlyName()), source);
             if (use_runtime_semantics_) {
                 return ZeroValue(t);
             } else {
@@ -2689,7 +2689,7 @@
             [&](Default) {
                 TINT_ICE(Resolver, builder.Diagnostics())
                     << "unhandled element type for frexp() const-eval: "
-                    << builder.FriendlyName(s->Type());
+                    << s->Type()->FriendlyName();
                 return FractExp{utils::Failure, utils::Failure};
             });
     };
@@ -3318,7 +3318,7 @@
             return utils::Failure;
         }
         auto e2_scaled = Mul(source, ty, e2_scale.Get(), e2);
-        if (!e1_scaled) {
+        if (!e2_scaled) {
             return utils::Failure;
         }
         return Sub(source, ty, e1_scaled.Get(), e2_scaled.Get());
diff --git a/src/tint/resolver/const_eval_builtin_test.cc b/src/tint/resolver/const_eval_builtin_test.cc
index 7aecfac..a172ca3 100644
--- a/src/tint/resolver/const_eval_builtin_test.cc
+++ b/src/tint/resolver/const_eval_builtin_test.cc
@@ -2288,6 +2288,14 @@
             // Overflow the k^2 operation
             E({down_right, pos_y, Val(T::Highest())}, error_msg(T::Highest(), "*", T::Highest())),
         });
+    ConcatIntoIf<std::is_same_v<T, f32>>(  //
+        r, std::vector<Case>{
+               // Overflow the final multiply by e2 operation
+               // From https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=58526
+               E({Vec(T(-2.22218755e-15), T(0)), Vec(T(-198225753253481323832809619456.0), T(0)),
+                  Val(T(40.0313720703125))},
+                 error_msg(T(35267222007971840.0), "*", T(-198225753253481323832809619456.0))),
+           });
 
     return r;
 }
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 94d916c..696f279 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -75,8 +75,6 @@
 template <typename CASE>
 class MaterializeTest : public resolver::ResolverTestWithParam<CASE> {
   protected:
-    using ProgramBuilder::FriendlyName;
-
     void CheckTypesAndValues(const sem::ValueExpression* expr,
                              const tint::type::Type* expected_sem_ty,
                              const std::variant<AInt, AFloat>& expected_value) {
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index de29692..a65d825 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -921,9 +921,8 @@
     }
     auto* cond = expr->ConstantValue();
     if (auto* ty = cond->Type(); !ty->Is<type::Bool>()) {
-        AddError(
-            "const assertion condition must be a bool, got '" + builder_->FriendlyName(ty) + "'",
-            assertion->condition->source);
+        AddError("const assertion condition must be a bool, got '" + ty->FriendlyName() + "'",
+                 assertion->condition->source);
         return nullptr;
     }
     if (!cond->ValueAs<bool>()) {
@@ -1833,8 +1832,8 @@
         materialized_val = val.Get();
         if (TINT_UNLIKELY(!materialized_val)) {
             TINT_ICE(Resolver, diagnostics_)
-                << decl->source << "ConvertValue(" << builder_->FriendlyName(expr_val->Type())
-                << " -> " << builder_->FriendlyName(concrete_ty) << ") returned invalid value";
+                << decl->source << "ConvertValue(" << expr_val->Type()->FriendlyName() << " -> "
+                << concrete_ty->FriendlyName() << ") returned invalid value";
             return nullptr;
         }
     }
@@ -2590,7 +2589,7 @@
         }
         if (!ApplyAddressSpaceUsageToType(address_space, store_ty,
                                           store_ty_expr->Declaration()->source)) {
-            AddNote("while instantiating " + builder_->FriendlyName(out), ident->source);
+            AddNote("while instantiating " + out->FriendlyName(), ident->source);
             return nullptr;
         }
         return out;
@@ -3837,7 +3836,7 @@
 
     if (auto* ty = count_val->Type(); !ty->is_integer_scalar()) {
         AddError("array count must evaluate to a constant integer expression, but is type '" +
-                     builder_->FriendlyName(ty) + "'",
+                     ty->FriendlyName() + "'",
                  count_expr->source);
         return nullptr;
     }
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 9cbb65f..4014b21 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -593,7 +593,7 @@
         if (TINT_UNLIKELY(((!type::Type::DeepestElementOf(ty)->IsAnyOf<type::I32, type::U32>())))) {
             TINT_ICE(Transform, b.Diagnostics())
                 << "insertBits polyfill only support i32, u32, and vector of i32 or u32, got "
-                << b.FriendlyName(ty);
+                << ty->FriendlyName();
             return {};
         }
 
diff --git a/src/tint/transform/builtin_polyfill_test.cc b/src/tint/transform/builtin_polyfill_test.cc
index 65c1f29..ad47af6 100644
--- a/src/tint/transform/builtin_polyfill_test.cc
+++ b/src/tint/transform/builtin_polyfill_test.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/transform/direct_variable_access.h"
 #include "src/tint/transform/test_helper.h"
 
 namespace tint::transform {
@@ -3673,8 +3674,23 @@
 DataMap polyfillWorkgroupUniformLoad() {
     BuiltinPolyfill::Builtins builtins;
     builtins.workgroup_uniform_load = true;
+
     DataMap data;
     data.Add<BuiltinPolyfill::Config>(builtins);
+
+    return data;
+}
+
+DataMap polyfillWorkgroupUniformLoadWithDirectVariableAccess() {
+    DataMap data;
+
+    BuiltinPolyfill::Builtins builtins;
+    builtins.workgroup_uniform_load = true;
+    data.Add<BuiltinPolyfill::Config>(builtins);
+
+    DirectVariableAccess::Options options;
+    data.Add<DirectVariableAccess::Config>(options);
+
     return data;
 }
 
@@ -3830,6 +3846,50 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(BuiltinPolyfillTest, WorkgroupUniformLoad_DirectVariableAccess) {
+    auto* src = R"(
+var<workgroup> v : i32;
+var<workgroup> v2 : i32;
+
+fn f() {
+  let r = workgroupUniformLoad(&v);
+  let s = workgroupUniformLoad(&v2);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn tint_workgroupUniformLoad_v() -> i32 {
+  workgroupBarrier();
+  let result = v;
+  workgroupBarrier();
+  return result;
+}
+
+fn tint_workgroupUniformLoad_v2() -> i32 {
+  workgroupBarrier();
+  let result = v2;
+  workgroupBarrier();
+  return result;
+}
+
+var<workgroup> v : i32;
+
+var<workgroup> v2 : i32;
+
+fn f() {
+  let r = tint_workgroupUniformLoad_v();
+  let s = tint_workgroupUniformLoad_v2();
+}
+)";
+
+    auto got = Run<BuiltinPolyfill, DirectVariableAccess>(
+        src, polyfillWorkgroupUniformLoadWithDirectVariableAccess());
+
+    EXPECT_EQ(expect, str(got));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // quantizeToF16
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index c25bcac..3860de4 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -251,7 +251,7 @@
             [&](Default) -> const ast::Expression* {
                 TINT_ICE(Transform, b.Diagnostics())
                     << "unhandled object type in robustness of array index: "
-                    << src->FriendlyName(obj_type->UnwrapRef());
+                    << obj_type->UnwrapRef()->FriendlyName();
                 return nullptr;
             });
     }
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index fa4fade..fed3943 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -414,7 +414,7 @@
                     auto std140_mat = std140_mats.GetOrCreate(mat, [&] {
                         auto name = b.Symbols().New("mat" + std::to_string(mat->columns()) + "x" +
                                                     std::to_string(mat->rows()) + "_" +
-                                                    src->FriendlyName(mat->type()));
+                                                    mat->type()->FriendlyName());
                         auto members =
                             DecomposedMatrixStructMembers(mat, "col", mat->Align(), mat->Size());
                         b.Structure(name, members);
@@ -652,7 +652,7 @@
             [&](const type::F16*) { return "f16"; },
             [&](Default) {
                 TINT_ICE(Transform, b.Diagnostics())
-                    << "unhandled type for conversion name: " << src->FriendlyName(ty);
+                    << "unhandled type for conversion name: " << ty->FriendlyName();
                 return "";
             });
     }
@@ -728,7 +728,7 @@
                         stmts.Push(b.Return(b.Call(mat_ty, std::move(mat_args))));
                     } else {
                         TINT_ICE(Transform, b.Diagnostics())
-                            << "failed to find std140 matrix info for: " << src->FriendlyName(ty);
+                            << "failed to find std140 matrix info for: " << ty->FriendlyName();
                     }
                 },  //
                 [&](const type::Array* arr) {
@@ -758,7 +758,7 @@
                 },
                 [&](Default) {
                     TINT_ICE(Transform, b.Diagnostics())
-                        << "unhandled type for conversion: " << src->FriendlyName(ty);
+                        << "unhandled type for conversion: " << ty->FriendlyName();
                 });
 
             // Generate the function
@@ -1104,7 +1104,7 @@
                 },  //
                 [&](Default) -> ExprTypeName {
                     TINT_ICE(Transform, b.Diagnostics())
-                        << "unhandled type for access chain: " << src->FriendlyName(ty);
+                        << "unhandled type for access chain: " << ty->FriendlyName();
                     return {};
                 });
         }
@@ -1125,7 +1125,7 @@
                 },  //
                 [&](Default) -> ExprTypeName {
                     TINT_ICE(Transform, b.Diagnostics())
-                        << "unhandled type for access chain: " << src->FriendlyName(ty);
+                        << "unhandled type for access chain: " << ty->FriendlyName();
                     return {};
                 });
         }
@@ -1154,7 +1154,7 @@
             },  //
             [&](Default) -> ExprTypeName {
                 TINT_ICE(Transform, b.Diagnostics())
-                    << "unhandled type for access chain: " << src->FriendlyName(ty);
+                    << "unhandled type for access chain: " << ty->FriendlyName();
                 return {};
             });
     }
diff --git a/src/tint/transform/vectorize_matrix_conversions.cc b/src/tint/transform/vectorize_matrix_conversions.cc
index 748346e..97e4d3c 100644
--- a/src/tint/transform/vectorize_matrix_conversions.cc
+++ b/src/tint/transform/vectorize_matrix_conversions.cc
@@ -124,8 +124,8 @@
                 utils::GetOrCreate(matrix_convs, HelperFunctionKey{{src_type, dst_type}}, [&] {
                     auto name = b.Symbols().New(
                         "convert_mat" + std::to_string(src_type->columns()) + "x" +
-                        std::to_string(src_type->rows()) + "_" + b.FriendlyName(src_type->type()) +
-                        "_" + b.FriendlyName(dst_type->type()));
+                        std::to_string(src_type->rows()) + "_" + src_type->type()->FriendlyName() +
+                        "_" + dst_type->type()->FriendlyName());
                     b.Func(name,
                            utils::Vector{
                                b.Param("value", CreateASTTypeFor(ctx, src_type)),
diff --git a/src/tint/type/test_helper.h b/src/tint/type/test_helper.h
index 45ff61a..95827b0 100644
--- a/src/tint/type/test_helper.h
+++ b/src/tint/type/test_helper.h
@@ -45,15 +45,15 @@
 }  // namespace tint::type
 
 /// Helper macro for testing that a type was as expected
-#define EXPECT_TYPE(GOT, EXPECT)                                         \
-    do {                                                                 \
-        const type::Type* got = GOT;                                     \
-        const type::Type* expect = EXPECT;                               \
-        if (got != expect) {                                             \
-            ADD_FAILURE() << #GOT " != " #EXPECT "\n"                    \
-                          << "  " #GOT ": " << FriendlyName(got) << "\n" \
-                          << "  " #EXPECT ": " << FriendlyName(expect);  \
-        }                                                                \
+#define EXPECT_TYPE(GOT, EXPECT)                                                                \
+    do {                                                                                        \
+        const type::Type* got = GOT;                                                            \
+        const type::Type* expect = EXPECT;                                                      \
+        if (got != expect) {                                                                    \
+            ADD_FAILURE() << #GOT " != " #EXPECT "\n"                                           \
+                          << "  " #GOT ": " << (got ? got->FriendlyName() : "<null>") << "\n"   \
+                          << "  " #EXPECT ": " << (expect ? expect->FriendlyName() : "<null>"); \
+        }                                                                                       \
     } while (false)
 
 #endif  // SRC_TINT_TYPE_TEST_HELPER_H_
diff --git a/src/tint/utils/castable.h b/src/tint/utils/castable.h
index abe7610..1df31e7 100644
--- a/src/tint/utils/castable.h
+++ b/src/tint/utils/castable.h
@@ -423,9 +423,9 @@
     using TrueBase = BASE;
 
     /// Constructor
-    /// @param args the arguments to forward to the base class.
+    /// @param arguments the arguments to forward to the base class.
     template <typename... ARGS>
-    inline explicit Castable(ARGS&&... args) : TrueBase(std::forward<ARGS>(args)...) {
+    inline explicit Castable(ARGS&&... arguments) : TrueBase(std::forward<ARGS>(arguments)...) {
         this->type_info_ = &TypeInfo::Of<CLASS>();
     }
 
diff --git a/src/tint/utils/hash.h b/src/tint/utils/hash.h
index a63a303..af590f4 100644
--- a/src/tint/utils/hash.h
+++ b/src/tint/utils/hash.h
@@ -18,6 +18,7 @@
 #include <stdint.h>
 #include <cstdio>
 #include <functional>
+#include <string>
 #include <tuple>
 #include <utility>
 #include <variant>
@@ -152,6 +153,29 @@
     }
 };
 
+/// Hasher specialization for std::string, which also supports hashing of const char* and
+/// std::string_view without first constructing a std::string.
+template <>
+struct Hasher<std::string> {
+    /// @param str the string to hash
+    /// @returns a hash of the string
+    size_t operator()(const std::string& str) const {
+        return std::hash<std::string_view>()(std::string_view(str));
+    }
+
+    /// @param str the string to hash
+    /// @returns a hash of the string
+    size_t operator()(const char* str) const {
+        return std::hash<std::string_view>()(std::string_view(str));
+    }
+
+    /// @param str the string to hash
+    /// @returns a hash of the string
+    size_t operator()(const std::string_view& str) const {
+        return std::hash<std::string_view>()(str);
+    }
+};
+
 /// @returns a hash of the variadic list of arguments.
 ///          The returned hash is dependent on the order of the arguments.
 template <typename... ARGS>
@@ -176,6 +200,47 @@
     return hash;
 }
 
+/// A STL-compatible equal_to implementation that specializes for types.
+template <typename T>
+struct EqualTo {
+    /// @param lhs the left hand side value
+    /// @param rhs the right hand side value
+    /// @returns true if the two values are equal
+    constexpr bool operator()(const T& lhs, const T& rhs) const {
+        return std::equal_to<T>()(lhs, rhs);
+    }
+};
+
+/// A specialization for EqualTo for std::string, which supports additional comparision with
+/// std::string_view and const char*.
+template <>
+struct EqualTo<std::string> {
+    /// @param lhs the left hand side value
+    /// @param rhs the right hand side value
+    /// @returns true if the two values are equal
+    bool operator()(const std::string& lhs, const std::string& rhs) const { return lhs == rhs; }
+
+    /// @param lhs the left hand side value
+    /// @param rhs the right hand side value
+    /// @returns true if the two values are equal
+    bool operator()(const std::string& lhs, const char* rhs) const { return lhs == rhs; }
+
+    /// @param lhs the left hand side value
+    /// @param rhs the right hand side value
+    /// @returns true if the two values are equal
+    bool operator()(const std::string& lhs, std::string_view rhs) const { return lhs == rhs; }
+
+    /// @param lhs the left hand side value
+    /// @param rhs the right hand side value
+    /// @returns true if the two values are equal
+    bool operator()(const char* lhs, const std::string& rhs) const { return lhs == rhs; }
+
+    /// @param lhs the left hand side value
+    /// @param rhs the right hand side value
+    /// @returns true if the two values are equal
+    bool operator()(std::string_view lhs, const std::string& rhs) const { return lhs == rhs; }
+};
+
 /// Wrapper for a hashable type enabling the wrapped value to be used as a key
 /// for an unordered_map or unordered_set.
 template <typename T>
diff --git a/src/tint/utils/hash_test.cc b/src/tint/utils/hash_test.cc
index cdceab4..2261eb8 100644
--- a/src/tint/utils/hash_test.cc
+++ b/src/tint/utils/hash_test.cc
@@ -73,5 +73,41 @@
     EXPECT_EQ(m[W({2, 1})], 0);
 }
 
+TEST(EqualTo, String) {
+    std::string str_a = "hello";
+    std::string str_b = "world";
+    const char* cstr_a = "hello";
+    const char* cstr_b = "world";
+    std::string_view sv_a = "hello";
+    std::string_view sv_b = "world";
+    EXPECT_TRUE(EqualTo<std::string>()(str_a, str_a));
+    EXPECT_TRUE(EqualTo<std::string>()(str_a, cstr_a));
+    EXPECT_TRUE(EqualTo<std::string>()(str_a, sv_a));
+    EXPECT_TRUE(EqualTo<std::string>()(str_a, str_a));
+    EXPECT_TRUE(EqualTo<std::string>()(cstr_a, str_a));
+    EXPECT_TRUE(EqualTo<std::string>()(sv_a, str_a));
+
+    EXPECT_FALSE(EqualTo<std::string>()(str_a, str_b));
+    EXPECT_FALSE(EqualTo<std::string>()(str_a, cstr_b));
+    EXPECT_FALSE(EqualTo<std::string>()(str_a, sv_b));
+    EXPECT_FALSE(EqualTo<std::string>()(str_a, str_b));
+    EXPECT_FALSE(EqualTo<std::string>()(cstr_a, str_b));
+    EXPECT_FALSE(EqualTo<std::string>()(sv_a, str_b));
+
+    EXPECT_FALSE(EqualTo<std::string>()(str_b, str_a));
+    EXPECT_FALSE(EqualTo<std::string>()(str_b, cstr_a));
+    EXPECT_FALSE(EqualTo<std::string>()(str_b, sv_a));
+    EXPECT_FALSE(EqualTo<std::string>()(str_b, str_a));
+    EXPECT_FALSE(EqualTo<std::string>()(cstr_b, str_a));
+    EXPECT_FALSE(EqualTo<std::string>()(sv_b, str_a));
+
+    EXPECT_TRUE(EqualTo<std::string>()(str_b, str_b));
+    EXPECT_TRUE(EqualTo<std::string>()(str_b, cstr_b));
+    EXPECT_TRUE(EqualTo<std::string>()(str_b, sv_b));
+    EXPECT_TRUE(EqualTo<std::string>()(str_b, str_b));
+    EXPECT_TRUE(EqualTo<std::string>()(cstr_b, str_b));
+    EXPECT_TRUE(EqualTo<std::string>()(sv_b, str_b));
+}
+
 }  // namespace
 }  // namespace tint::utils
diff --git a/src/tint/utils/hashmap.h b/src/tint/utils/hashmap.h
index 6a5f3c5..98317c9 100644
--- a/src/tint/utils/hashmap.h
+++ b/src/tint/utils/hashmap.h
@@ -31,11 +31,14 @@
           typename VALUE,
           size_t N,
           typename HASH = Hasher<KEY>,
-          typename EQUAL = std::equal_to<KEY>>
+          typename EQUAL = EqualTo<KEY>>
 class Hashmap : public HashmapBase<KEY, VALUE, N, HASH, EQUAL> {
     using Base = HashmapBase<KEY, VALUE, N, HASH, EQUAL>;
     using PutMode = typename Base::PutMode;
 
+    template <typename T>
+    using ReferenceKeyType = traits::CharArrayToCharPtr<std::remove_reference_t<T>>;
+
   public:
     /// The key type
     using Key = KEY;
@@ -51,7 +54,7 @@
     /// The value returned by the Reference reflects the current state of the Hashmap, and so the
     /// referenced value may change, or transition between valid or invalid based on the current
     /// state of the Hashmap.
-    template <bool IS_CONST>
+    template <bool IS_CONST, typename K>
     class ReferenceT {
         /// `const Value` if IS_CONST, or `Value` if !IS_CONST
         using T = std::conditional_t<IS_CONST, const Value, Value>;
@@ -89,24 +92,34 @@
         friend Hashmap;
 
         /// Constructor
-        ReferenceT(Map& map, const Key& key)
-            : map_(map), key_(key), cached_(nullptr), generation_(map.Generation() - 1) {}
+        template <typename K_ARG>
+        ReferenceT(Map& map, K_ARG&& key)
+            : map_(map),
+              key_(std::forward<K_ARG>(key)),
+              cached_(nullptr),
+              generation_(map.Generation() - 1) {}
 
         /// Constructor
-        ReferenceT(Map& map, const Key& key, T* value)
-            : map_(map), key_(key), cached_(value), generation_(map.Generation()) {}
+        template <typename K_ARG>
+        ReferenceT(Map& map, K_ARG&& key, T* value)
+            : map_(map),
+              key_(std::forward<K_ARG>(key)),
+              cached_(value),
+              generation_(map.Generation()) {}
 
         Map& map_;
-        const Key key_;
+        const K key_;
         mutable T* cached_ = nullptr;
         mutable size_t generation_ = 0;
     };
 
     /// A mutable reference returned by Find()
-    using Reference = ReferenceT</*IS_CONST*/ false>;
+    template <typename K>
+    using Reference = ReferenceT</*IS_CONST*/ false, K>;
 
     /// An immutable reference returned by Find()
-    using ConstReference = ReferenceT</*IS_CONST*/ true>;
+    template <typename K>
+    using ConstReference = ReferenceT</*IS_CONST*/ true, K>;
 
     /// Adds a value to the map, if the map does not already contain an entry with the key @p key.
     /// @param key the entry key.
@@ -129,7 +142,8 @@
     /// @param key the key to search for.
     /// @returns the value of the entry that is equal to `value`, or no value if the entry was not
     ///          found.
-    std::optional<Value> Get(const Key& key) const {
+    template <typename K>
+    std::optional<Value> Get(K&& key) const {
         if (auto [found, index] = this->IndexOf(key); found) {
             return this->slots_[index].entry->value;
         }
@@ -169,18 +183,24 @@
     /// @param key the entry's key value to search for.
     /// @returns the value of the entry.
     template <typename K>
-    Reference GetOrZero(K&& key) {
+    auto GetOrZero(K&& key) {
         auto res = Add(std::forward<K>(key), Value{});
-        return Reference(*this, key, res.value);
+        return Reference<ReferenceKeyType<K>>(*this, key, res.value);
     }
 
     /// @param key the key to search for.
     /// @returns a reference to the entry that is equal to the given value.
-    Reference Find(const Key& key) { return Reference(*this, key); }
+    template <typename K>
+    auto Find(K&& key) {
+        return Reference<ReferenceKeyType<K>>(*this, std::forward<K>(key));
+    }
 
     /// @param key the key to search for.
     /// @returns a reference to the entry that is equal to the given value.
-    ConstReference Find(const Key& key) const { return ConstReference(*this, key); }
+    template <typename K>
+    auto Find(K&& key) const {
+        return ConstReference<ReferenceKeyType<K>>(*this, std::forward<K>(key));
+    }
 
     /// @returns the keys of the map as a vector.
     /// @note the order of the returned vector is non-deterministic between compilers.
@@ -232,14 +252,16 @@
     }
 
   private:
-    Value* Lookup(const Key& key) {
+    template <typename K>
+    Value* Lookup(K&& key) {
         if (auto [found, index] = this->IndexOf(key); found) {
             return &this->slots_[index].entry->value;
         }
         return nullptr;
     }
 
-    const Value* Lookup(const Key& key) const {
+    template <typename K>
+    const Value* Lookup(K&& key) const {
         if (auto [found, index] = this->IndexOf(key); found) {
             return &this->slots_[index].entry->value;
         }
diff --git a/src/tint/utils/hashmap_base.h b/src/tint/utils/hashmap_base.h
index 12c1ed2..f31dcf2 100644
--- a/src/tint/utils/hashmap_base.h
+++ b/src/tint/utils/hashmap_base.h
@@ -106,7 +106,7 @@
           typename VALUE,
           size_t N,
           typename HASH = Hasher<KEY>,
-          typename EQUAL = std::equal_to<KEY>>
+          typename EQUAL = EqualTo<KEY>>
 class HashmapBase {
     static constexpr bool ValueIsVoid = std::is_same_v<VALUE, void>;
 
@@ -157,8 +157,9 @@
     /// A slot can either be empty or filled with a value. If the slot is empty, #hash and #distance
     /// will be zero.
     struct Slot {
-        bool Equals(size_t key_hash, const Key& key) const {
-            return key_hash == hash && EQUAL()(key, KeyOf(*entry));
+        template <typename K>
+        bool Equals(size_t key_hash, K&& key) const {
+            return key_hash == hash && EQUAL()(std::forward<K>(key), KeyOf(*entry));
         }
 
         /// The slot value. If this does not contain a value, then the slot is vacant.
@@ -502,8 +503,9 @@
     /// @param key the key to hash
     /// @returns a tuple holding the target slot index for the given value, and the hash of the
     /// value, respectively.
-    HashResult Hash(const Key& key) const {
-        size_t hash = HASH()(key);
+    template <typename K>
+    HashResult Hash(K&& key) const {
+        size_t hash = HASH()(std::forward<K>(key));
         size_t index = Wrap(hash);
         return {index, hash};
     }
@@ -512,7 +514,8 @@
     /// @param key the key to search for.
     /// @returns a tuple holding a boolean representing whether the key was found in the map, and
     /// if found, the index of the slot that holds the key.
-    std::tuple<bool, size_t> IndexOf(const Key& key) const {
+    template <typename K>
+    std::tuple<bool, size_t> IndexOf(K&& key) const {
         const auto hash = Hash(key);
         const auto count = slots_.Length();
         for (size_t distance = 0, index = hash.scan_start; distance < count; distance++) {
diff --git a/src/tint/utils/hashmap_test.cc b/src/tint/utils/hashmap_test.cc
index 6d993e2..f27875c 100644
--- a/src/tint/utils/hashmap_test.cc
+++ b/src/tint/utils/hashmap_test.cc
@@ -155,6 +155,134 @@
     EXPECT_FALSE(one);
 }
 
+TEST(Hashmap, StringKeys) {
+    Hashmap<std::string, int, 4> map;
+    EXPECT_FALSE(map.Find("zero"));
+    EXPECT_FALSE(map.Find(std::string("zero")));
+    EXPECT_FALSE(map.Find(std::string_view("zero")));
+
+    map.Add("three", 3);
+    auto three_cstr = map.Find("three");
+    auto three_str = map.Find(std::string("three"));
+    auto three_sv = map.Find(std::string_view("three"));
+    map.Add(std::string("two"), 2);
+    auto two_cstr = map.Find("two");
+    auto two_str = map.Find(std::string("two"));
+    auto two_sv = map.Find(std::string_view("two"));
+    map.Add("four", 4);
+    auto four_cstr = map.Find("four");
+    auto four_str = map.Find(std::string("four"));
+    auto four_sv = map.Find(std::string_view("four"));
+    map.Add(std::string("eight"), 8);
+    auto eight_cstr = map.Find("eight");
+    auto eight_str = map.Find(std::string("eight"));
+    auto eight_sv = map.Find(std::string_view("eight"));
+
+    ASSERT_TRUE(three_cstr);
+    ASSERT_TRUE(three_str);
+    ASSERT_TRUE(three_sv);
+    ASSERT_TRUE(two_cstr);
+    ASSERT_TRUE(two_str);
+    ASSERT_TRUE(two_sv);
+    ASSERT_TRUE(four_cstr);
+    ASSERT_TRUE(four_str);
+    ASSERT_TRUE(four_sv);
+    ASSERT_TRUE(eight_cstr);
+    ASSERT_TRUE(eight_str);
+    ASSERT_TRUE(eight_sv);
+
+    EXPECT_EQ(*three_cstr, 3);
+    EXPECT_EQ(*three_str, 3);
+    EXPECT_EQ(*three_sv, 3);
+    EXPECT_EQ(*two_cstr, 2);
+    EXPECT_EQ(*two_str, 2);
+    EXPECT_EQ(*two_sv, 2);
+    EXPECT_EQ(*four_cstr, 4);
+    EXPECT_EQ(*four_str, 4);
+    EXPECT_EQ(*four_sv, 4);
+    EXPECT_EQ(*eight_cstr, 8);
+    EXPECT_EQ(*eight_str, 8);
+    EXPECT_EQ(*eight_sv, 8);
+
+    map.Add("zero", 0);  // Note: Find called before Add() is okay!
+    auto zero_cstr = map.Find("zero");
+    auto zero_str = map.Find(std::string("zero"));
+    auto zero_sv = map.Find(std::string_view("zero"));
+
+    map.Add(std::string("five"), 5);
+    auto five_cstr = map.Find("five");
+    auto five_str = map.Find(std::string("five"));
+    auto five_sv = map.Find(std::string_view("five"));
+    map.Add("six", 6);
+    auto six_cstr = map.Find("six");
+    auto six_str = map.Find(std::string("six"));
+    auto six_sv = map.Find(std::string_view("six"));
+    map.Add("one", 1);
+    auto one_cstr = map.Find("one");
+    auto one_str = map.Find(std::string("one"));
+    auto one_sv = map.Find(std::string_view("one"));
+    map.Add(std::string("seven"), 7);
+    auto seven_cstr = map.Find("seven");
+    auto seven_str = map.Find(std::string("seven"));
+    auto seven_sv = map.Find(std::string_view("seven"));
+
+    ASSERT_TRUE(zero_cstr);
+    ASSERT_TRUE(zero_str);
+    ASSERT_TRUE(zero_sv);
+    ASSERT_TRUE(three_cstr);
+    ASSERT_TRUE(three_str);
+    ASSERT_TRUE(three_sv);
+    ASSERT_TRUE(two_cstr);
+    ASSERT_TRUE(two_str);
+    ASSERT_TRUE(two_sv);
+    ASSERT_TRUE(four_cstr);
+    ASSERT_TRUE(four_str);
+    ASSERT_TRUE(four_sv);
+    ASSERT_TRUE(eight_cstr);
+    ASSERT_TRUE(eight_str);
+    ASSERT_TRUE(eight_sv);
+    ASSERT_TRUE(five_cstr);
+    ASSERT_TRUE(five_str);
+    ASSERT_TRUE(five_sv);
+    ASSERT_TRUE(six_cstr);
+    ASSERT_TRUE(six_str);
+    ASSERT_TRUE(six_sv);
+    ASSERT_TRUE(one_cstr);
+    ASSERT_TRUE(one_str);
+    ASSERT_TRUE(one_sv);
+    ASSERT_TRUE(seven_cstr);
+    ASSERT_TRUE(seven_str);
+    ASSERT_TRUE(seven_sv);
+
+    EXPECT_EQ(*zero_cstr, 0);
+    EXPECT_EQ(*zero_str, 0);
+    EXPECT_EQ(*zero_sv, 0);
+    EXPECT_EQ(*three_cstr, 3);
+    EXPECT_EQ(*three_str, 3);
+    EXPECT_EQ(*three_sv, 3);
+    EXPECT_EQ(*two_cstr, 2);
+    EXPECT_EQ(*two_str, 2);
+    EXPECT_EQ(*two_sv, 2);
+    EXPECT_EQ(*four_cstr, 4);
+    EXPECT_EQ(*four_str, 4);
+    EXPECT_EQ(*four_sv, 4);
+    EXPECT_EQ(*eight_cstr, 8);
+    EXPECT_EQ(*eight_str, 8);
+    EXPECT_EQ(*eight_sv, 8);
+    EXPECT_EQ(*five_cstr, 5);
+    EXPECT_EQ(*five_str, 5);
+    EXPECT_EQ(*five_sv, 5);
+    EXPECT_EQ(*six_cstr, 6);
+    EXPECT_EQ(*six_str, 6);
+    EXPECT_EQ(*six_sv, 6);
+    EXPECT_EQ(*one_cstr, 1);
+    EXPECT_EQ(*one_str, 1);
+    EXPECT_EQ(*one_sv, 1);
+    EXPECT_EQ(*seven_cstr, 7);
+    EXPECT_EQ(*seven_str, 7);
+    EXPECT_EQ(*seven_sv, 7);
+}
+
 TEST(Hashmap, Iterator) {
     using Map = Hashmap<int, std::string, 8>;
     using Entry = typename Map::Entry;
diff --git a/src/tint/utils/traits.h b/src/tint/utils/traits.h
index 525ed9b..711ea36 100644
--- a/src/tint/utils/traits.h
+++ b/src/tint/utils/traits.h
@@ -159,16 +159,16 @@
 
 namespace detail {
 /// Base template for IsTypeIn
-template <class T, class TypeList>
+template <typename T, typename TypeList>
 struct IsTypeIn;
 
 /// Specialization for IsTypeIn
-template <class T, template <class...> class TypeContainer, class... Ts>
+template <typename T, template <typename...> typename TypeContainer, typename... Ts>
 struct IsTypeIn<T, TypeContainer<Ts...>> : std::disjunction<std::is_same<T, Ts>...> {};
 }  // namespace detail
 
 /// Evaluates to true if T is one of the types in the TypeContainer's template arguments.
-/// Works for std::variant, std::tuple, std::pair, or any class template where all parameters are
+/// Works for std::variant, std::tuple, std::pair, or any typename template where all parameters are
 /// types.
 template <typename T, typename TypeContainer>
 static constexpr bool IsTypeIn = detail::IsTypeIn<T, TypeContainer>::value;
@@ -183,6 +183,32 @@
     std::is_same_v<Decay<T>, std::string> || std::is_same_v<Decay<T>, std::string_view> ||
     std::is_same_v<Decay<T>, const char*>;
 
+namespace detail {
+/// Helper for CharArrayToCharPtr
+template <typename T>
+struct CharArrayToCharPtrImpl {
+    /// Evaluates to T
+    using type = T;
+};
+/// Specialization of CharArrayToCharPtrImpl for `char[N]`
+template <size_t N>
+struct CharArrayToCharPtrImpl<char[N]> {
+    /// Evaluates to `char*`
+    using type = char*;
+};
+/// Specialization of CharArrayToCharPtrImpl for `const char[N]`
+template <size_t N>
+struct CharArrayToCharPtrImpl<const char[N]> {
+    /// Evaluates to `const char*`
+    using type = const char*;
+};
+}  // namespace detail
+
+/// Evaluates to `char*` or `const char*` if `T` is `char[N]` or `const char[N]`, respectively,
+/// otherwise T.
+template <typename T>
+using CharArrayToCharPtr = typename detail::CharArrayToCharPtrImpl<T>::type;
+
 }  // namespace tint::utils::traits
 
 #endif  // SRC_TINT_UTILS_TRAITS_H_
diff --git a/src/tint/utils/traits_test.cc b/src/tint/utils/traits_test.cc
index 578ed44..83a80f9 100644
--- a/src/tint/utils/traits_test.cc
+++ b/src/tint/utils/traits_test.cc
@@ -241,4 +241,9 @@
     static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, float>);
 }
 
+static_assert(std::is_same_v<char*, CharArrayToCharPtr<char[2]>>);
+static_assert(std::is_same_v<const char*, CharArrayToCharPtr<const char[2]>>);
+static_assert(std::is_same_v<int, CharArrayToCharPtr<int>>);
+static_assert(std::is_same_v<int[2], CharArrayToCharPtr<int[2]>>);
+
 }  // namespace tint::utils::traits
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 8fcdd3d..85644e2 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -170,7 +170,6 @@
     manager.Add<transform::PreservePadding>();  // Must come before DirectVariableAccess
 
     manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
-    manager.Add<transform::DirectVariableAccess>();
 
     manager.Add<transform::PromoteSideEffectsToDecl>();
 
@@ -203,9 +202,11 @@
         polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
         polyfills.workgroup_uniform_load = true;
         data.Add<transform::BuiltinPolyfill::Config>(polyfills);
-        manager.Add<transform::BuiltinPolyfill>();
+        manager.Add<transform::BuiltinPolyfill>();  // Must come before DirectVariableAccess
     }
 
+    manager.Add<transform::DirectVariableAccess>();
+
     if (!options.disable_workgroup_init) {
         // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
         // ZeroInitWorkgroupMemory may inject new builtin parameters.
@@ -2147,9 +2148,8 @@
             }
         },
         [&](Default) {
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "unhandled constant type: " + builder_.FriendlyName(constant->Type()));
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unhandled constant type: " + constant->Type()->FriendlyName());
         });
 }
 
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index ed86e9e..e8b697b 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -176,8 +176,6 @@
 
     manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
 
-    manager.Add<transform::DirectVariableAccess>();
-
     // LocalizeStructArrayAssignment must come after:
     // * SimplifyPointers, because it assumes assignment to arrays in structs are
     // done directly, not indirectly.
@@ -229,9 +227,11 @@
         polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
         polyfills.workgroup_uniform_load = true;
         data.Add<transform::BuiltinPolyfill::Config>(polyfills);
-        manager.Add<transform::BuiltinPolyfill>();
+        manager.Add<transform::BuiltinPolyfill>();  // Must come before DirectVariableAccess
     }
 
+    manager.Add<transform::DirectVariableAccess>();
+
     if (!options.disable_workgroup_init) {
         // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
         // ZeroInitWorkgroupMemory may inject new builtin parameters.
@@ -3482,9 +3482,8 @@
             return true;
         },
         [&](Default) {
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "unhandled constant type: " + builder_.FriendlyName(constant->Type()));
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unhandled constant type: " + constant->Type()->FriendlyName());
             return false;
         });
 }
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 14288ee..d955d78 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -1788,9 +1788,8 @@
             return true;
         },
         [&](Default) {
-            diagnostics_.add_error(
-                diag::System::Writer,
-                "unhandled constant type: " + builder_.FriendlyName(constant->Type()));
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unhandled constant type: " + constant->Type()->FriendlyName());
             return false;
         });
 }
diff --git a/src/tint/writer/spirv/binary_writer.cc b/src/tint/writer/spirv/binary_writer.cc
index 69ac353..4952bdb 100644
--- a/src/tint/writer/spirv/binary_writer.cc
+++ b/src/tint/writer/spirv/binary_writer.cc
@@ -28,9 +28,9 @@
 
 BinaryWriter::~BinaryWriter() = default;
 
-void BinaryWriter::WriteBuilder(Builder* builder) {
-    out_.reserve(builder->total_size());
-    builder->iterate([this](const Instruction& inst) { this->process_instruction(inst); });
+void BinaryWriter::WriteModule(const Module* module) {
+    out_.reserve(module->TotalSize());
+    module->Iterate([this](const Instruction& inst) { this->process_instruction(inst); });
 }
 
 void BinaryWriter::WriteInstruction(const Instruction& inst) {
diff --git a/src/tint/writer/spirv/binary_writer.h b/src/tint/writer/spirv/binary_writer.h
index e1e7f68..0b1c234 100644
--- a/src/tint/writer/spirv/binary_writer.h
+++ b/src/tint/writer/spirv/binary_writer.h
@@ -17,11 +17,11 @@
 
 #include <vector>
 
-#include "src/tint/writer/spirv/builder.h"
+#include "src/tint/writer/spirv/module.h"
 
 namespace tint::writer::spirv {
 
-/// Writer to convert from builder to SPIR-V binary
+/// Writer to convert from module to SPIR-V binary.
 class BinaryWriter {
   public:
     /// Constructor
@@ -32,11 +32,10 @@
     /// @param bound the bound to output
     void WriteHeader(uint32_t bound);
 
-    /// Writes the given builder data into a binary. Note, this does not emit
-    /// the SPIR-V header. You **must** call WriteHeader() before WriteBuilder()
-    /// if you want the SPIR-V to be emitted.
-    /// @param builder the builder to assemble from
-    void WriteBuilder(Builder* builder);
+    /// Writes the given module data into a binary. Note, this does not emit the SPIR-V header. You
+    /// **must** call WriteHeader() before WriteModule() if you want the SPIR-V to be emitted.
+    /// @param module the module to assemble from
+    void WriteModule(const Module* module);
 
     /// Writes the given instruction into the binary.
     /// @param inst the instruction to assemble
diff --git a/src/tint/writer/spirv/binary_writer_test.cc b/src/tint/writer/spirv/binary_writer_test.cc
index 11812cf..c043b25 100644
--- a/src/tint/writer/spirv/binary_writer_test.cc
+++ b/src/tint/writer/spirv/binary_writer_test.cc
@@ -33,11 +33,11 @@
 }
 
 TEST_F(BinaryWriterTest, Float) {
-    spirv::Builder& b = Build();
+    Module m;
 
-    b.push_annot(spv::Op::OpKill, {Operand(2.4f)});
+    m.PushAnnot(spv::Op::OpKill, {Operand(2.4f)});
     BinaryWriter bw;
-    bw.WriteBuilder(&b);
+    bw.WriteModule(&m);
 
     auto res = bw.result();
     ASSERT_EQ(res.size(), 2u);
@@ -47,11 +47,11 @@
 }
 
 TEST_F(BinaryWriterTest, Int) {
-    spirv::Builder& b = Build();
+    Module m;
 
-    b.push_annot(spv::Op::OpKill, {Operand(2u)});
+    m.PushAnnot(spv::Op::OpKill, {Operand(2u)});
     BinaryWriter bw;
-    bw.WriteBuilder(&b);
+    bw.WriteModule(&m);
 
     auto res = bw.result();
     ASSERT_EQ(res.size(), 2u);
@@ -59,11 +59,11 @@
 }
 
 TEST_F(BinaryWriterTest, String) {
-    spirv::Builder& b = Build();
+    Module m;
 
-    b.push_annot(spv::Op::OpKill, {Operand("my_string")});
+    m.PushAnnot(spv::Op::OpKill, {Operand("my_string")});
     BinaryWriter bw;
-    bw.WriteBuilder(&b);
+    bw.WriteModule(&m);
 
     auto res = bw.result();
     ASSERT_EQ(res.size(), 4u);
@@ -84,11 +84,11 @@
 }
 
 TEST_F(BinaryWriterTest, String_Multiple4Length) {
-    spirv::Builder& b = Build();
+    Module m;
 
-    b.push_annot(spv::Op::OpKill, {Operand("mystring")});
+    m.PushAnnot(spv::Op::OpKill, {Operand("mystring")});
     BinaryWriter bw;
-    bw.WriteBuilder(&b);
+    bw.WriteModule(&m);
 
     auto res = bw.result();
     ASSERT_EQ(res.size(), 4u);
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 972ba7c..add10aa 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -58,15 +58,6 @@
 
 const char kGLSLstd450[] = "GLSL.std.450";
 
-uint32_t size_of(const InstructionList& instructions) {
-    uint32_t size = 0;
-    for (const auto& inst : instructions) {
-        size += inst.word_length();
-    }
-
-    return size;
-}
-
 uint32_t pipeline_stage_to_execution_model(ast::PipelineStage stage) {
     SpvExecutionModel model = SpvExecutionModelVertex;
 
@@ -270,14 +261,13 @@
                                       builtin::Extension::kChromiumExperimentalPushConstant,
                                       builtin::Extension::kF16,
                                   })) {
-        error_ = builder_.Diagnostics().str();
         return false;
     }
 
-    push_capability(SpvCapabilityShader);
+    module_.PushCapability(SpvCapabilityShader);
 
-    push_memory_model(spv::Op::OpMemoryModel,
-                      {U32Operand(SpvAddressingModelLogical), U32Operand(SpvMemoryModelGLSL450)});
+    module_.PushMemoryModel(spv::Op::OpMemoryModel, {U32Operand(SpvAddressingModelLogical),
+                                                     U32Operand(SpvMemoryModelGLSL450)});
 
     for (auto ext : builder_.Sem().Module()->Extensions()) {
         GenerateExtension(ext);
@@ -309,7 +299,8 @@
 uint32_t Builder::LookupVariableID(const sem::Variable* var) {
     auto it = var_to_id_.find(var);
     if (it == var_to_id_.end()) {
-        error_ = "unable to find ID for variable: " + var->Declaration()->name->symbol.Name();
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unable to find ID for variable: " + var->Declaration()->name->symbol.Name();
         return 0;
     }
     return it->second;
@@ -325,85 +316,21 @@
 }
 
 Operand Builder::result_op() {
-    return Operand(next_id());
-}
-
-uint32_t Builder::total_size() const {
-    // The 5 covers the magic, version, generator, id bound and reserved.
-    uint32_t size = 5;
-
-    size += size_of(capabilities_);
-    size += size_of(extensions_);
-    size += size_of(ext_imports_);
-    size += size_of(memory_model_);
-    size += size_of(entry_points_);
-    size += size_of(execution_modes_);
-    size += size_of(debug_);
-    size += size_of(annotations_);
-    size += size_of(types_);
-    for (const auto& func : functions_) {
-        size += func.word_length();
-    }
-
-    return size;
-}
-
-void Builder::iterate(std::function<void(const Instruction&)> cb) const {
-    for (const auto& inst : capabilities_) {
-        cb(inst);
-    }
-    for (const auto& inst : extensions_) {
-        cb(inst);
-    }
-    for (const auto& inst : ext_imports_) {
-        cb(inst);
-    }
-    for (const auto& inst : memory_model_) {
-        cb(inst);
-    }
-    for (const auto& inst : entry_points_) {
-        cb(inst);
-    }
-    for (const auto& inst : execution_modes_) {
-        cb(inst);
-    }
-    for (const auto& inst : debug_) {
-        cb(inst);
-    }
-    for (const auto& inst : annotations_) {
-        cb(inst);
-    }
-    for (const auto& inst : types_) {
-        cb(inst);
-    }
-    for (const auto& func : functions_) {
-        func.iterate(cb);
-    }
-}
-
-void Builder::push_capability(uint32_t cap) {
-    if (capability_set_.count(cap) == 0) {
-        capability_set_.insert(cap);
-        capabilities_.push_back(Instruction{spv::Op::OpCapability, {Operand(cap)}});
-    }
-}
-
-void Builder::push_extension(const char* extension) {
-    extensions_.push_back(Instruction{spv::Op::OpExtension, {Operand(extension)}});
+    return Operand(module_.NextId());
 }
 
 bool Builder::GenerateExtension(builtin::Extension extension) {
     switch (extension) {
         case builtin::Extension::kChromiumExperimentalDp4A:
-            push_extension("SPV_KHR_integer_dot_product");
-            push_capability(SpvCapabilityDotProductKHR);
-            push_capability(SpvCapabilityDotProductInput4x8BitPackedKHR);
+            module_.PushExtension("SPV_KHR_integer_dot_product");
+            module_.PushCapability(SpvCapabilityDotProductKHR);
+            module_.PushCapability(SpvCapabilityDotProductInput4x8BitPackedKHR);
             break;
         case builtin::Extension::kF16:
-            push_capability(SpvCapabilityFloat16);
-            push_capability(SpvCapabilityUniformAndStorageBuffer16BitAccess);
-            push_capability(SpvCapabilityStorageBuffer16BitAccess);
-            push_capability(SpvCapabilityStorageInputOutput16);
+            module_.PushCapability(SpvCapabilityFloat16);
+            module_.PushCapability(SpvCapabilityUniformAndStorageBuffer16BitAccess);
+            module_.PushCapability(SpvCapabilityStorageBuffer16BitAccess);
+            module_.PushCapability(SpvCapabilityStorageInputOutput16);
             break;
         default:
             return false;
@@ -447,7 +374,7 @@
 
 bool Builder::GenerateBreakStatement(const ast::BreakStatement*) {
     if (merge_stack_.empty()) {
-        error_ = "Attempted to break without a merge block";
+        TINT_ICE(Writer, builder_.Diagnostics()) << "Attempted to break without a merge block";
         return false;
     }
     if (!push_function_inst(spv::Op::OpBranch, {Operand(merge_stack_.back())})) {
@@ -471,7 +398,8 @@
 
 bool Builder::GenerateContinueStatement(const ast::ContinueStatement*) {
     if (continue_stack_.empty()) {
-        error_ = "Attempted to continue without a continue block";
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "Attempted to continue without a continue block";
         return false;
     }
     if (!push_function_inst(spv::Op::OpBranch, {Operand(continue_stack_.back())})) {
@@ -493,7 +421,7 @@
 bool Builder::GenerateEntryPoint(const ast::Function* func, uint32_t id) {
     auto stage = pipeline_stage_to_execution_model(func->PipelineStage());
     if (stage == SpvExecutionModelMax) {
-        error_ = "Unknown pipeline stage provided";
+        TINT_ICE(Writer, builder_.Diagnostics()) << "Unknown pipeline stage provided";
         return false;
     }
 
@@ -510,14 +438,14 @@
 
         uint32_t var_id = LookupVariableID(var);
         if (var_id == 0) {
-            error_ =
-                "unable to find ID for global variable: " + var->Declaration()->name->symbol.Name();
+            TINT_ICE(Writer, builder_.Diagnostics()) << "unable to find ID for global variable: " +
+                                                            var->Declaration()->name->symbol.Name();
             return false;
         }
 
         operands.push_back(Operand(var_id));
     }
-    push_entry_point(spv::Op::OpEntryPoint, operands);
+    module_.PushEntryPoint(spv::Op::OpEntryPoint, operands);
 
     return true;
 }
@@ -527,19 +455,19 @@
 
     // WGSL fragment shader origin is upper left
     if (func->PipelineStage() == ast::PipelineStage::kFragment) {
-        push_execution_mode(spv::Op::OpExecutionMode,
-                            {Operand(id), U32Operand(SpvExecutionModeOriginUpperLeft)});
+        module_.PushExecutionMode(spv::Op::OpExecutionMode,
+                                  {Operand(id), U32Operand(SpvExecutionModeOriginUpperLeft)});
     } else if (func->PipelineStage() == ast::PipelineStage::kCompute) {
         auto& wgsize = func_sem->WorkgroupSize();
 
         // Check if the workgroup_size uses pipeline-overridable constants.
         if (!wgsize[0].has_value() || !wgsize[1].has_value() || !wgsize[2].has_value()) {
-            error_ =
-                "override-expressions should have been removed with the SubstituteOverride "
-                "transform";
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "override-expressions should have been removed with the SubstituteOverride "
+                   "transform";
             return false;
         }
-        push_execution_mode(
+        module_.PushExecutionMode(
             spv::Op::OpExecutionMode,
             {Operand(id), U32Operand(SpvExecutionModeLocalSize),  //
              Operand(wgsize[0].value()), Operand(wgsize[1].value()), Operand(wgsize[2].value())});
@@ -548,8 +476,8 @@
     for (auto it : func_sem->TransitivelyReferencedBuiltinVariables()) {
         auto builtin = builder_.Sem().Get(it.second)->Value();
         if (builtin == builtin::BuiltinValue::kFragDepth) {
-            push_execution_mode(spv::Op::OpExecutionMode,
-                                {Operand(id), U32Operand(SpvExecutionModeDepthReplacing)});
+            module_.PushExecutionMode(spv::Op::OpExecutionMode,
+                                      {Operand(id), U32Operand(SpvExecutionModeDepthReplacing)});
         }
     }
 
@@ -579,7 +507,8 @@
         [&](const ast::LiteralExpression* l) { return GenerateLiteralIfNeeded(l); },
         [&](const ast::UnaryOpExpression* u) { return GenerateUnaryOpExpression(u); },
         [&](Default) {
-            error_ = "unknown expression type: " + std::string(expr->TypeInfo().name);
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "unknown expression type: " + std::string(expr->TypeInfo().name);
             return 0;
         });
 }
@@ -599,7 +528,7 @@
     auto func_op = result_op();
     auto func_id = std::get<uint32_t>(func_op);
 
-    push_debug(spv::Op::OpName, {Operand(func_id), Operand(func_ast->name->symbol.Name())});
+    module_.PushDebug(spv::Op::OpName, {Operand(func_id), Operand(func_ast->name->symbol.Name())});
 
     auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
     if (ret_id == 0) {
@@ -623,15 +552,18 @@
             return false;
         }
 
-        push_debug(spv::Op::OpName,
-                   {Operand(param_id), Operand(param->Declaration()->name->symbol.Name())});
+        module_.PushDebug(spv::Op::OpName,
+                          {Operand(param_id), Operand(param->Declaration()->name->symbol.Name())});
         params.push_back(
             Instruction{spv::Op::OpFunctionParameter, {Operand(param_type_id), param_op}});
 
         RegisterVariable(param, param_id);
     }
 
-    push_function(Function{definition_inst, result_op(), std::move(params)});
+    // Start a new function.
+    current_function_ = Function{definition_inst, result_op(), std::move(params)};
+    current_label_id_ = current_function_.label_id();
+    TINT_DEFER(current_function_ = Function());
 
     for (auto* stmt : func_ast->body->statements) {
         if (!GenerateStatement(stmt)) {
@@ -659,6 +591,9 @@
 
     func_symbol_to_id_[func_ast->name->symbol] = func_id;
 
+    // Add the function to the module.
+    module_.PushFunction(std::move(current_function_));
+
     return true;
 }
 
@@ -681,7 +616,7 @@
             ops.push_back(Operand(param_type_id));
         }
 
-        push_type(spv::Op::OpTypeFunction, std::move(ops));
+        module_.PushType(spv::Op::OpTypeFunction, std::move(ops));
         return func_type_id;
     });
 }
@@ -705,7 +640,7 @@
 
     if (v->Is<ast::Let>()) {
         if (!v->initializer) {
-            error_ = "missing initializer for let";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "missing initializer for let";
             return false;
         }
         RegisterVariable(sem, init_id);
@@ -721,7 +656,7 @@
         return false;
     }
 
-    push_debug(spv::Op::OpName, {Operand(var_id), Operand(v->name->symbol.Name())});
+    module_.PushDebug(spv::Op::OpName, {Operand(var_id), Operand(v->name->symbol.Name())});
 
     // TODO(dsinclair) We could detect if the initializer is fully const and emit
     // an initializer value for the variable instead of doing the OpLoad.
@@ -782,7 +717,7 @@
         return false;
     }
 
-    push_debug(spv::Op::OpName, {Operand(var_id), Operand(v->name->symbol.Name())});
+    module_.PushDebug(spv::Op::OpName, {Operand(var_id), Operand(v->name->symbol.Name())});
 
     OperandList ops = {Operand(type_id), result, U32Operand(ConvertAddressSpace(sc))};
 
@@ -795,12 +730,12 @@
             auto access = st ? st->access() : sem->Access();
             switch (access) {
                 case builtin::Access::kWrite:
-                    push_annot(spv::Op::OpDecorate,
-                               {Operand(var_id), U32Operand(SpvDecorationNonReadable)});
+                    module_.PushAnnot(spv::Op::OpDecorate,
+                                      {Operand(var_id), U32Operand(SpvDecorationNonReadable)});
                     break;
                 case builtin::Access::kRead:
-                    push_annot(spv::Op::OpDecorate,
-                               {Operand(var_id), U32Operand(SpvDecorationNonWritable)});
+                    module_.PushAnnot(spv::Op::OpDecorate,
+                                      {Operand(var_id), U32Operand(SpvDecorationNonWritable)});
                     break;
                 case builtin::Access::kUndefined:
                 case builtin::Access::kReadWrite:
@@ -826,21 +761,22 @@
         }
     }
 
-    push_type(spv::Op::OpVariable, std::move(ops));
+    module_.PushType(spv::Op::OpVariable, std::move(ops));
 
     for (auto* attr : v->attributes) {
         bool ok = Switch(
             attr,
             [&](const ast::BuiltinAttribute* builtin_attr) {
                 auto builtin = builder_.Sem().Get(builtin_attr)->Value();
-                push_annot(spv::Op::OpDecorate,
-                           {Operand(var_id), U32Operand(SpvDecorationBuiltIn),
-                            U32Operand(ConvertBuiltin(builtin, sem->AddressSpace()))});
+                module_.PushAnnot(spv::Op::OpDecorate,
+                                  {Operand(var_id), U32Operand(SpvDecorationBuiltIn),
+                                   U32Operand(ConvertBuiltin(builtin, sem->AddressSpace()))});
                 return true;
             },
             [&](const ast::LocationAttribute*) {
-                push_annot(spv::Op::OpDecorate, {Operand(var_id), U32Operand(SpvDecorationLocation),
-                                                 Operand(sem->Location().value())});
+                module_.PushAnnot(spv::Op::OpDecorate,
+                                  {Operand(var_id), U32Operand(SpvDecorationLocation),
+                                   Operand(sem->Location().value())});
                 return true;
             },
             [&](const ast::InterpolateAttribute* interpolate) {
@@ -860,19 +796,20 @@
                 return true;
             },
             [&](const ast::InvariantAttribute*) {
-                push_annot(spv::Op::OpDecorate,
-                           {Operand(var_id), U32Operand(SpvDecorationInvariant)});
+                module_.PushAnnot(spv::Op::OpDecorate,
+                                  {Operand(var_id), U32Operand(SpvDecorationInvariant)});
                 return true;
             },
             [&](const ast::BindingAttribute*) {
                 auto bp = sem->BindingPoint();
-                push_annot(spv::Op::OpDecorate, {Operand(var_id), U32Operand(SpvDecorationBinding),
-                                                 Operand(bp->binding)});
+                module_.PushAnnot(
+                    spv::Op::OpDecorate,
+                    {Operand(var_id), U32Operand(SpvDecorationBinding), Operand(bp->binding)});
                 return true;
             },
             [&](const ast::GroupAttribute*) {
                 auto bp = sem->BindingPoint();
-                push_annot(
+                module_.PushAnnot(
                     spv::Op::OpDecorate,
                     {Operand(var_id), U32Operand(SpvDecorationDescriptorSet), Operand(bp->group)});
                 return true;
@@ -884,7 +821,7 @@
                 return true;  // ignored
             },
             [&](Default) {
-                error_ = "unknown attribute";
+                TINT_ICE(Writer, builder_.Diagnostics()) << "unknown attribute";
                 return false;
             });
         if (!ok) {
@@ -1119,7 +1056,8 @@
                 return GenerateMemberAccessor(member, &info);
             },
             [&](Default) {
-                error_ = "invalid accessor in list: " + std::string(accessor->TypeInfo().name);
+                TINT_ICE(Writer, builder_.Diagnostics())
+                    << "invalid accessor in list: " + std::string(accessor->TypeInfo().name);
                 return false;
             });
         if (!ok) {
@@ -1157,7 +1095,8 @@
             return LookupVariableID(user->Variable());
         }
     }
-    error_ = "identifier '" + expr->identifier->symbol.Name() + "' does not resolve to a variable";
+    TINT_ICE(Writer, builder_.Diagnostics())
+        << "identifier '" + expr->identifier->symbol.Name() + "' does not resolve to a variable";
     return 0;
 }
 
@@ -1232,7 +1171,7 @@
     auto result = result_op();
     auto id = std::get<uint32_t>(result);
 
-    push_ext_import(spv::Op::OpExtInstImport, {result, Operand(kGLSLstd450)});
+    module_.PushExtImport(spv::Op::OpExtInstImport, {result, Operand(kGLSLstd450)});
 
     // Remember it for later.
     import_name_to_id_[kGLSLstd450] = id;
@@ -1251,7 +1190,7 @@
             return GenerateValueConstructorOrConversion(call, var);
         }
     }
-    error_ = "unknown constructor expression";
+    TINT_ICE(Writer, builder_.Diagnostics()) << "unknown constructor expression";
     return 0;
 }
 
@@ -1404,9 +1343,9 @@
                     if (idx_id == 0) {
                         return 0;
                     }
-                    push_type(spv::Op::OpSpecConstantOp,
-                              {Operand(value_type_id), extract, U32Operand(SpvOpCompositeExtract),
-                               Operand(id), Operand(idx_id)});
+                    module_.PushType(spv::Op::OpSpecConstantOp, {Operand(value_type_id), extract,
+                                                                 U32Operand(SpvOpCompositeExtract),
+                                                                 Operand(id), Operand(idx_id)});
 
                     result_is_spec_composite = true;
                 }
@@ -1414,7 +1353,7 @@
                 ops.push_back(Operand(extract_id));
             }
         } else {
-            error_ = "Unhandled type cast value type";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "Unhandled type cast value type";
             return 0;
         }
     }
@@ -1438,9 +1377,9 @@
         ops[kOpsResultIdx] = result;
 
         if (result_is_spec_composite) {
-            push_type(spv::Op::OpSpecConstantComposite, ops);
+            module_.PushType(spv::Op::OpSpecConstantComposite, ops);
         } else if (result_is_constant_composite) {
-            push_type(spv::Op::OpConstantComposite, ops);
+            module_.PushType(spv::Op::OpConstantComposite, ops);
         } else {
             if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
                 return 0;
@@ -1554,7 +1493,8 @@
             zero_id = GenerateConstantIfNeeded(ScalarConstant::I32(0));
             one_id = GenerateConstantIfNeeded(ScalarConstant::I32(1));
         } else {
-            error_ = "invalid destination type for bool conversion";
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "invalid destination type for bool conversion";
             return false;
         }
         if (auto* to_vec = to_type->As<type::Vector>()) {
@@ -1590,9 +1530,9 @@
     }
 
     if (op == spv::Op::OpNop) {
-        error_ =
-            "unable to determine conversion type for cast, from: " + from_type->FriendlyName() +
-            " to: " + to_type->FriendlyName();
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unable to determine conversion type for cast, from: " + from_type->FriendlyName() +
+                   " to: " + to_type->FriendlyName();
         return 0;
     }
 
@@ -1637,9 +1577,9 @@
                     return;
             }
         },
-        [&](Default) { error_ = "unknown literal type"; });
+        [&](Default) { TINT_ICE(Writer, builder_.Diagnostics()) << "unknown literal type"; });
 
-    if (!error_.empty()) {
+    if (has_error()) {
         return false;
     }
 
@@ -1674,13 +1614,13 @@
         }
 
         auto& global_scope = scope_stack_[0];
-        return utils::GetOrCreate(global_scope.type_init_to_id_, OperandListKey{ops},
-                                  [&]() -> uint32_t {
-                                      auto result = result_op();
-                                      ops[kOpsResultIdx] = result;
-                                      push_type(spv::Op::OpConstantComposite, std::move(ops));
-                                      return std::get<uint32_t>(result);
-                                  });
+        return utils::GetOrCreate(
+            global_scope.type_init_to_id_, OperandListKey{ops}, [&]() -> uint32_t {
+                auto result = result_op();
+                ops[kOpsResultIdx] = result;
+                module_.PushType(spv::Op::OpConstantComposite, std::move(ops));
+                return std::get<uint32_t>(result);
+            });
     };
 
     return Switch(
@@ -1710,14 +1650,15 @@
         [&](const type::Array* a) {
             auto count = a->ConstantCount();
             if (!count) {
-                error_ = type::Array::kErrExpectedConstantCount;
+                TINT_ICE(Writer, builder_.Diagnostics()) << type::Array::kErrExpectedConstantCount;
                 return static_cast<uint32_t>(0);
             }
             return composite(count.value());
         },
         [&](const type::Struct* s) { return composite(s->Members().Length()); },
         [&](Default) {
-            error_ = "unhandled constant type: " + builder_.FriendlyName(ty);
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "unhandled constant type: " + ty->FriendlyName();
             return 0;
         });
 }
@@ -1762,28 +1703,31 @@
 
     switch (constant.kind) {
         case ScalarConstant::Kind::kU32: {
-            push_type(spv::Op::OpConstant, {Operand(type_id), result, Operand(constant.value.u32)});
+            module_.PushType(spv::Op::OpConstant,
+                             {Operand(type_id), result, Operand(constant.value.u32)});
             break;
         }
         case ScalarConstant::Kind::kI32: {
-            push_type(spv::Op::OpConstant,
-                      {Operand(type_id), result, U32Operand(constant.value.i32)});
+            module_.PushType(spv::Op::OpConstant,
+                             {Operand(type_id), result, U32Operand(constant.value.i32)});
             break;
         }
         case ScalarConstant::Kind::kF32: {
-            push_type(spv::Op::OpConstant, {Operand(type_id), result, Operand(constant.value.f32)});
+            module_.PushType(spv::Op::OpConstant,
+                             {Operand(type_id), result, Operand(constant.value.f32)});
             break;
         }
         case ScalarConstant::Kind::kF16: {
-            push_type(spv::Op::OpConstant, {Operand(type_id), result,
-                                            U32Operand(constant.value.f16.bits_representation)});
+            module_.PushType(
+                spv::Op::OpConstant,
+                {Operand(type_id), result, U32Operand(constant.value.f16.bits_representation)});
             break;
         }
         case ScalarConstant::Kind::kBool: {
             if (constant.value.b) {
-                push_type(spv::Op::OpConstantTrue, {Operand(type_id), result});
+                module_.PushType(spv::Op::OpConstantTrue, {Operand(type_id), result});
             } else {
-                push_type(spv::Op::OpConstantFalse, {Operand(type_id), result});
+                module_.PushType(spv::Op::OpConstantFalse, {Operand(type_id), result});
             }
             break;
         }
@@ -1802,7 +1746,7 @@
     return utils::GetOrCreate(const_null_to_id_, type, [&] {
         auto result = result_op();
 
-        push_type(spv::Op::OpConstantNull, {Operand(type_id), result});
+        module_.PushType(spv::Op::OpConstantNull, {Operand(type_id), result});
 
         return std::get<uint32_t>(result);
     });
@@ -1825,7 +1769,7 @@
         for (uint32_t i = 0; i < type->Width(); i++) {
             ops.push_back(Operand(value_id));
         }
-        push_type(spv::Op::OpConstantComposite, ops);
+        module_.PushType(spv::Op::OpConstantComposite, ops);
 
         const_splat_to_id_[key] = result_id;
         return result_id;
@@ -2025,7 +1969,8 @@
 
         // This should already have been validated by resolver
         if (lhs_mat->rows() != rhs_mat->rows() || lhs_mat->columns() != rhs_mat->columns()) {
-            error_ = "matrices must have same dimensionality for add or subtract";
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "matrices must have same dimensionality for add or subtract";
             return 0;
         }
 
@@ -2070,7 +2015,7 @@
         } else if (lhs_is_bool_or_vec) {
             op = spv::Op::OpLogicalAnd;
         } else {
-            error_ = "invalid and expression";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "invalid and expression";
             return 0;
         }
     } else if (expr->IsAdd()) {
@@ -2091,7 +2036,7 @@
         } else if (lhs_is_integer_or_vec) {
             op = spv::Op::OpIEqual;
         } else {
-            error_ = "invalid equal expression";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "invalid equal expression";
             return 0;
         }
     } else if (expr->IsGreaterThan()) {
@@ -2171,7 +2116,7 @@
             // float matrix * matrix
             op = spv::Op::OpMatrixTimesMatrix;
         } else {
-            error_ = "invalid multiply expression";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "invalid multiply expression";
             return 0;
         }
     } else if (expr->IsNotEqual()) {
@@ -2182,7 +2127,7 @@
         } else if (lhs_is_integer_or_vec) {
             op = spv::Op::OpINotEqual;
         } else {
-            error_ = "invalid not-equal expression";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "invalid not-equal expression";
             return 0;
         }
     } else if (expr->IsOr()) {
@@ -2191,7 +2136,7 @@
         } else if (lhs_is_bool_or_vec) {
             op = spv::Op::OpLogicalOr;
         } else {
-            error_ = "invalid and expression";
+            TINT_ICE(Writer, builder_.Diagnostics()) << "invalid and expression";
             return 0;
         }
     } else if (expr->IsShiftLeft()) {
@@ -2206,7 +2151,7 @@
     } else if (expr->IsXor()) {
         op = spv::Op::OpBitwiseXor;
     } else {
-        error_ = "unknown binary expression";
+        TINT_ICE(Writer, builder_.Diagnostics()) << "unknown binary expression";
         return 0;
     }
 
@@ -2267,7 +2212,8 @@
 
     auto func_id = func_symbol_to_id_[ident->symbol];
     if (func_id == 0) {
-        error_ = "unable to find called function: " + ident->symbol.Name();
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unable to find called function: " + ident->symbol.Name();
         return 0;
     }
     ops.push_back(Operand(func_id));
@@ -2297,11 +2243,11 @@
     }
 
     if (builtin->IsFineDerivative() || builtin->IsCoarseDerivative()) {
-        push_capability(SpvCapabilityDerivativeControl);
+        module_.PushCapability(SpvCapabilityDerivativeControl);
     }
 
     if (builtin->IsImageQuery()) {
-        push_capability(SpvCapabilityImageQuery);
+        module_.PushCapability(SpvCapabilityImageQuery);
     }
 
     if (builtin->IsTexture()) {
@@ -2371,16 +2317,18 @@
         case builtin::Function::kArrayLength: {
             auto* address_of = call->Arguments()[0]->Declaration()->As<ast::UnaryOpExpression>();
             if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
-                error_ = "arrayLength() expected pointer to member access, got " +
-                         std::string(address_of->TypeInfo().name);
+                TINT_ICE(Writer, builder_.Diagnostics())
+                    << "arrayLength() expected pointer to member access, got " +
+                           std::string(address_of->TypeInfo().name);
                 return 0;
             }
             auto* array_expr = address_of->expr;
 
             auto* accessor = array_expr->As<ast::MemberAccessorExpression>();
             if (!accessor) {
-                error_ = "arrayLength() expected pointer to member access, got pointer to " +
-                         std::string(array_expr->TypeInfo().name);
+                TINT_ICE(Writer, builder_.Diagnostics())
+                    << "arrayLength() expected pointer to member access, got pointer to " +
+                           std::string(array_expr->TypeInfo().name);
                 return 0;
             }
 
@@ -2392,7 +2340,8 @@
 
             auto* type = TypeOf(accessor->object)->UnwrapRef();
             if (!type->Is<type::Struct>()) {
-                error_ = "invalid type (" + type->FriendlyName() + ") for runtime array length";
+                TINT_ICE(Writer, builder_.Diagnostics())
+                    << "invalid type (" + type->FriendlyName() + ") for runtime array length";
                 return 0;
             }
             // Runtime array must be the last member in the structure
@@ -2589,7 +2538,8 @@
         default: {
             auto inst_id = builtin_to_glsl_method(builtin);
             if (inst_id == 0) {
-                error_ = "unknown method " + std::string(builtin->str());
+                TINT_ICE(Writer, builder_.Diagnostics())
+                    << "unknown method " + std::string(builtin->str());
                 return 0;
             }
             glsl_std450(inst_id);
@@ -2598,7 +2548,8 @@
     }
 
     if (op == spv::Op::OpNop) {
-        error_ = "unable to determine operator for: " + std::string(builtin->str());
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unable to determine operator for: " + std::string(builtin->str());
         return 0;
     }
 
@@ -2789,7 +2740,7 @@
             uint32_t spirv_dims = 0;
             switch (texture_type->dim()) {
                 case type::TextureDimension::kNone:
-                    error_ = "texture dimension is kNone";
+                    TINT_ICE(Writer, builder_.Diagnostics()) << "texture dimension is kNone";
                     return false;
                 case type::TextureDimension::k1d:
                 case type::TextureDimension::k2d:
@@ -2826,7 +2777,7 @@
             uint32_t spirv_dims = 0;
             switch (texture_type->dim()) {
                 default:
-                    error_ = "texture is not arrayed";
+                    TINT_ICE(Writer, builder_.Diagnostics()) << "texture is not arrayed";
                     return false;
                 case type::TextureDimension::k2dArray:
                 case type::TextureDimension::kCubeArray:
@@ -3016,7 +2967,8 @@
     }
 
     if (op == spv::Op::OpNop) {
-        error_ = "unable to determine operator for: " + std::string(builtin->str());
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unable to determine operator for: " + std::string(builtin->str());
         return false;
     }
 
@@ -3046,8 +2998,8 @@
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
     } else {
-        error_ = "unexpected barrier builtin type ";
-        error_ += builtin::str(builtin->Type());
+        TINT_ICE(Writer, builder_.Diagnostics())
+            << "unexpected barrier builtin type " << builtin::str(builtin->Type());
         return false;
     }
 
@@ -3290,7 +3242,8 @@
             // We need to create the sampled image type and cache the result.
             auto sampled_image_type = result_op();
             auto texture_type_id = GenerateTypeIfNeeded(texture_type);
-            push_type(spv::Op::OpTypeSampledImage, {sampled_image_type, Operand(texture_type_id)});
+            module_.PushType(spv::Op::OpTypeSampledImage,
+                             {sampled_image_type, Operand(texture_type_id)});
             return std::get<uint32_t>(sampled_image_type);
         });
 
@@ -3357,7 +3310,7 @@
 
     // if there are no more else statements we branch on false to the merge
     // block otherwise we branch to the false block
-    auto false_block_id = else_stmt ? next_id() : merge_block_id;
+    auto false_block_id = else_stmt ? module_.NextId() : merge_block_id;
 
     if (!push_function_inst(spv::Op::OpBranchConditional,
                             {Operand(cond_id), Operand(true_block_id), Operand(false_block_id)})) {
@@ -3620,7 +3573,8 @@
             return true;  // Not emitted
         },
         [&](Default) {
-            error_ = "unknown statement type: " + std::string(stmt->TypeInfo().name);
+            TINT_ICE(Writer, builder_.Diagnostics())
+                << "unknown statement type: " + std::string(stmt->TypeInfo().name);
             return false;
         });
 }
@@ -3631,7 +3585,7 @@
 
 uint32_t Builder::GenerateTypeIfNeeded(const type::Type* type) {
     if (type == nullptr) {
-        error_ = "attempting to generate type from null type";
+        TINT_ICE(Writer, builder_.Diagnostics()) << "attempting to generate type from null type";
         return 0;
     }
 
@@ -3677,19 +3631,19 @@
                 return GenerateArrayType(arr, result);
             },
             [&](const type::Bool*) {
-                push_type(spv::Op::OpTypeBool, {result});
+                module_.PushType(spv::Op::OpTypeBool, {result});
                 return true;
             },
             [&](const type::F32*) {
-                push_type(spv::Op::OpTypeFloat, {result, Operand(32u)});
+                module_.PushType(spv::Op::OpTypeFloat, {result, Operand(32u)});
                 return true;
             },
             [&](const type::F16*) {
-                push_type(spv::Op::OpTypeFloat, {result, Operand(16u)});
+                module_.PushType(spv::Op::OpTypeFloat, {result, Operand(16u)});
                 return true;
             },
             [&](const type::I32*) {
-                push_type(spv::Op::OpTypeInt, {result, Operand(32u), Operand(1u)});
+                module_.PushType(spv::Op::OpTypeInt, {result, Operand(32u), Operand(1u)});
                 return true;
             },
             [&](const type::Matrix* mat) {  //
@@ -3705,14 +3659,14 @@
                 return GenerateStructType(str, result);
             },
             [&](const type::U32*) {
-                push_type(spv::Op::OpTypeInt, {result, Operand(32u), Operand(0u)});
+                module_.PushType(spv::Op::OpTypeInt, {result, Operand(32u), Operand(0u)});
                 return true;
             },
             [&](const type::Vector* vec) {  //
                 return GenerateVectorType(vec, result);
             },
             [&](const type::Void*) {
-                push_type(spv::Op::OpTypeVoid, {result});
+                module_.PushType(spv::Op::OpTypeVoid, {result});
                 return true;
             },
             [&](const type::StorageTexture* tex) {
@@ -3734,7 +3688,7 @@
             },
             [&](const type::Texture* tex) { return GenerateTextureType(tex, result); },
             [&](const type::Sampler* s) {
-                push_type(spv::Op::OpTypeSampler, {result});
+                module_.PushType(spv::Op::OpTypeSampler, {result});
 
                 // Register both of the sampler type names. In SPIR-V they're the same
                 // sampler type, so we need to match that when we do the dedup check.
@@ -3747,7 +3701,8 @@
                 return true;
             },
             [&](Default) {
-                error_ = "unable to convert type: " + type->FriendlyName();
+                TINT_ICE(Writer, builder_.Diagnostics())
+                    << "unable to convert type: " + type->FriendlyName();
                 return false;
             });
 
@@ -3776,9 +3731,9 @@
     if (dim == type::TextureDimension::k1d) {
         dim_literal = SpvDim1D;
         if (texture->Is<type::SampledTexture>()) {
-            push_capability(SpvCapabilitySampled1D);
+            module_.PushCapability(SpvCapabilitySampled1D);
         } else if (texture->Is<type::StorageTexture>()) {
-            push_capability(SpvCapabilityImage1D);
+            module_.PushCapability(SpvCapabilityImage1D);
         }
     }
     if (dim == type::TextureDimension::k3d) {
@@ -3806,7 +3761,7 @@
 
     if (dim == type::TextureDimension::kCubeArray) {
         if (texture->IsAnyOf<type::SampledTexture, type::DepthTexture>()) {
-            push_capability(SpvCapabilitySampledCubeArray);
+            module_.PushCapability(SpvCapabilitySampledCubeArray);
         }
     }
 
@@ -3831,10 +3786,10 @@
         format_literal = convert_texel_format_to_spv(t->texel_format());
     }
 
-    push_type(spv::Op::OpTypeImage,
-              {result, Operand(type_id), Operand(dim_literal), Operand(depth_literal),
-               Operand(array_literal), Operand(ms_literal), Operand(sampled_literal),
-               Operand(format_literal)});
+    module_.PushType(spv::Op::OpTypeImage,
+                     {result, Operand(type_id), Operand(dim_literal), Operand(depth_literal),
+                      Operand(array_literal), Operand(ms_literal), Operand(sampled_literal),
+                      Operand(format_literal)});
 
     return true;
 }
@@ -3847,11 +3802,11 @@
 
     auto result_id = std::get<uint32_t>(result);
     if (arr->Count()->Is<type::RuntimeArrayCount>()) {
-        push_type(spv::Op::OpTypeRuntimeArray, {result, Operand(elem_type)});
+        module_.PushType(spv::Op::OpTypeRuntimeArray, {result, Operand(elem_type)});
     } else {
         auto count = arr->ConstantCount();
         if (!count) {
-            error_ = type::Array::kErrExpectedConstantCount;
+            TINT_ICE(Writer, builder_.Diagnostics()) << type::Array::kErrExpectedConstantCount;
             return static_cast<uint32_t>(0);
         }
 
@@ -3860,11 +3815,12 @@
             return false;
         }
 
-        push_type(spv::Op::OpTypeArray, {result, Operand(elem_type), Operand(len_id)});
+        module_.PushType(spv::Op::OpTypeArray, {result, Operand(elem_type), Operand(len_id)});
     }
 
-    push_annot(spv::Op::OpDecorate,
-               {Operand(result_id), U32Operand(SpvDecorationArrayStride), Operand(arr->Stride())});
+    module_.PushAnnot(
+        spv::Op::OpDecorate,
+        {Operand(result_id), U32Operand(SpvDecorationArrayStride), Operand(arr->Stride())});
     return true;
 }
 
@@ -3875,7 +3831,8 @@
         return false;
     }
 
-    push_type(spv::Op::OpTypeMatrix, {result, Operand(col_type_id), Operand(mat->columns())});
+    module_.PushType(spv::Op::OpTypeMatrix,
+                     {result, Operand(col_type_id), Operand(mat->columns())});
     return true;
 }
 
@@ -3887,11 +3844,11 @@
 
     auto stg_class = ConvertAddressSpace(ptr->AddressSpace());
     if (stg_class == SpvStorageClassMax) {
-        error_ = "invalid address space for pointer";
+        TINT_ICE(Writer, builder_.Diagnostics()) << "invalid address space for pointer";
         return false;
     }
 
-    push_type(spv::Op::OpTypePointer, {result, U32Operand(stg_class), Operand(subtype_id)});
+    module_.PushType(spv::Op::OpTypePointer, {result, U32Operand(stg_class), Operand(subtype_id)});
 
     return true;
 }
@@ -3904,11 +3861,11 @@
 
     auto stg_class = ConvertAddressSpace(ref->AddressSpace());
     if (stg_class == SpvStorageClassMax) {
-        error_ = "invalid address space for reference";
+        TINT_ICE(Writer, builder_.Diagnostics()) << "invalid address space for reference";
         return false;
     }
 
-    push_type(spv::Op::OpTypePointer, {result, U32Operand(stg_class), Operand(subtype_id)});
+    module_.PushType(spv::Op::OpTypePointer, {result, U32Operand(stg_class), Operand(subtype_id)});
 
     return true;
 }
@@ -3917,7 +3874,8 @@
     auto struct_id = std::get<uint32_t>(result);
 
     if (struct_type->Name().IsValid()) {
-        push_debug(spv::Op::OpName, {Operand(struct_id), Operand(struct_type->Name().Name())});
+        module_.PushDebug(spv::Op::OpName,
+                          {Operand(struct_id), Operand(struct_type->Name().Name())});
     }
 
     OperandList ops;
@@ -3926,7 +3884,8 @@
     if (auto* sem_str = struct_type->As<sem::Struct>()) {
         auto* decl = sem_str->Declaration();
         if (ast::HasAttribute<transform::AddBlockAttribute::BlockAttribute>(decl->attributes)) {
-            push_annot(spv::Op::OpDecorate, {Operand(struct_id), U32Operand(SpvDecorationBlock)});
+            module_.PushAnnot(spv::Op::OpDecorate,
+                              {Operand(struct_id), U32Operand(SpvDecorationBlock)});
         }
     }
 
@@ -3939,15 +3898,15 @@
         ops.push_back(Operand(mem_id));
     }
 
-    push_type(spv::Op::OpTypeStruct, std::move(ops));
+    module_.PushType(spv::Op::OpTypeStruct, std::move(ops));
     return true;
 }
 
 uint32_t Builder::GenerateStructMember(uint32_t struct_id,
                                        uint32_t idx,
                                        const type::StructMember* member) {
-    push_debug(spv::Op::OpMemberName,
-               {Operand(struct_id), Operand(idx), Operand(member->Name().Name())});
+    module_.PushDebug(spv::Op::OpMemberName,
+                      {Operand(struct_id), Operand(idx), Operand(member->Name().Name())});
 
     // Note: This will generate layout annotations for *all* structs, whether or
     // not they are used in host-shareable variables. This is officially ok in
@@ -3955,20 +3914,20 @@
     // to only generate the layout info for structs used for certain storage
     // classes.
 
-    push_annot(spv::Op::OpMemberDecorate,
-               {Operand(struct_id), Operand(idx), U32Operand(SpvDecorationOffset),
-                Operand(member->Offset())});
+    module_.PushAnnot(spv::Op::OpMemberDecorate,
+                      {Operand(struct_id), Operand(idx), U32Operand(SpvDecorationOffset),
+                       Operand(member->Offset())});
 
     // Infer and emit matrix layout.
     auto* matrix_type = GetNestedMatrixType(member->Type());
     if (matrix_type) {
-        push_annot(spv::Op::OpMemberDecorate,
-                   {Operand(struct_id), Operand(idx), U32Operand(SpvDecorationColMajor)});
+        module_.PushAnnot(spv::Op::OpMemberDecorate,
+                          {Operand(struct_id), Operand(idx), U32Operand(SpvDecorationColMajor)});
         const uint32_t scalar_elem_size = matrix_type->type()->Size();
         const uint32_t effective_row_count = (matrix_type->rows() == 2) ? 2 : 4;
-        push_annot(spv::Op::OpMemberDecorate,
-                   {Operand(struct_id), Operand(idx), U32Operand(SpvDecorationMatrixStride),
-                    Operand(effective_row_count * scalar_elem_size)});
+        module_.PushAnnot(spv::Op::OpMemberDecorate,
+                          {Operand(struct_id), Operand(idx), U32Operand(SpvDecorationMatrixStride),
+                           Operand(effective_row_count * scalar_elem_size)});
     }
 
     return GenerateTypeIfNeeded(member->Type());
@@ -3980,7 +3939,7 @@
         return false;
     }
 
-    push_type(spv::Op::OpTypeVector, {result, Operand(type_id), Operand(vec->Width())});
+    module_.PushType(spv::Op::OpTypeVector, {result, Operand(type_id), Operand(vec->Width())});
     return true;
 }
 
@@ -4042,7 +4001,7 @@
         case builtin::BuiltinValue::kNumWorkgroups:
             return SpvBuiltInNumWorkgroups;
         case builtin::BuiltinValue::kSampleIndex:
-            push_capability(SpvCapabilitySampleRateShading);
+            module_.PushCapability(SpvCapabilitySampleRateShading);
             return SpvBuiltInSampleId;
         case builtin::BuiltinValue::kSampleMask:
             return SpvBuiltInSampleMask;
@@ -4057,10 +4016,11 @@
                                           builtin::InterpolationSampling sampling) {
     switch (type) {
         case builtin::InterpolationType::kLinear:
-            push_annot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationNoPerspective)});
+            module_.PushAnnot(spv::Op::OpDecorate,
+                              {Operand(id), U32Operand(SpvDecorationNoPerspective)});
             break;
         case builtin::InterpolationType::kFlat:
-            push_annot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationFlat)});
+            module_.PushAnnot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationFlat)});
             break;
         case builtin::InterpolationType::kPerspective:
         case builtin::InterpolationType::kUndefined:
@@ -4068,11 +4028,12 @@
     }
     switch (sampling) {
         case builtin::InterpolationSampling::kCentroid:
-            push_annot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationCentroid)});
+            module_.PushAnnot(spv::Op::OpDecorate,
+                              {Operand(id), U32Operand(SpvDecorationCentroid)});
             break;
         case builtin::InterpolationSampling::kSample:
-            push_capability(SpvCapabilitySampleRateShading);
-            push_annot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationSample)});
+            module_.PushCapability(SpvCapabilitySampleRateShading);
+            module_.PushAnnot(spv::Op::OpDecorate, {Operand(id), U32Operand(SpvDecorationSample)});
             break;
         case builtin::InterpolationSampling::kCenter:
         case builtin::InterpolationSampling::kUndefined:
@@ -4101,13 +4062,13 @@
         case builtin::TexelFormat::kRgba8Sint:
             return SpvImageFormatRgba8i;
         case builtin::TexelFormat::kRg32Uint:
-            push_capability(SpvCapabilityStorageImageExtendedFormats);
+            module_.PushCapability(SpvCapabilityStorageImageExtendedFormats);
             return SpvImageFormatRg32ui;
         case builtin::TexelFormat::kRg32Sint:
-            push_capability(SpvCapabilityStorageImageExtendedFormats);
+            module_.PushCapability(SpvCapabilityStorageImageExtendedFormats);
             return SpvImageFormatRg32i;
         case builtin::TexelFormat::kRg32Float:
-            push_capability(SpvCapabilityStorageImageExtendedFormats);
+            module_.PushCapability(SpvCapabilityStorageImageExtendedFormats);
             return SpvImageFormatRg32f;
         case builtin::TexelFormat::kRgba16Uint:
             return SpvImageFormatRgba16ui;
@@ -4128,22 +4089,22 @@
 }
 
 bool Builder::push_function_inst(spv::Op op, const OperandList& operands) {
-    if (functions_.empty()) {
+    if (!current_function_) {
         utils::StringStream ss;
         ss << "Internal error: trying to add SPIR-V instruction " << int(op)
            << " outside a function";
-        error_ = ss.str();
+        TINT_ICE(Writer, builder_.Diagnostics()) << ss.str();
         return false;
     }
-    functions_.back().push_inst(op, operands);
+    current_function_.push_inst(op, operands);
     return true;
 }
 
 bool Builder::InsideBasicBlock() const {
-    if (functions_.empty()) {
+    if (!current_function_) {
         return false;
     }
-    const auto& instructions = functions_.back().instructions();
+    const auto& instructions = current_function_.instructions();
     if (instructions.empty()) {
         // The Function object does not explicitly represent its entry block
         // label.  So return *true* because an empty list means the only
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index 1bbffc8..17e96cd 100644
--- a/src/tint/writer/spirv/builder.h
+++ b/src/tint/writer/spirv/builder.h
@@ -15,6 +15,7 @@
 #ifndef SRC_TINT_WRITER_SPIRV_BUILDER_H_
 #define SRC_TINT_WRITER_SPIRV_BUILDER_H_
 
+#include <memory>
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
@@ -39,6 +40,7 @@
 #include "src/tint/sem/builtin.h"
 #include "src/tint/type/storage_texture.h"
 #include "src/tint/writer/spirv/function.h"
+#include "src/tint/writer/spirv/module.h"
 #include "src/tint/writer/spirv/scalar_constant.h"
 
 // Forward declarations
@@ -54,7 +56,7 @@
 
 namespace tint::writer::spirv {
 
-/// Builder class to create SPIR-V instructions from a module.
+/// Builder class to create a SPIR-V module from a Tint AST.
 class Builder {
   public:
     /// Contains information for generating accessor chains
@@ -87,105 +89,23 @@
     /// @returns true if the SPIR-V was successfully built
     bool Build();
 
-    /// @returns the error string or blank if no error was reported.
-    const std::string& error() const { return error_; }
+    /// @returns the list of diagnostics raised by the builder
+    const diag::List& Diagnostics() const { return builder_.Diagnostics(); }
+
     /// @returns true if the builder encountered an error
-    bool has_error() const { return !error_.empty(); }
+    bool has_error() const { return Diagnostics().contains_errors(); }
 
-    /// @returns the number of uint32_t's needed to make up the results
-    uint32_t total_size() const;
+    /// @returns the module that this builder has produced
+    spirv::Module& Module() { return module_; }
 
-    /// @returns the id bound for this program
-    uint32_t id_bound() const { return next_id_; }
-
-    /// @returns the next id to be used
-    uint32_t next_id() {
-        auto id = next_id_;
-        next_id_ += 1;
-        return id;
+    /// Add an empty function to the builder, to be used for testing purposes.
+    void PushFunctionForTesting() {
+        current_function_ = Function(Instruction(spv::Op::OpFunction, {}), {}, {});
     }
 
-    /// Iterates over all the instructions in the correct order and calls the
-    /// given callback
-    /// @param cb the callback to execute
-    void iterate(std::function<void(const Instruction&)> cb) const;
+    /// @returns the current function
+    const Function& CurrentFunction() { return current_function_; }
 
-    /// Adds an instruction to the list of capabilities, if the capability
-    /// hasn't already been added.
-    /// @param cap the capability to set
-    void push_capability(uint32_t cap);
-    /// @returns the capabilities
-    const InstructionList& capabilities() const { return capabilities_; }
-    /// Adds an instruction to the extensions
-    /// @param extension the name of the extension
-    void push_extension(const char* extension);
-    /// @returns the extensions
-    const InstructionList& extensions() const { return extensions_; }
-    /// Adds an instruction to the ext import
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_ext_import(spv::Op op, const OperandList& operands) {
-        ext_imports_.push_back(Instruction{op, operands});
-    }
-    /// @returns the ext imports
-    const InstructionList& ext_imports() const { return ext_imports_; }
-    /// Adds an instruction to the memory model
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_memory_model(spv::Op op, const OperandList& operands) {
-        memory_model_.push_back(Instruction{op, operands});
-    }
-    /// @returns the memory model
-    const InstructionList& memory_model() const { return memory_model_; }
-    /// Adds an instruction to the entry points
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_entry_point(spv::Op op, const OperandList& operands) {
-        entry_points_.push_back(Instruction{op, operands});
-    }
-    /// @returns the entry points
-    const InstructionList& entry_points() const { return entry_points_; }
-    /// Adds an instruction to the execution modes
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_execution_mode(spv::Op op, const OperandList& operands) {
-        execution_modes_.push_back(Instruction{op, operands});
-    }
-    /// @returns the execution modes
-    const InstructionList& execution_modes() const { return execution_modes_; }
-    /// Adds an instruction to the debug
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_debug(spv::Op op, const OperandList& operands) {
-        debug_.push_back(Instruction{op, operands});
-    }
-    /// @returns the debug instructions
-    const InstructionList& debug() const { return debug_; }
-    /// Adds an instruction to the types
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_type(spv::Op op, const OperandList& operands) {
-        types_.push_back(Instruction{op, operands});
-    }
-    /// @returns the type instructions
-    const InstructionList& types() const { return types_; }
-    /// Adds an instruction to the annotations
-    /// @param op the op to set
-    /// @param operands the operands for the instruction
-    void push_annot(spv::Op op, const OperandList& operands) {
-        annotations_.push_back(Instruction{op, operands});
-    }
-    /// @returns the annotations
-    const InstructionList& annots() const { return annotations_; }
-
-    /// Adds a function to the builder
-    /// @param func the function to add
-    void push_function(const Function& func) {
-        functions_.push_back(func);
-        current_label_id_ = func.label_id();
-    }
-    /// @returns the functions
-    const std::vector<Function>& functions() const { return functions_; }
     /// Pushes an instruction to the current function. If we're outside
     /// a function then issue an internal error and return false.
     /// @param op the operation
@@ -195,11 +115,11 @@
     /// Pushes a variable to the current function
     /// @param operands the variable operands
     void push_function_var(const OperandList& operands) {
-        if (TINT_UNLIKELY(functions_.empty())) {
+        if (TINT_UNLIKELY(!current_function_)) {
             TINT_ICE(Writer, builder_.Diagnostics())
                 << "push_function_var() called without a function";
         }
-        functions_.back().push_var(operands);
+        current_function_.push_var(operands);
     }
 
     /// @returns true if the current instruction insertion point is
@@ -590,19 +510,9 @@
     void PopScope();
 
     ProgramBuilder builder_;
-    std::string error_;
-    uint32_t next_id_ = 1;
+    spirv::Module module_;
+    Function current_function_;
     uint32_t current_label_id_ = 0;
-    InstructionList capabilities_;
-    InstructionList extensions_;
-    InstructionList ext_imports_;
-    InstructionList memory_model_;
-    InstructionList entry_points_;
-    InstructionList execution_modes_;
-    InstructionList debug_;
-    InstructionList types_;
-    InstructionList annotations_;
-    std::vector<Function> functions_;
 
     // Scope holds per-block information
     struct Scope {
@@ -625,7 +535,6 @@
     std::vector<Scope> scope_stack_;
     std::vector<uint32_t> merge_stack_;
     std::vector<uint32_t> continue_stack_;
-    std::unordered_set<uint32_t> capability_set_;
     bool zero_initialize_workgroup_memory_ = false;
 
     struct ContinuingInfo {
diff --git a/src/tint/writer/spirv/builder_accessor_expression_test.cc b/src/tint/writer/spirv/builder_accessor_expression_test.cc
index e6692ab..d93b414 100644
--- a/src/tint/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_accessor_expression_test.cc
@@ -32,9 +32,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeInt 32 1
 %5 = OpTypeVector %6 3
@@ -45,9 +45,10 @@
 %13 = OpTypePointer Function %6
 %14 = OpConstantNull %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%12 = OpVariable %13 Function %14
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%12 = OpVariable %13 Function %14
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%11 = OpCompositeExtract %6 %10 1
 OpStore %12 %11
 OpReturn
@@ -66,18 +67,19 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeInt 32 1
 %6 = OpConstant %5 2
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%7 = OpVariable %8 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%7 = OpVariable %8 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpStore %7 %6
 OpReturn
 )");
@@ -95,9 +97,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeInt 32 0
 %7 = OpTypeVector %8 3
@@ -108,10 +110,12 @@
 %12 = OpTypePointer Function %8
 %16 = OpConstantNull %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 %15 = OpVariable %12 Function %16
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%13 = OpAccessChain %12 %5 %11
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%13 = OpAccessChain %12 %5 %11
 %14 = OpLoad %8 %13
 OpStore %15 %14
 OpReturn
@@ -132,9 +136,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -146,11 +150,12 @@
 %15 = OpTypePointer Function %8
 %19 = OpConstantNull %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 %10 = OpVariable %11 Function %13
 %18 = OpVariable %15 Function %19
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%14 = OpLoad %12 %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(%14 = OpLoad %12 %10
 %16 = OpAccessChain %15 %5 %14
 %17 = OpLoad %8 %16
 OpStore %18 %17
@@ -170,9 +175,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeInt 32 1
 %5 = OpTypeVector %6 3
@@ -183,9 +188,10 @@
 %13 = OpTypePointer Function %6
 %14 = OpConstantNull %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%12 = OpVariable %13 Function %14
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%12 = OpVariable %13 Function %14
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%11 = OpCompositeExtract %6 %10 2
 OpStore %12 %11
 OpReturn
@@ -204,9 +210,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -217,10 +223,12 @@
 %12 = OpTypePointer Function %8
 %16 = OpConstantNull %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 %15 = OpVariable %12 Function %16
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%13 = OpAccessChain %12 %5 %11
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%13 = OpAccessChain %12 %5 %11
 %14 = OpLoad %8 %13
 OpStore %15 %14
 OpReturn
@@ -241,9 +249,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -257,11 +265,12 @@
 %18 = OpTypePointer Function %8
 %22 = OpConstantNull %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 %12 = OpVariable %13 Function %14
 %21 = OpVariable %18 Function %22
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %12 %11
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %12 %11
 %15 = OpLoad %10 %12
 %17 = OpIAdd %10 %15 %16
 %19 = OpAccessChain %18 %5 %17
@@ -284,9 +293,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %7 = OpTypeFloat 32
 %6 = OpTypeVector %7 3
@@ -308,9 +317,10 @@
 %25 = OpTypePointer Function %7
 %26 = OpConstantNull %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%24 = OpVariable %25 Function %26
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%24 = OpVariable %25 Function %26
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%21 = OpCompositeExtract %6 %18 1
 %23 = OpCompositeExtract %7 %21 2
 OpStore %24 %23
@@ -331,18 +341,19 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeFloat 32
 %6 = OpConstant %5 6
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%7 = OpVariable %8 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%7 = OpVariable %8 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpStore %7 %6
 OpReturn
 )");
@@ -360,9 +371,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 3
@@ -377,10 +388,11 @@
 %16 = OpTypePointer Function %9
 %20 = OpConstantNull %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %12
 %19 = OpVariable %16 Function %20
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%17 = OpAccessChain %16 %5 %14 %15
 %18 = OpLoad %9 %17
 OpStore %19 %18
@@ -402,9 +414,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 3
@@ -421,11 +433,12 @@
 %20 = OpTypePointer Function %9
 %24 = OpConstantNull %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %12
 %15 = OpVariable %16 Function %17
 %23 = OpVariable %20 Function %24
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %15 %14
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %15 %14
 %18 = OpLoad %13 %15
 %21 = OpAccessChain %20 %5 %18 %19
 %22 = OpLoad %9 %21
@@ -447,9 +460,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %7 = OpTypeFloat 32
 %6 = OpTypeVector %7 3
@@ -471,9 +484,10 @@
 %25 = OpTypePointer Function %22
 %26 = OpConstantNull %22
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%24 = OpVariable %25 Function %26
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%24 = OpVariable %25 Function %26
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%21 = OpCompositeExtract %6 %18 1
 %23 = OpVectorShuffle %22 %21 %21 0 1
 OpStore %24 %23
@@ -493,9 +507,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 3
@@ -511,10 +525,12 @@
 %21 = OpTypePointer Function %17
 %22 = OpConstantNull %17
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %12
 %20 = OpVariable %21 Function %22
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%16 = OpAccessChain %15 %5 %14
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%16 = OpAccessChain %15 %5 %14
 %18 = OpLoad %8 %16
 %19 = OpVectorShuffle %17 %18 %18 0 1
 OpStore %20 %19
@@ -536,9 +552,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 3
@@ -556,11 +572,12 @@
 %25 = OpTypePointer Function %21
 %26 = OpConstantNull %21
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %12
 %15 = OpVariable %16 Function %17
 %24 = OpVariable %25 Function %26
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %15 %14
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %15 %14
 %18 = OpLoad %13 %15
 %20 = OpAccessChain %19 %5 %18
 %22 = OpLoad %8 %20
@@ -587,9 +604,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %7 = OpTypeFloat 32
 %6 = OpTypeVector %7 2
@@ -607,9 +624,10 @@
 %19 = OpConstantNull %8
 %22 = OpTypePointer Function %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%21 = OpVariable %22 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%21 = OpVariable %22 Function %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%18 = OpCompositeExtract %6 %16 1
 %20 = OpCompositeExtract %7 %18 0
 OpStore %21 %20
@@ -634,18 +652,19 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeFloat 32
 %6 = OpConstant %5 -0.5
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%7 = OpVariable %8 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%7 = OpVariable %8 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpStore %7 %6
 OpReturn
 )");
@@ -663,9 +682,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 3
@@ -679,10 +698,11 @@
 %15 = OpTypePointer Function %9
 %19 = OpConstantNull %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %12
 %18 = OpVariable %15 Function %19
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%16 = OpAccessChain %15 %5 %13 %14
 %17 = OpLoad %9 %16
 OpStore %18 %17
@@ -704,9 +724,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %10 = OpTypeInt 32 0
@@ -722,11 +742,12 @@
 %19 = OpTypePointer Function %9
 %23 = OpConstantNull %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %13
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %13
 %15 = OpVariable %16 Function %17
 %22 = OpVariable %19 Function %23
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %15 %14
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %15 %14
 %18 = OpLoad %10 %15
 %20 = OpAccessChain %19 %5 %18 %14
 %21 = OpLoad %9 %20
@@ -749,9 +770,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %7 = OpTypeFloat 32
 %6 = OpTypeVector %7 2
@@ -768,9 +789,10 @@
 %19 = OpTypePointer Function %6
 %20 = OpConstantNull %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%18 = OpVariable %19 Function %20
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%18 = OpVariable %19 Function %20
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%17 = OpCompositeExtract %6 %14 1
 OpStore %18 %17
 OpReturn
@@ -791,9 +813,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 2
@@ -803,9 +825,10 @@
 %11 = OpTypePointer Function %5
 %12 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%10 = OpVariable %11 Function %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%10 = OpVariable %11 Function %12
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpStore %10 %9
 OpReturn
 )");
@@ -823,9 +846,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 2
@@ -837,10 +860,12 @@
 %13 = OpTypePointer Function %8
 %17 = OpConstantNull %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %10
 %16 = OpVariable %13 Function %17
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%14 = OpAccessChain %13 %5 %12
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%14 = OpAccessChain %13 %5 %12
 %15 = OpLoad %8 %14
 OpStore %16 %15
 OpReturn
@@ -861,9 +886,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeVector %9 2
@@ -876,11 +901,12 @@
 %16 = OpTypePointer Function %8
 %20 = OpConstantNull %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %10
 %11 = OpVariable %12 Function %14
 %19 = OpVariable %16 Function %20
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%15 = OpLoad %13 %11
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(%15 = OpLoad %13 %11
 %17 = OpAccessChain %16 %5 %15
 %18 = OpLoad %8 %17
 OpStore %19 %18
@@ -910,9 +936,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeStruct %8 %8
@@ -922,9 +948,11 @@
 %11 = OpConstant %10 1
 %12 = OpTypePointer Function %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%13 = OpAccessChain %12 %5 %11
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%13 = OpAccessChain %12 %5 %11
 %14 = OpLoad %8 %13
 OpReturn
 )");
@@ -956,9 +984,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeStruct %9 %9
@@ -970,9 +998,10 @@
 %13 = OpConstant %11 1
 %14 = OpTypePointer Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%15 = OpAccessChain %14 %5 %12 %13
 %16 = OpLoad %9 %15
 OpReturn
@@ -1001,16 +1030,17 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeStruct %6 %6
 %7 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%8 = OpCompositeExtract %6 %7 1
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%8 = OpCompositeExtract %6 %7 1
 OpReturn
 )");
 
@@ -1042,17 +1072,17 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %7 = OpTypeFloat 32
 %6 = OpTypeStruct %7 %7
 %5 = OpTypeStruct %6
 %8 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%9 = OpCompositeExtract %6 %8 0
 %10 = OpCompositeExtract %7 %9 1
 OpReturn
@@ -1087,9 +1117,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeStruct %9 %9
@@ -1100,9 +1130,10 @@
 %12 = OpConstant %11 0
 %13 = OpTypePointer Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%14 = OpAccessChain %13 %5 %12 %12
 %15 = OpLoad %9 %14
 OpReturn
@@ -1134,9 +1165,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeStruct %9 %9
@@ -1148,9 +1179,10 @@
 %13 = OpTypePointer Function %9
 %15 = OpConstant %9 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%14 = OpAccessChain %13 %5 %12 %12
 OpStore %14 %15
 OpReturn
@@ -1186,9 +1218,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
 %8 = OpTypeStruct %9 %9
@@ -1200,10 +1232,11 @@
 %14 = OpTypeInt 32 0
 %15 = OpConstant %14 0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %10
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %10
 %11 = OpVariable %12 Function %13
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%16 = OpAccessChain %12 %5 %15 %15
 %17 = OpLoad %9 %16
 OpStore %11 %17
@@ -1223,9 +1256,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -1235,9 +1268,11 @@
 %11 = OpConstant %10 1
 %12 = OpTypePointer Function %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%13 = OpAccessChain %12 %5 %11
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              R"(%13 = OpAccessChain %12 %5 %11
 %14 = OpLoad %8 %13
 OpReturn
 )");
@@ -1255,9 +1290,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -1265,9 +1300,10 @@
 %9 = OpConstantNull %7
 %11 = OpTypeVector %8 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%10 = OpLoad %7 %5
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(%10 = OpLoad %7 %5
 %12 = OpVectorShuffle %11 %10 %10 1 0
 OpReturn
 )");
@@ -1285,9 +1321,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -1295,9 +1331,10 @@
 %9 = OpConstantNull %7
 %12 = OpTypeVector %8 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%10 = OpLoad %7 %5
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(%10 = OpLoad %7 %5
 %11 = OpVectorShuffle %7 %10 %10 1 0 2
 %13 = OpVectorShuffle %12 %11 %11 0 2
 OpReturn
@@ -1316,18 +1353,19 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
 %6 = OpTypePointer Function %7
 %9 = OpConstantNull %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%10 = OpLoad %7 %5
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(%10 = OpLoad %7 %5
 %11 = OpVectorShuffle %7 %10 %10 1 0 2
 %12 = OpCompositeExtract %8 %11 0
 OpReturn
@@ -1346,9 +1384,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
@@ -1357,9 +1395,10 @@
 %12 = OpTypeInt 32 1
 %13 = OpConstant %12 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %9
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(%10 = OpLoad %7 %5
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(%10 = OpLoad %7 %5
 %11 = OpVectorShuffle %7 %10 %10 1 0 2
 %14 = OpCompositeExtract %8 %11 1
 OpReturn
@@ -1399,9 +1438,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %13 = OpTypeFloat 32
 %12 = OpTypeVector %13 3
@@ -1422,9 +1461,10 @@
 %22 = OpTypePointer Function %12
 %24 = OpTypeVector %13 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %17
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+              R"(%5 = OpVariable %6 Function %17
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%23 = OpAccessChain %22 %5 %19 %20 %21 %20 %20
 %25 = OpLoad %12 %23
 %26 = OpVectorShuffle %24 %25 %25 1 0
diff --git a/src/tint/writer/spirv/builder_assign_test.cc b/src/tint/writer/spirv/builder_assign_test.cc
index a6b8bc9..cab0ed4 100644
--- a/src/tint/writer/spirv/builder_assign_test.cc
+++ b/src/tint/writer/spirv/builder_assign_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "src/tint/writer/spirv/spv_dump.h"
 #include "src/tint/writer/spirv/test_helper.h"
 
@@ -31,21 +32,21 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %5 = OpConstant %3 1
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %1 %5
 )");
 }
@@ -59,14 +60,15 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_FALSE(b.GenerateAssignStatement(assign)) << b.error();
+    tint::SetInternalCompilerErrorReporter(nullptr);
+
+    EXPECT_FALSE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_TRUE(b.has_error());
-    EXPECT_EQ(b.error(),
-              "Internal error: trying to add SPIR-V instruction 62 outside a "
-              "function");
+    EXPECT_THAT(b.Diagnostics().str(),
+                ::testing::HasSubstr("trying to add SPIR-V instruction 62 outside a function"));
 }
 
 TEST_F(BuilderTest, Assign_Var_ZeroInitializer) {
@@ -79,21 +81,21 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
 %1 = OpVariable %2 Private %5
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %1 %5
 )");
 }
@@ -109,14 +111,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -126,7 +128,7 @@
 %8 = OpConstant %4 3
 %9 = OpConstantComposite %3 %6 %7 %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %1 %9
 )");
 }
@@ -142,14 +144,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -159,7 +161,7 @@
 %8 = OpConstant %4 3
 %9 = OpConstantComposite %3 %6 %7 %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %1 %9
 )");
 }
@@ -185,14 +187,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeStruct %4 %4
 %2 = OpTypePointer Function %3
 %5 = OpConstantNull %3
@@ -202,7 +204,7 @@
 %10 = OpConstant %4 4
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpAccessChain %8 %1 %7
 OpStore %9 %10
 )");
@@ -218,14 +220,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -235,7 +237,7 @@
 %8 = OpConstantComposite %3 %6 %6 %7
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %1 %8
 )");
 }
@@ -251,14 +253,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -269,7 +271,7 @@
 %10 = OpConstant %4 1
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpAccessChain %8 %1 %7
 OpStore %9 %10
 )");
@@ -286,14 +288,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.error();
+    EXPECT_TRUE(b.GenerateAssignStatement(assign)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -304,7 +306,7 @@
 %10 = OpConstant %4 1
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpAccessChain %8 %1 %7
 OpStore %9 %10
 )");
diff --git a/src/tint/writer/spirv/builder_binary_expression_test.cc b/src/tint/writer/spirv/builder_binary_expression_test.cc
index 705bf77..36bd427 100644
--- a/src/tint/writer/spirv/builder_binary_expression_test.cc
+++ b/src/tint/writer/spirv/builder_binary_expression_test.cc
@@ -46,14 +46,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 3
 %3 = OpConstant %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %1 %2 %3\n");
 }
 
@@ -75,15 +75,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %1 %4 %4\n");
 }
 TEST_P(BinaryArithSignedIntegerTest, Scalar_Loads) {
@@ -96,19 +96,19 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Function %3
 %4 = OpConstantNull %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%1 = OpVariable %2 Function %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%5 = OpLoad %3 %1
 %6 = OpLoad %3 %1
 %7 = )" + param.name +
@@ -140,14 +140,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 3
 %3 = OpConstant %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %1 %2 %3\n");
 }
 TEST_P(BinaryArithUnsignedIntegerTest, Vector) {
@@ -168,15 +168,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 0
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %1 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -206,14 +206,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 3.20000005
 %3 = OpConstant %1 4.5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %1 %2 %3\n");
 }
 
@@ -229,15 +229,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %1 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(BuilderTest,
@@ -263,14 +263,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1.998p+1
 %3 = OpConstant %1 0x1.2p+2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %1 %2 %3\n");
 }
 
@@ -288,15 +288,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstantComposite %1 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %1 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(BuilderTest,
@@ -320,14 +320,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %3 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %1 %2 %3\n");
 }
 
@@ -343,17 +343,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeVector %2 3
 %3 = OpConstantNull %2
 %4 = OpConstantTrue %2
 %5 = OpConstantComposite %1 %3 %4 %3
 %6 = OpConstantComposite %1 %4 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%7 = " + param.name + " %1 %5 %6\n");
 }
 INSTANTIATE_TEST_SUITE_P(BuilderTest,
@@ -376,15 +376,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 3
 %3 = OpConstant %1 4
 %5 = OpTypeBool
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %5 %2 %3\n");
 }
 
@@ -400,17 +400,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 0
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 %7 = OpTypeBool
 %6 = OpTypeVector %7 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %6 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -436,15 +436,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 3
 %3 = OpConstant %1 4
 %5 = OpTypeBool
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %5 %2 %3\n");
 }
 
@@ -460,17 +460,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 %7 = OpTypeBool
 %6 = OpTypeVector %7 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %6 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -496,15 +496,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 3.20000005
 %3 = OpConstant %1 4.5
 %5 = OpTypeBool
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %5 %2 %3\n");
 }
 
@@ -520,17 +520,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 %7 = OpTypeBool
 %6 = OpTypeVector %7 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %6 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -558,15 +558,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1.998p+1
 %3 = OpConstant %1 0x1.2p+2
 %5 = OpTypeBool
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%4 = " + param.name + " %5 %2 %3\n");
 }
 
@@ -584,17 +584,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstantComposite %1 %3 %3 %3
 %7 = OpTypeBool
 %6 = OpTypeVector %7 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = " + param.name + " %6 %4 %4\n");
 }
 INSTANTIATE_TEST_SUITE_P(
@@ -617,16 +617,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = OpVectorTimesScalar %1 %4 %3\n");
 }
 
@@ -642,16 +642,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstantComposite %1 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = OpVectorTimesScalar %1 %4 %3\n");
 }
 
@@ -665,16 +665,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 1
 %3 = OpTypeVector %1 3
 %4 = OpConstantComposite %3 %2 %2 %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = OpVectorTimesScalar %3 %4 %2\n");
 }
 
@@ -690,16 +690,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+0
 %3 = OpTypeVector %1 3
 %4 = OpConstantComposite %3 %2 %2 %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               "%5 = OpVectorTimesScalar %3 %4 %2\n");
 }
 
@@ -711,11 +711,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -723,7 +723,7 @@
 %6 = OpConstantNull %3
 %8 = OpConstant %5 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %9 = OpMatrixTimesScalar %3 %7 %8
 )");
@@ -739,11 +739,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -751,7 +751,7 @@
 %6 = OpConstantNull %3
 %8 = OpConstant %5 0x1p+0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %9 = OpMatrixTimesScalar %3 %7 %8
 )");
@@ -765,11 +765,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -777,7 +777,7 @@
 %6 = OpConstantNull %3
 %7 = OpConstant %5 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%8 = OpLoad %3 %1
 %9 = OpMatrixTimesScalar %3 %8 %7
 )");
@@ -793,11 +793,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -805,7 +805,7 @@
 %6 = OpConstantNull %3
 %7 = OpConstant %5 0x1p+0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%8 = OpLoad %3 %1
 %9 = OpMatrixTimesScalar %3 %8 %7
 )");
@@ -820,11 +820,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -833,7 +833,7 @@
 %8 = OpConstant %5 1
 %9 = OpConstantComposite %4 %8 %8 %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %10 = OpMatrixTimesVector %4 %7 %9
 )");
@@ -850,11 +850,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -863,7 +863,7 @@
 %8 = OpConstant %5 0x1p+0
 %9 = OpConstantComposite %4 %8 %8 %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %10 = OpMatrixTimesVector %4 %7 %9
 )");
@@ -878,11 +878,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -891,7 +891,7 @@
 %7 = OpConstant %5 1
 %8 = OpConstantComposite %4 %7 %7 %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %10 = OpVectorTimesMatrix %4 %8 %9
 )");
@@ -909,11 +909,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
@@ -922,7 +922,7 @@
 %7 = OpConstant %5 0x1p+0
 %8 = OpConstantComposite %4 %7 %7 %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %10 = OpVectorTimesMatrix %4 %8 %9
 )");
@@ -936,18 +936,18 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
 %6 = OpConstantNull %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %8 = OpLoad %3 %1
 %9 = OpMatrixTimesMatrix %3 %7 %8
@@ -964,18 +964,18 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%5 = OpTypeFloat 16
 %4 = OpTypeVector %5 3
 %3 = OpTypeMatrix %4 3
 %2 = OpTypePointer Function %3
 %6 = OpConstantNull %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %8 = OpLoad %3 %1
 %9 = OpMatrixTimesMatrix %3 %7 %8
@@ -993,15 +993,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    b.GenerateLabel(b.next_id());
-    ASSERT_TRUE(b.GenerateFunctionVariable(v0)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(v1)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(v3)) << b.error();
+    b.PushFunctionForTesting();
+    b.GenerateLabel(b.Module().NextId());
+    ASSERT_TRUE(b.GenerateFunctionVariable(v0)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(v1)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(v2)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(v3)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 22u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 22u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeInt 32 1
 %3 = OpConstant %2 1
 %5 = OpTypePointer Function %2
@@ -1011,7 +1011,7 @@
 %11 = OpConstant %2 4
 %16 = OpTypeBool
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpLabel
 OpStore %4 %3
 OpStore %8 %7
@@ -1041,21 +1041,21 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    b.GenerateLabel(b.next_id());
+    b.PushFunctionForTesting();
+    b.GenerateLabel(b.Module().NextId());
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %3 = OpConstantTrue %2
 %5 = OpTypePointer Private %2
 %4 = OpVariable %5 Private %3
 %6 = OpConstantNull %2
 %7 = OpVariable %5 Private %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpLabel
 %8 = OpLoad %2 %4
 OpSelectionMerge %9 None
@@ -1086,17 +1086,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(t)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(f)) << b.error();
-    b.GenerateLabel(b.next_id());
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(t)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(f)) << b.Diagnostics();
+    b.GenerateLabel(b.Module().NextId());
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %3 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%4 = OpLabel
 OpSelectionMerge %5 None
 OpBranchConditional %2 %5 %6
@@ -1131,17 +1131,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(t)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(f)) << b.error();
-    b.GenerateLabel(b.next_id());
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(t)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(f)) << b.Diagnostics();
+    b.GenerateLabel(b.Module().NextId());
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %3 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%4 = OpLabel
 OpSelectionMerge %5 None
 OpBranchConditional %2 %6 %5
@@ -1169,15 +1169,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    b.GenerateLabel(b.next_id());
-    ASSERT_TRUE(b.GenerateFunctionVariable(v0)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(v1)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-    ASSERT_TRUE(b.GenerateFunctionVariable(v3)) << b.error();
+    b.PushFunctionForTesting();
+    b.GenerateLabel(b.Module().NextId());
+    ASSERT_TRUE(b.GenerateFunctionVariable(v0)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(v1)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(v2)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunctionVariable(v3)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 22u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 22u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeInt 32 1
 %3 = OpConstant %2 1
 %5 = OpTypePointer Function %2
@@ -1187,7 +1187,7 @@
 %11 = OpConstant %2 4
 %16 = OpTypeBool
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpLabel
 OpStore %4 %3
 OpStore %8 %7
@@ -1218,21 +1218,21 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    b.GenerateLabel(b.next_id());
+    b.PushFunctionForTesting();
+    b.GenerateLabel(b.Module().NextId());
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %3 = OpConstantTrue %2
 %5 = OpTypePointer Private %2
 %4 = OpVariable %5 Private %3
 %6 = OpConstantNull %2
 %7 = OpVariable %5 Private %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpLabel
 %8 = OpLoad %2 %4
 OpSelectionMerge %9 None
@@ -1357,7 +1357,7 @@
     WrapInFunction(expr);
 
     spirv::Builder& b = Build();
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %11 "test_function"
@@ -1409,7 +1409,7 @@
     WrapInFunction(expr);
 
     spirv::Builder& b = Build();
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %11 "test_function"
@@ -1489,7 +1489,7 @@
     WrapInFunction(expr);
 
     spirv::Builder& b = Build();
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %11 "test_function"
@@ -1537,7 +1537,7 @@
     WrapInFunction(expr);
 
     spirv::Builder& b = Build();
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %11 "test_function"
@@ -1651,7 +1651,7 @@
     WrapInFunction(expr);
 
     spirv::Builder& b = Build();
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %10 "test_function"
@@ -1714,7 +1714,7 @@
     WrapInFunction(expr);
 
     spirv::Builder& b = Build();
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), capability_decl + R"(
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %14 "test_function"
diff --git a/src/tint/writer/spirv/builder_bitcast_expression_test.cc b/src/tint/writer/spirv/builder_bitcast_expression_test.cc
index 3841e69..cc417ab 100644
--- a/src/tint/writer/spirv/builder_bitcast_expression_test.cc
+++ b/src/tint/writer/spirv/builder_bitcast_expression_test.cc
@@ -29,14 +29,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateBitcastExpression(bitcast), 1u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 0
 %3 = OpTypeFloat 32
 %4 = OpConstant %3 2.4000001
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpBitcast %2 %4
 )");
 }
@@ -48,13 +48,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateBitcastExpression(bitcast), 1u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpConstant %2 2.4000001
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpCopyObject %2 %3
 )");
 }
diff --git a/src/tint/writer/spirv/builder_block_test.cc b/src/tint/writer/spirv/builder_block_test.cc
index f89db11..4783360 100644
--- a/src/tint/writer/spirv/builder_block_test.cc
+++ b/src/tint/writer/spirv/builder_block_test.cc
@@ -32,13 +32,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateStatement(outer)) << b.error();
+    EXPECT_TRUE(b.GenerateStatement(outer)) << b.Diagnostics();
     EXPECT_FALSE(b.has_error());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Function %3
 %4 = OpConstantNull %3
 %5 = OpConstant %3 1
@@ -46,12 +46,12 @@
 %8 = OpConstant %3 3
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%1 = OpVariable %2 Function %4
 %6 = OpVariable %2 Function %4
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %1 %5
 OpStore %6 %7
 OpStore %1 %8
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index 280b878..ba33a443 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -63,15 +63,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(tex)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(tex)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateExpression(expr1), 8u) << b.error();
-    EXPECT_EQ(b.GenerateExpression(expr2), 17u) << b.error();
+    EXPECT_EQ(b.GenerateExpression(expr1), 8u) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(expr2), 17u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeImage %4 2D 0 0 0 1 Unknown
 %2 = OpTypePointer UniformConstant %3
 %1 = OpVariable %2 UniformConstant
@@ -85,7 +85,7 @@
 %16 = OpConstantComposite %13 %14 %15
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %7 %5
 %10 = OpLoad %3 %1
 %12 = OpSampledImage %11 %10 %9
@@ -107,8 +107,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect =
@@ -143,8 +143,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect =
@@ -182,10 +182,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeBool
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -194,7 +194,8 @@
 )");
 
     // both any and all are 'passthrough' for scalar booleans
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), "%10 = OpLoad %3 %1\nOpReturn\n");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+              "%10 = OpLoad %3 %1\nOpReturn\n");
 }
 
 TEST_P(BuiltinBoolTest, Call_Bool_Vector) {
@@ -208,10 +209,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeBool
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -225,7 +226,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
                          BuiltinBoolTest,
@@ -243,11 +244,11 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v3)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(bool_v3)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v3)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(bool_v3)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -260,7 +261,7 @@
 %12 = OpTypeVoid
 %11 = OpTypeFunction %12
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%16 = OpLoad %8 %6
 %17 = OpLoad %3 %1
 %18 = OpLoad %3 %1
@@ -292,9 +293,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeFloat 32
 %4 = OpTypeRuntimeArray %5
@@ -305,13 +306,13 @@
 %6 = OpTypeFunction %7
 %11 = OpTypeInt 32 0
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -336,9 +337,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%4 = OpTypeFloat 32
 %5 = OpTypeRuntimeArray %4
@@ -349,13 +350,13 @@
 %6 = OpTypeFunction %7
 %11 = OpTypeInt 32 0
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 1
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -384,9 +385,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeFloat 32
 %4 = OpTypeRuntimeArray %5
@@ -397,13 +398,13 @@
 %6 = OpTypeFunction %7
 %11 = OpTypeInt 32 0
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -445,9 +446,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeFloat 32
 %4 = OpTypeRuntimeArray %5
@@ -458,13 +459,13 @@
 %6 = OpTypeFunction %7
 %11 = OpTypeInt 32 0
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(%10 = OpArrayLength %11 %1 0
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -489,7 +490,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -530,7 +531,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -570,7 +571,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -614,7 +615,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -680,7 +681,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -717,7 +718,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -752,7 +753,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -791,7 +792,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -828,7 +829,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -867,7 +868,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -906,7 +907,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -947,7 +948,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -986,7 +987,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1029,7 +1030,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1077,7 +1078,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1117,7 +1118,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1155,7 +1156,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -1193,7 +1194,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -1229,7 +1230,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1269,7 +1270,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1307,7 +1308,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1347,7 +1348,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1387,7 +1388,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -1429,7 +1430,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -1469,7 +1470,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1513,7 +1514,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1562,7 +1563,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1603,7 +1604,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -1645,7 +1646,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 %15 = OpExtInstImport "GLSL.std.450"
@@ -1699,7 +1700,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpCapability Float16
@@ -1753,7 +1754,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
@@ -1799,7 +1800,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpCapability Float16
@@ -1849,7 +1850,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 %17 = OpExtInstImport "GLSL.std.450"
@@ -1905,7 +1906,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpCapability Float16
@@ -1960,7 +1961,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
@@ -2008,7 +2009,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpCapability Float16
@@ -2060,7 +2061,7 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
@@ -2099,7 +2100,7 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
     auto got = DumpBuilder(b);
     auto* expect = R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
@@ -2163,10 +2164,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -2179,7 +2180,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 
 TEST_P(BuiltinIntTest, Call_SInt_Vector) {
@@ -2193,10 +2194,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 1
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -2210,7 +2211,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 
 TEST_P(BuiltinIntTest, Call_UInt_Scalar) {
@@ -2224,10 +2225,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 0
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -2240,7 +2241,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 
 TEST_P(BuiltinIntTest, Call_UInt_Vector) {
@@ -2254,10 +2255,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 0
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -2271,7 +2272,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
                          BuiltinIntTest,
@@ -2291,7 +2292,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -2329,7 +2330,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -2373,7 +2374,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpName %3 "a_func"
@@ -2406,7 +2407,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpName %3 "a_func"
@@ -2443,7 +2444,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -2482,7 +2483,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -2527,7 +2528,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -2566,7 +2567,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -2611,7 +2612,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -2651,7 +2652,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -2697,7 +2698,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
@@ -2737,7 +2738,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
@@ -2779,7 +2780,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -2820,7 +2821,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -2858,7 +2859,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -2900,7 +2901,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -2942,7 +2943,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -2987,7 +2988,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -3029,7 +3030,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -3075,7 +3076,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -3125,8 +3126,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%12 = OpExtInstImport "GLSL.std.450"
@@ -3162,8 +3163,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(%12 = OpExtInstImport "GLSL.std.450"
@@ -3197,8 +3198,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpName %1 "var"
@@ -3235,8 +3236,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpName %1 "var"
@@ -3276,10 +3277,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -3287,7 +3288,7 @@
 %7 = OpTypeVoid
 %6 = OpTypeFunction %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%11 = OpLoad %3 %1
 %12 = OpLoad %3 %1
 %10 = OpDot %4 %11 %12
@@ -3307,10 +3308,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 16
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -3318,7 +3319,7 @@
 %7 = OpTypeVoid
 %6 = OpTypeFunction %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%11 = OpLoad %3 %1
 %12 = OpLoad %3 %1
 %10 = OpDot %4 %11 %12
@@ -3336,10 +3337,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 0
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -3347,7 +3348,7 @@
 %7 = OpTypeVoid
 %6 = OpTypeFunction %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%11 = OpLoad %3 %1
 %12 = OpLoad %3 %1
 %13 = OpCompositeExtract %4 %11 0
@@ -3375,10 +3376,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 1
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -3386,7 +3387,7 @@
 %7 = OpTypeVoid
 %6 = OpTypeFunction %7
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%11 = OpLoad %3 %1
 %12 = OpLoad %3 %1
 %13 = OpCompositeExtract %4 %11 0
@@ -3424,10 +3425,10 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -3440,7 +3441,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 
 TEST_P(BuiltinDeriveTest, Call_Derivative_Vector) {
@@ -3457,16 +3458,16 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     if (param.name != "dpdx" && param.name != "dpdy" && param.name != "fwidth") {
-        EXPECT_EQ(DumpInstructions(b.capabilities()),
+        EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
                   R"(OpCapability DerivativeControl
 )");
     }
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -3480,7 +3481,7 @@
 OpReturn
 )",
                                       "${op}", param.op);
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), expected);
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), expected);
 }
 INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
                          BuiltinDeriveTest,
@@ -3529,9 +3530,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeInt 32 0
 %6 = OpTypeInt 32 1
@@ -3546,7 +3547,7 @@
 %15 = OpTypePointer StorageBuffer %5
 %19 = OpTypePointer StorageBuffer %6
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(%16 = OpAccessChain %15 %1 %13 %13
@@ -3555,7 +3556,7 @@
 %17 = OpAtomicLoad %6 %20 %12 %13
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -3595,9 +3596,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeInt 32 0
 %6 = OpTypeInt 32 1
@@ -3617,7 +3618,7 @@
 %22 = OpTypePointer StorageBuffer %5
 %27 = OpTypePointer StorageBuffer %6
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(OpStore %12 %11
@@ -3630,7 +3631,7 @@
 OpAtomicStore %28 %11 %20 %29
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -3666,9 +3667,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     std::string expected_types = R"(%5 = OpTypeInt 32 1
 %4 = OpTypeStruct %5
@@ -3685,7 +3686,7 @@
 %17 = OpConstant %15 0
 %19 = OpTypePointer StorageBuffer %5
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     std::string expected_instructions = R"(OpStore %11 %10
@@ -3695,7 +3696,7 @@
     expected_instructions += "%14 = " + GetParam().op + " %5 %20 %16 %17 %21\n";
     expected_instructions += "OpReturn\n";
 
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -3739,9 +3740,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     std::string expected_types = R"(%5 = OpTypeInt 32 0
 %4 = OpTypeStruct %5
@@ -3757,7 +3758,7 @@
 %16 = OpConstant %5 0
 %18 = OpTypePointer StorageBuffer %5
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     std::string expected_instructions = R"(OpStore %11 %10
@@ -3767,7 +3768,7 @@
     expected_instructions += "%14 = " + GetParam().op + " %5 %19 %15 %16 %20\n";
     expected_instructions += "OpReturn\n";
 
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -3817,9 +3818,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeInt 32 0
 %6 = OpTypeInt 32 1
@@ -3840,7 +3841,7 @@
 %23 = OpTypePointer StorageBuffer %5
 %28 = OpTypePointer StorageBuffer %6
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(OpStore %12 %11
@@ -3853,7 +3854,7 @@
 %26 = OpAtomicExchange %6 %29 %20 %21 %30
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -3891,9 +3892,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%5 = OpTypeInt 32 0
 %6 = OpTypeInt 32 1
@@ -3915,7 +3916,7 @@
 %28 = OpConstant %6 20
 %29 = OpConstant %6 10
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(%18 = OpAccessChain %17 %1 %15 %15
@@ -3928,7 +3929,7 @@
 %23 = OpCompositeConstruct %24 %30 %31
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -3953,7 +3954,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     if (pack4) {
         auto got = DumpBuilder(b);
@@ -4024,7 +4025,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     if (pack4) {
         auto got = DumpBuilder(b);
@@ -4092,9 +4093,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
@@ -4102,13 +4103,13 @@
 %7 = OpConstant %6 2
 %8 = OpConstant %6 264
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(OpControlBarrier %7 %7 %8
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -4126,9 +4127,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    ASSERT_EQ(b.functions().size(), 1_u);
+    ASSERT_EQ(b.Module().Functions().size(), 1_u);
 
     auto* expected_types = R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
@@ -4136,13 +4137,13 @@
 %7 = OpConstant %6 2
 %8 = OpConstant %6 72
 )";
-    auto got_types = DumpInstructions(b.types());
+    auto got_types = DumpInstructions(b.Module().Types());
     EXPECT_EQ(expected_types, got_types);
 
     auto* expected_instructions = R"(OpControlBarrier %7 %7 %8
 OpReturn
 )";
-    auto got_instructions = DumpInstructions(b.functions()[0].instructions());
+    auto got_instructions = DumpInstructions(b.Module().Functions()[0].instructions());
     EXPECT_EQ(expected_instructions, got_instructions);
 
     Validate(b);
@@ -4163,7 +4164,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
@@ -4200,7 +4201,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     auto got = DumpBuilder(b);
     auto expect = R"(OpEntryPoint GLCompute %3 "test_function"
diff --git a/src/tint/writer/spirv/builder_builtin_texture_test.cc b/src/tint/writer/spirv/builder_builtin_texture_test.cc
index 4697582..cd17ec1 100644
--- a/src/tint/writer/spirv/builder_builtin_texture_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_texture_test.cc
@@ -3726,16 +3726,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateExpression(call), 8u) << b.error();
+    EXPECT_EQ(b.GenerateExpression(call), 8u) << b.Diagnostics();
 
     auto expected = expected_texture_overload(param.overload);
-    EXPECT_EQ(expected.types, "\n" + DumpInstructions(b.types()));
-    EXPECT_EQ(expected.instructions, "\n" + DumpInstructions(b.functions()[0].instructions()));
-    EXPECT_EQ(expected.capabilities, "\n" + DumpInstructions(b.capabilities()));
+    EXPECT_EQ(expected.types, "\n" + DumpInstructions(b.Module().Types()));
+    EXPECT_EQ(expected.instructions, "\n" + DumpInstructions(b.CurrentFunction().instructions()));
+    EXPECT_EQ(expected.capabilities, "\n" + DumpInstructions(b.Module().Capabilities()));
 }
 
 // Check the SPIRV generated passes validation
@@ -3756,7 +3756,7 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
     Validate(b);
 }
@@ -3781,12 +3781,14 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.error();
+    tint::SetInternalCompilerErrorReporter(nullptr);
+
+    ASSERT_TRUE(b.GenerateGlobalVariable(texture)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(sampler)) << b.Diagnostics();
     EXPECT_EQ(b.GenerateExpression(call), 0u);
-    EXPECT_THAT(b.error(),
-                ::testing::StartsWith("Internal error: trying to add SPIR-V instruction "));
-    EXPECT_THAT(b.error(), ::testing::EndsWith(" outside a function"));
+    EXPECT_THAT(b.Diagnostics().str(),
+                ::testing::HasSubstr("Internal error: trying to add SPIR-V instruction "));
+    EXPECT_THAT(b.Diagnostics().str(), ::testing::HasSubstr(" outside a function"));
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/builder_call_test.cc b/src/tint/writer/spirv/builder_call_test.cc
index a0631ea..b4774cb 100644
--- a/src/tint/writer/spirv/builder_call_test.cc
+++ b/src/tint/writer/spirv/builder_call_test.cc
@@ -36,8 +36,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(a_func)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 OpName %4 "a"
@@ -76,8 +76,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(a_func)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(a_func)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "a_func"
 OpName %4 "a"
diff --git a/src/tint/writer/spirv/builder_const_assert_test.cc b/src/tint/writer/spirv/builder_const_assert_test.cc
index bca6deb..0644c28 100644
--- a/src/tint/writer/spirv/builder_const_assert_test.cc
+++ b/src/tint/writer/spirv/builder_const_assert_test.cc
@@ -27,11 +27,11 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
     // const asserts are not emitted
-    EXPECT_EQ(DumpInstructions(b.types()), "");
-    EXPECT_EQ(b.functions().size(), 0u);
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), "");
+    EXPECT_EQ(b.Module().Functions().size(), 0u);
 }
 
 TEST_F(BuilderTest, FunctionConstAssert) {
@@ -39,13 +39,13 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
     // const asserts are not emitted
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 }
 
diff --git a/src/tint/writer/spirv/builder_constructor_expression_test.cc b/src/tint/writer/spirv/builder_constructor_expression_test.cc
index 29a47ac..7416bc2 100644
--- a/src/tint/writer/spirv/builder_constructor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_constructor_expression_test.cc
@@ -29,9 +29,9 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateConstructorExpression(g, c), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 42.2000008
 )");
 }
@@ -43,9 +43,9 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateConstructorExpression(nullptr, t), 5u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
@@ -59,17 +59,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 4u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstantComposite %1 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_WithAlias) {
@@ -82,13 +82,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 2u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_IdentifierExpression_Param) {
@@ -99,23 +99,23 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
 
     EXPECT_EQ(b.GenerateExpression(t), 8u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Function %3
 %4 = OpConstantNull %3
 %5 = OpTypeVector %3 2
 %6 = OpConstant %3 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%1 = OpVariable %2 Function %4
 )");
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %8 = OpCompositeConstruct %5 %6 %7
 )");
@@ -128,11 +128,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-    ASSERT_EQ(b.GenerateExpression(cast), 10u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
+    ASSERT_EQ(b.GenerateExpression(cast), 10u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -143,7 +143,7 @@
 %12 = OpTypeInt 32 0
 %11 = OpTypeVector %12 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %7 %6
 %13 = OpLoad %1 %7
 %10 = OpBitcast %11 %13
@@ -156,15 +156,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(cast), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_I32_With_I32) {
@@ -173,13 +173,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 2u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_U32_With_U32) {
@@ -188,13 +188,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 2u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_F32_With_F32) {
@@ -203,13 +203,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 2u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_F16_With_F16) {
@@ -220,13 +220,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 2u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Literal) {
@@ -235,15 +235,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeVector %2 2
 %3 = OpConstantTrue %2
 %4 = OpConstantComposite %1 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_Bool_Var) {
@@ -253,17 +253,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-    ASSERT_EQ(b.GenerateExpression(cast), 8u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
+    ASSERT_EQ(b.GenerateExpression(cast), 8u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpCompositeConstruct %6 %7 %7
@@ -276,15 +276,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F16_Literal) {
@@ -295,15 +295,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F32_F32) {
@@ -313,17 +313,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 9u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -340,17 +340,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 9u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -364,16 +364,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstantComposite %1 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_With_F16_F16_Const) {
@@ -384,16 +384,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstantComposite %1 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_F32_With_Vec2) {
@@ -403,11 +403,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -415,7 +415,7 @@
 %7 = OpTypePointer Function %1
 %8 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 )");
@@ -430,11 +430,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -442,7 +442,7 @@
 %7 = OpTypePointer Function %1
 %8 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 )");
@@ -454,16 +454,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstantComposite %1 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec2_F16_With_Vec2_Const) {
@@ -474,16 +474,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstantComposite %1 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32) {
@@ -493,17 +493,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -521,17 +521,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -546,17 +546,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstant %2 3
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F16_Const) {
@@ -567,17 +567,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstant %2 0x1.8p+1
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Bool) {
@@ -587,17 +587,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -612,16 +612,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeVector %2 3
 %3 = OpConstantTrue %2
 %4 = OpConstantNull %2
 %5 = OpConstantComposite %1 %3 %4 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_F32_F32) {
@@ -631,17 +631,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -659,17 +659,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 10u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -684,17 +684,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstant %2 3
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F16_F16_F16_Const) {
@@ -705,17 +705,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstant %2 0x1.8p+1
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F32_Vec2) {
@@ -725,11 +725,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 14u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 2
 %4 = OpConstant %2 3
@@ -739,7 +739,7 @@
 %9 = OpTypeVector %2 3
 %10 = OpConstant %2 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %11 = OpLoad %1 %6
 %12 = OpCompositeExtract %2 %11 0
@@ -757,11 +757,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 14u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstant %2 0x1.8p+1
@@ -771,7 +771,7 @@
 %9 = OpTypeVector %2 3
 %10 = OpConstant %2 0x1p+0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %11 = OpLoad %1 %6
 %12 = OpCompositeExtract %2 %11 0
@@ -786,17 +786,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstant %2 3
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_F16_Vec2_Const) {
@@ -807,17 +807,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstant %2 0x1.8p+1
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Vec2_F32) {
@@ -827,11 +827,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 14u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -841,7 +841,7 @@
 %9 = OpTypeVector %2 3
 %13 = OpConstant %2 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -859,11 +859,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 14u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -873,7 +873,7 @@
 %9 = OpTypeVector %2 3
 %13 = OpConstant %2 0x1.8p+1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -888,17 +888,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstant %2 3
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_With_Vec2_F16_Const) {
@@ -909,17 +909,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstant %2 0x1.8p+1
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_F32_With_Vec3) {
@@ -929,11 +929,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 11u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -942,7 +942,7 @@
 %8 = OpTypePointer Function %1
 %9 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %7 %6
 %11 = OpLoad %1 %7
 )");
@@ -957,11 +957,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 11u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -970,7 +970,7 @@
 %8 = OpTypePointer Function %1
 %9 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %7 %6
 %11 = OpLoad %1 %7
 )");
@@ -982,17 +982,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstant %2 3
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec3_F16_With_Vec3_Const) {
@@ -1003,17 +1003,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstant %2 0x1.8p+1
 %6 = OpConstantComposite %1 %3 %4 %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Bool) {
@@ -1023,17 +1023,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 8u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpCompositeConstruct %6 %7 %7 %7 %7
@@ -1046,15 +1046,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeVector %2 4
 %3 = OpConstantTrue %2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32) {
@@ -1064,17 +1064,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 8u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpCompositeConstruct %6 %7 %7 %7 %7
@@ -1090,17 +1090,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 8u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpCompositeConstruct %6 %7 %7 %7 %7
@@ -1113,15 +1113,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F16_Const) {
@@ -1132,15 +1132,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_F32_F32) {
@@ -1150,17 +1150,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 11u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -1179,17 +1179,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 11u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %6 = OpTypeVector %1 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %7 = OpLoad %1 %3
 %8 = OpLoad %1 %3
@@ -1205,10 +1205,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1216,7 +1216,7 @@
 %6 = OpConstant %2 4
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F16_F16_F16_F16_Const) {
@@ -1227,10 +1227,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1238,7 +1238,7 @@
 %6 = OpConstant %2 0x1p+2
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_F32_Vec2) {
@@ -1248,11 +1248,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 13u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1261,7 +1261,7 @@
 %8 = OpConstantNull %1
 %9 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -1279,11 +1279,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 13u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1292,7 +1292,7 @@
 %8 = OpConstantNull %1
 %9 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -1307,10 +1307,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1318,7 +1318,7 @@
 %6 = OpConstant %2 4
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F16_F16_Vec2_Const) {
@@ -1329,10 +1329,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1340,7 +1340,7 @@
 %6 = OpConstant %2 0x1p+2
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_Vec2_F32) {
@@ -1350,11 +1350,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 15u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 2
 %4 = OpConstant %2 3
@@ -1365,7 +1365,7 @@
 %10 = OpConstant %2 1
 %14 = OpConstant %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %11 = OpLoad %1 %6
 %12 = OpCompositeExtract %2 %11 0
@@ -1383,11 +1383,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 15u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstant %2 0x1.8p+1
@@ -1398,7 +1398,7 @@
 %10 = OpConstant %2 0x1p+0
 %14 = OpConstant %2 0x1p+2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %11 = OpLoad %1 %6
 %12 = OpCompositeExtract %2 %11 0
@@ -1413,10 +1413,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1424,7 +1424,7 @@
 %6 = OpConstant %2 4
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F16_Vec2_F16_Const) {
@@ -1435,10 +1435,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1446,7 +1446,7 @@
 %6 = OpConstant %2 0x1p+2
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec2_F32_F32) {
@@ -1456,11 +1456,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 15u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1471,7 +1471,7 @@
 %13 = OpConstant %2 3
 %14 = OpConstant %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -1489,11 +1489,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 15u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1504,7 +1504,7 @@
 %13 = OpConstant %2 0x1.8p+1
 %14 = OpConstant %2 0x1p+2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -1519,10 +1519,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1530,7 +1530,7 @@
 %6 = OpConstant %2 4
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec2_F16_F16_Const) {
@@ -1541,10 +1541,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 7u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1552,7 +1552,7 @@
 %6 = OpConstant %2 0x1p+2
 %7 = OpConstantComposite %1 %3 %4 %5 %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_F32_With_Vec2_Vec2) {
@@ -1562,11 +1562,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 16u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -1575,7 +1575,7 @@
 %8 = OpConstantNull %1
 %9 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -1596,11 +1596,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 16u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -1609,7 +1609,7 @@
 %8 = OpConstantNull %1
 %9 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 %10 = OpLoad %1 %6
 %11 = OpCompositeExtract %2 %10 0
@@ -1627,16 +1627,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
 %5 = OpConstantComposite %1 %3 %4 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_F16_With_Vec2_Vec2_Const) {
@@ -1647,16 +1647,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 5u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
 %5 = OpConstantComposite %1 %3 %4 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F32_Vec3) {
@@ -1666,11 +1666,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 13u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -1678,7 +1678,7 @@
 %7 = OpConstantNull %1
 %8 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %5 %4
 %9 = OpLoad %1 %5
 %10 = OpCompositeExtract %2 %9 0
@@ -1697,11 +1697,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 13u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -1709,7 +1709,7 @@
 %7 = OpConstantNull %1
 %8 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %5 %4
 %9 = OpLoad %1 %5
 %10 = OpCompositeExtract %2 %9 0
@@ -1725,15 +1725,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_F16_Vec3_Const) {
@@ -1744,15 +1744,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec3_F32) {
@@ -1762,11 +1762,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 13u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -1774,7 +1774,7 @@
 %7 = OpConstantNull %1
 %8 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %5 %4
 %9 = OpLoad %1 %5
 %10 = OpCompositeExtract %2 %9 0
@@ -1793,11 +1793,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateStatement(var));
     EXPECT_EQ(b.GenerateExpression(cast), 13u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -1805,7 +1805,7 @@
 %7 = OpConstantNull %1
 %8 = OpTypeVector %2 4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %5 %4
 %9 = OpLoad %1 %5
 %10 = OpCompositeExtract %2 %9 0
@@ -1821,15 +1821,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_With_Vec3_F16_Const) {
@@ -1840,15 +1840,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_F32_With_Vec4) {
@@ -1858,15 +1858,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_Vec4_F16_With_Vec4) {
@@ -1878,15 +1878,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"()");
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"()");
 }
 
 TEST_F(SpvBuilderConstructorTest, Type_GlobalConst_F32_With_F32) {
@@ -1897,14 +1897,14 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeFloat 32
 %6 = OpConstant %5 2
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %7 %6
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %7 %6
 OpReturn
 )");
     Validate(b);
@@ -1920,14 +1920,14 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeFloat 16
 %6 = OpConstant %5 0x1p+1
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %7 %6
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %7 %6
 OpReturn
 )");
     Validate(b);
@@ -1940,7 +1940,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Private %1
 %3 = OpVariable %4 Private %2
@@ -1959,7 +1959,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Private %1
 %3 = OpVariable %4 Private %2
@@ -1977,14 +1977,14 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeInt 32 0
 %6 = OpConstant %5 1
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %7 %6
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %7 %6
 OpReturn
 )");
     Validate(b);
@@ -2000,14 +2000,14 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeInt 32 0
 %6 = OpConstant %5 1
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %7 %6
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %7 %6
 OpReturn
 )");
     Validate(b);
@@ -2020,7 +2020,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 1
 %4 = OpTypePointer Private %1
 %3 = OpVariable %4 Private %2
@@ -2039,7 +2039,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 1
 %4 = OpTypePointer Private %1
 %3 = OpVariable %4 Private %2
@@ -2057,7 +2057,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 2
@@ -2066,7 +2066,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2082,7 +2082,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 2
@@ -2091,7 +2091,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2103,10 +2103,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3
@@ -2121,10 +2121,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3
@@ -2139,7 +2139,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 2
@@ -2148,7 +2148,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2164,7 +2164,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 2
@@ -2173,7 +2173,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2186,7 +2186,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3
@@ -2208,7 +2208,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 2
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3
@@ -2229,7 +2229,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 3
@@ -2238,7 +2238,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2254,7 +2254,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 3
@@ -2263,7 +2263,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2276,7 +2276,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2298,7 +2298,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2319,7 +2319,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -2328,7 +2328,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2344,7 +2344,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -2353,7 +2353,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2366,7 +2366,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2388,7 +2388,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2409,7 +2409,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 3
@@ -2418,7 +2418,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2434,7 +2434,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 3
@@ -2443,7 +2443,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2455,10 +2455,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2473,10 +2473,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2491,7 +2491,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 3
@@ -2500,7 +2500,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2516,7 +2516,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 3
@@ -2525,7 +2525,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2537,10 +2537,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2555,10 +2555,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2573,7 +2573,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 3
@@ -2582,7 +2582,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2598,7 +2598,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 3
@@ -2607,7 +2607,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2619,10 +2619,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2637,10 +2637,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -2655,7 +2655,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -2664,7 +2664,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2680,7 +2680,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -2689,7 +2689,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2701,10 +2701,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2719,10 +2719,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2737,7 +2737,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -2746,7 +2746,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2762,7 +2762,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -2771,7 +2771,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2783,10 +2783,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2801,10 +2801,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2819,7 +2819,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -2828,7 +2828,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2844,7 +2844,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -2853,7 +2853,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2865,10 +2865,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2883,10 +2883,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2901,7 +2901,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -2910,7 +2910,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2926,7 +2926,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -2935,7 +2935,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -2947,10 +2947,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2965,10 +2965,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -2983,7 +2983,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -2992,7 +2992,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -3008,7 +3008,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -3017,7 +3017,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -3029,10 +3029,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -3047,10 +3047,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -3065,7 +3065,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -3074,7 +3074,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -3086,10 +3086,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -3104,10 +3104,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -3122,7 +3122,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 32
 %5 = OpTypeVector %6 4
@@ -3131,7 +3131,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -3147,7 +3147,7 @@
     spirv::Builder& b = SanitizeAndBuild();
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %6 = OpTypeFloat 16
 %5 = OpTypeVector %6 4
@@ -3156,7 +3156,7 @@
 %10 = OpTypePointer Function %5
 %11 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %9 %8
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpStore %9 %8
 OpReturn
 )");
     Validate(b);
@@ -3168,10 +3168,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -3186,10 +3186,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateConstructorExpression(g, cast), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 4
 %3 = OpConstant %2 0x1p+1
 %4 = OpConstantComposite %1 %3 %3 %3 %3
@@ -3202,10 +3202,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 2
 %4 = OpConstant %3 2
@@ -3222,10 +3222,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 2
 %4 = OpConstant %3 0x1p+1
@@ -3240,10 +3240,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 3
 %4 = OpConstant %3 2
@@ -3260,10 +3260,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 3
 %4 = OpConstant %3 0x1p+1
@@ -3279,10 +3279,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 4
 %4 = OpConstant %3 2
@@ -3300,10 +3300,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 4
 %4 = OpConstant %3 0x1p+1
@@ -3318,10 +3318,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 2
 %4 = OpConstant %3 2
@@ -3338,10 +3338,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 2
 %4 = OpConstant %3 0x1p+1
@@ -3357,10 +3357,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 3
 %4 = OpConstant %3 2
@@ -3378,10 +3378,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 3
 %4 = OpConstant %3 0x1p+1
@@ -3397,10 +3397,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 4
 %4 = OpConstant %3 2
@@ -3418,10 +3418,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 4
 %4 = OpConstant %3 0x1p+1
@@ -3436,10 +3436,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 4
 %1 = OpTypeMatrix %2 2
 %4 = OpConstant %3 2
@@ -3456,10 +3456,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 4
 %1 = OpTypeMatrix %2 2
 %4 = OpConstant %3 0x1p+1
@@ -3475,10 +3475,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 4
 %1 = OpTypeMatrix %2 3
 %4 = OpConstant %3 2
@@ -3496,10 +3496,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 4
 %1 = OpTypeMatrix %2 3
 %4 = OpConstant %3 0x1p+1
@@ -3515,10 +3515,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 4
 %1 = OpTypeMatrix %2 4
 %4 = OpConstant %3 2
@@ -3536,10 +3536,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 4
 %1 = OpTypeMatrix %2 4
 %4 = OpConstant %3 0x1p+1
@@ -3554,10 +3554,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 5
 %1 = OpTypeArray %2 %4
@@ -3574,10 +3574,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(cast), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 5
 %1 = OpTypeArray %2 %4
@@ -3593,9 +3593,9 @@
     WrapInFunction(t);
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(t), 10u);
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 3
 %4 = OpTypeInt 32 0
 %5 = OpConstant %4 2
@@ -3617,9 +3617,9 @@
     WrapInFunction(t);
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(t), 10u);
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 3
 %4 = OpTypeInt 32 0
 %5 = OpConstant %4 2
@@ -3639,11 +3639,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(v1), 4u);
     EXPECT_EQ(b.GenerateExpression(v2), 4u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 2
 %4 = OpConstantComposite %1 %3 %3 %3
@@ -3657,11 +3657,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(a1), 6u);
     EXPECT_EQ(b.GenerateExpression(a2), 6u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 3
 %1 = OpTypeArray %2 %4
@@ -3679,11 +3679,11 @@
     WrapInFunction(WrapInStatement(a1), WrapInStatement(a2));
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_EQ(b.GenerateExpression(a1), 7u);
     EXPECT_EQ(b.GenerateExpression(a2), 9u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 2
 %1 = OpTypeArray %2 %4
@@ -3706,12 +3706,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 6u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpTypeVector %2 3
 %1 = OpTypeStruct %2 %3
 %4 = OpConstant %2 2
@@ -3727,12 +3727,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstantNull %1
 )");
 }
@@ -3746,12 +3746,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstantNull %1
 )");
 }
@@ -3763,12 +3763,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstantNull %1
 )");
 }
@@ -3780,12 +3780,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstantNull %1
 )");
 }
@@ -3797,12 +3797,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantNull %1
 )");
 }
@@ -3814,12 +3814,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 3u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeVector %2 2
 %3 = OpConstantNull %1
 )");
@@ -3832,12 +3832,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 4u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 4
 %4 = OpConstantNull %1
@@ -3853,12 +3853,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 4u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 2
 %1 = OpTypeMatrix %2 4
 %4 = OpConstantNull %1
@@ -3872,12 +3872,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 5u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 2
 %1 = OpTypeArray %2 %4
@@ -3892,12 +3892,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
     EXPECT_EQ(b.GenerateExpression(t), 3u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeStruct %2
 %3 = OpConstantNull %1
 )");
@@ -3910,17 +3910,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeInt 32 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpBitcast %7 %8
@@ -3934,17 +3934,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2.4000001
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeInt 32 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertFToS %7 %8
@@ -3960,17 +3960,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1.33p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeInt 32 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertFToS %7 %8
@@ -3984,17 +3984,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeInt 32 0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpBitcast %7 %8
@@ -4008,17 +4008,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2.4000001
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeInt 32 0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertFToU %7 %8
@@ -4034,17 +4034,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1.33p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeInt 32 0
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertFToU %7 %8
@@ -4058,17 +4058,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeFloat 32
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertSToF %7 %8
@@ -4082,17 +4082,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeFloat 32
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertUToF %7 %8
@@ -4108,17 +4108,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1p+1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeFloat 32
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpFConvert %7 %8
@@ -4134,17 +4134,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeFloat 16
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertSToF %7 %8
@@ -4160,17 +4160,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeFloat 16
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpConvertUToF %7 %8
@@ -4186,17 +4186,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 2
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 %7 = OpTypeFloat 16
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %8 = OpLoad %1 %3
 %6 = OpFConvert %7 %8
@@ -4211,11 +4211,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 0
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4223,7 +4223,7 @@
 %8 = OpTypeInt 32 1
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpBitcast %7 %9
 )");
@@ -4237,11 +4237,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4249,7 +4249,7 @@
 %8 = OpTypeInt 32 1
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertFToS %7 %9
 )");
@@ -4265,11 +4265,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 16
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4277,7 +4277,7 @@
 %8 = OpTypeInt 32 1
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertFToS %7 %9
 )");
@@ -4291,11 +4291,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 1
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4303,7 +4303,7 @@
 %8 = OpTypeInt 32 0
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpBitcast %7 %9
 )");
@@ -4317,11 +4317,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4329,7 +4329,7 @@
 %8 = OpTypeInt 32 0
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertFToU %7 %9
 )");
@@ -4345,11 +4345,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 16
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4357,7 +4357,7 @@
 %8 = OpTypeInt 32 0
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertFToU %7 %9
 )");
@@ -4371,11 +4371,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 1
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4383,7 +4383,7 @@
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertSToF %7 %9
 )");
@@ -4397,11 +4397,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 0
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4409,7 +4409,7 @@
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertUToF %7 %9
 )");
@@ -4425,11 +4425,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 16
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4437,7 +4437,7 @@
 %8 = OpTypeFloat 32
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpFConvert %7 %9
 )");
@@ -4453,11 +4453,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 1
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4465,7 +4465,7 @@
 %8 = OpTypeFloat 16
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertSToF %7 %9
 )");
@@ -4481,11 +4481,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 0
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4493,7 +4493,7 @@
 %8 = OpTypeFloat 16
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpConvertUToF %7 %9
 )");
@@ -4509,11 +4509,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateExpression(cast), 6u) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Private %3
 %5 = OpConstantNull %3
@@ -4521,7 +4521,7 @@
 %8 = OpTypeFloat 16
 %7 = OpTypeVector %8 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%9 = OpLoad %3 %1
 %6 = OpFConvert %7 %9
 )");
diff --git a/src/tint/writer/spirv/builder_discard_test.cc b/src/tint/writer/spirv/builder_discard_test.cc
index 747b85c..f5c2f8a 100644
--- a/src/tint/writer/spirv/builder_discard_test.cc
+++ b/src/tint/writer/spirv/builder_discard_test.cc
@@ -28,9 +28,9 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateStatement(stmt)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpKill
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateStatement(stmt)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"(OpKill
 )");
 }
 
diff --git a/src/tint/writer/spirv/builder_entry_point_test.cc b/src/tint/writer/spirv/builder_entry_point_test.cc
index 085a6f2..0e057a3 100644
--- a/src/tint/writer/spirv/builder_entry_point_test.cc
+++ b/src/tint/writer/spirv/builder_entry_point_test.cc
@@ -233,7 +233,7 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
@@ -330,14 +330,14 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
     // Make sure we generate the SampleRateShading capability.
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
               R"(OpCapability Shader
 OpCapability SampleRateShading
 )");
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 BuiltIn SampleId
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %1 BuiltIn SampleId
 OpDecorate %1 Flat
 )");
 }
diff --git a/src/tint/writer/spirv/builder_format_conversion_test.cc b/src/tint/writer/spirv/builder_format_conversion_test.cc
index 1efdcca..6bd075d 100644
--- a/src/tint/writer/spirv/builder_format_conversion_test.cc
+++ b/src/tint/writer/spirv/builder_format_conversion_test.cc
@@ -39,11 +39,11 @@
     EXPECT_EQ(b.convert_texel_format_to_spv(param.ast_format), param.spv_format);
 
     if (param.extended_format) {
-        EXPECT_EQ(DumpInstructions(b.capabilities()),
+        EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
                   R"(OpCapability StorageImageExtendedFormats
 )");
     } else {
-        EXPECT_EQ(DumpInstructions(b.capabilities()), "");
+        EXPECT_EQ(DumpInstructions(b.Module().Capabilities()), "");
     }
 }
 
diff --git a/src/tint/writer/spirv/builder_function_attribute_test.cc b/src/tint/writer/spirv/builder_function_attribute_test.cc
index 1b23982..c215f21 100644
--- a/src/tint/writer/spirv/builder_function_attribute_test.cc
+++ b/src/tint/writer/spirv/builder_function_attribute_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/writer/spirv/spv_dump.h"
@@ -32,8 +33,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.entry_points()),
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().EntryPoints()),
               R"(OpEntryPoint Fragment %3 "main"
 )");
 }
@@ -72,11 +73,11 @@
     spirv::Builder& b = Build();
 
     if (var) {
-        ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+        ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
     }
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
-    auto preamble = b.entry_points();
+    auto preamble = b.Module().EntryPoints();
     ASSERT_GE(preamble.size(), 1u);
     EXPECT_EQ(preamble[0].opcode(), spv::Op::OpEntryPoint);
 
@@ -98,8 +99,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.execution_modes()),
+    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().ExecutionModes()),
               R"(OpExecutionMode %3 OriginUpperLeft
 )");
 }
@@ -110,8 +111,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.execution_modes()),
+    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().ExecutionModes()),
               R"(OpExecutionMode %3 LocalSize 1 1 1
 )");
 }
@@ -125,8 +126,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.execution_modes()),
+    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().ExecutionModes()),
               R"(OpExecutionMode %3 LocalSize 2 4 6
 )");
 }
@@ -143,8 +144,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.execution_modes()),
+    ASSERT_TRUE(b.GenerateExecutionModes(func, 3)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().ExecutionModes()),
               R"(OpExecutionMode %3 LocalSize 2 3 4
 )");
 }
@@ -161,10 +162,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_FALSE(b.GenerateExecutionModes(func, 3)) << b.error();
-    EXPECT_EQ(
-        b.error(),
-        R"(override-expressions should have been removed with the SubstituteOverride transform)");
+    tint::SetInternalCompilerErrorReporter(nullptr);
+
+    EXPECT_FALSE(b.GenerateExecutionModes(func, 3)) << b.Diagnostics();
+    EXPECT_THAT(
+        b.Diagnostics().str(),
+        ::testing::HasSubstr(
+            "override-expressions should have been removed with the SubstituteOverride transform"));
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_WorkgroupSize_LiteralAndConst) {
@@ -178,10 +182,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_FALSE(b.GenerateExecutionModes(func, 3)) << b.error();
-    EXPECT_EQ(
-        b.error(),
-        R"(override-expressions should have been removed with the SubstituteOverride transform)");
+    tint::SetInternalCompilerErrorReporter(nullptr);
+
+    EXPECT_FALSE(b.GenerateExecutionModes(func, 3)) << b.Diagnostics();
+    EXPECT_THAT(
+        b.Diagnostics().str(),
+        ::testing::HasSubstr(
+            "override-expressions should have been removed with the SubstituteOverride transform"));
 }
 
 TEST_F(BuilderTest, Decoration_ExecutionMode_MultipleFragment) {
@@ -197,8 +204,8 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateFunction(func1)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func2)) << b.error();
+    ASSERT_TRUE(b.GenerateFunction(func1)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func2)) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b),
               R"(OpEntryPoint Fragment %3 "main1"
 OpEntryPoint Fragment %5 "main2"
@@ -235,7 +242,7 @@
 
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.execution_modes()),
+    EXPECT_EQ(DumpInstructions(b.Module().ExecutionModes()),
               R"(OpExecutionMode %11 OriginUpperLeft
 OpExecutionMode %11 DepthReplacing
 )");
diff --git a/src/tint/writer/spirv/builder_function_test.cc b/src/tint/writer/spirv/builder_function_test.cc
index 5cf97ea..04b5a84 100644
--- a/src/tint/writer/spirv/builder_function_test.cc
+++ b/src/tint/writer/spirv/builder_function_test.cc
@@ -70,8 +70,8 @@
     auto* var_a = program->AST().GlobalVariables()[0];
     auto* func = program->AST().Functions()[0];
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var_a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "a"
 OpName %6 "a_func"
 %3 = OpTypeFloat 32
@@ -161,7 +161,7 @@
 
     auto* func = program->AST().Functions()[0];
     ASSERT_TRUE(b.GenerateFunction(func));
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 )");
 }
@@ -174,7 +174,7 @@
 
     ASSERT_TRUE(b.GenerateFunction(func1));
     ASSERT_TRUE(b.GenerateFunction(func2));
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 )");
 }
diff --git a/src/tint/writer/spirv/builder_function_variable_test.cc b/src/tint/writer/spirv/builder_function_variable_test.cc
index 72cf12b..9f77d4e 100644
--- a/src/tint/writer/spirv/builder_function_variable_test.cc
+++ b/src/tint/writer/spirv/builder_function_variable_test.cc
@@ -28,16 +28,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Function %3
 %4 = OpConstantNull %3
 )");
 
-    const auto& func = b.functions()[0];
+    const auto& func = b.CurrentFunction();
     EXPECT_EQ(DumpInstructions(func.variables()),
               R"(%1 = OpVariable %2 Function %4
 )");
@@ -50,13 +50,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %6 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
@@ -64,10 +64,10 @@
 %7 = OpTypePointer Function %1
 %8 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%6 = OpVariable %7 Function %8
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %6 %5
 )");
 }
@@ -81,24 +81,24 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(a)) << b.error();
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(a)) << b.Diagnostics();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %7 "var"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %7 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 3
 %3 = OpTypeVector %1 2
 %4 = OpConstant %1 1
 %8 = OpTypePointer Function %3
 %9 = OpConstantNull %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%7 = OpVariable %8 Function %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%5 = OpFAdd %1 %2 %2
 %6 = OpCompositeConstruct %3 %4 %5
 OpStore %7 %6
@@ -116,24 +116,24 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "v"
 OpName %7 "v2"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%3 = OpVariable %4 Function %5
 %7 = OpVariable %4 Function %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %6 = OpLoad %1 %3
 OpStore %7 %6
@@ -151,24 +151,24 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "v"
 OpName %7 "v2"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%3 = OpVariable %4 Function %5
 %7 = OpVariable %4 Function %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 %6 = OpLoad %1 %3
 OpStore %7 %6
@@ -186,22 +186,22 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v2)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "v2"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "v2"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 1
 %4 = OpTypePointer Function %1
 %5 = OpConstantNull %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%3 = OpVariable %4 Function %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpStore %3 %2
 )");
 }
@@ -215,10 +215,10 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
@@ -235,10 +235,10 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), "");  // Not a mistake - 'const' is inlined
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), "");  // Not a mistake - 'const' is inlined
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/builder_global_variable_test.cc b/src/tint/writer/spirv/builder_global_variable_test.cc
index 569d5e9..671bacc 100644
--- a/src/tint/writer/spirv/builder_global_variable_test.cc
+++ b/src/tint/writer/spirv/builder_global_variable_test.cc
@@ -30,10 +30,10 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -47,12 +47,12 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %6 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
@@ -71,17 +71,17 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 42
 %4 = OpTypePointer Private %1
 %3 = OpVariable %4 Private %2
 %6 = OpTypeVoid
 %5 = OpTypeFunction %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 
     Validate(b);
@@ -96,9 +96,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -109,8 +109,8 @@
 %10 = OpTypeVoid
 %9 = OpTypeFunction %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 
     Validate(b);
@@ -126,9 +126,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 16
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 0x1p+0
 %4 = OpConstant %2 0x1p+1
@@ -139,8 +139,8 @@
 %10 = OpTypeVoid
 %9 = OpTypeFunction %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 
     Validate(b);
@@ -155,9 +155,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -168,8 +168,8 @@
 %10 = OpTypeVoid
 %9 = OpTypeFunction %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 
     Validate(b);
@@ -184,9 +184,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -197,8 +197,8 @@
 %10 = OpTypeVoid
 %9 = OpTypeFunction %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 
     Validate(b);
@@ -213,9 +213,9 @@
 
     spirv::Builder& b = SanitizeAndBuild();
 
-    ASSERT_TRUE(b.Build()) << b.error();
+    ASSERT_TRUE(b.Build()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 2
@@ -226,8 +226,8 @@
 %10 = OpTypeVoid
 %9 = OpTypeFunction %10
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"()");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()), R"(OpReturn
 )");
 
     Validate(b);
@@ -238,13 +238,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Binding 2
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %1 Binding 2
 OpDecorate %1 DescriptorSet 3
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeSampler
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeSampler
 %2 = OpTypePointer UniformConstant %3
 %1 = OpVariable %2 UniformConstant
 )");
@@ -324,7 +324,7 @@
 
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %3 Block
 OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %4 0 Offset 0
 OpMemberDecorate %4 1 Offset 4
@@ -332,7 +332,7 @@
 OpDecorate %1 Binding 0
 OpDecorate %1 DescriptorSet 0
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "b_block"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "b_block"
 OpMemberName %3 0 "inner"
 OpName %4 "A"
 OpMemberName %4 0 "a"
@@ -340,7 +340,7 @@
 OpName %1 "b"
 OpName %8 "unused_entry_point"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeInt 32 1
 %4 = OpTypeStruct %5 %5
 %3 = OpTypeStruct %4
 %2 = OpTypePointer StorageBuffer %3
@@ -366,21 +366,21 @@
 
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %3 Block
 OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %4 0 Offset 0
 OpDecorate %1 NonWritable
 OpDecorate %1 Binding 0
 OpDecorate %1 DescriptorSet 0
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "b_block"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "b_block"
 OpMemberName %3 0 "inner"
 OpName %4 "A"
 OpMemberName %4 0 "a"
 OpName %1 "b"
 OpName %8 "unused_entry_point"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeInt 32 1
 %4 = OpTypeStruct %5
 %3 = OpTypeStruct %4
 %2 = OpTypePointer StorageBuffer %3
@@ -406,21 +406,21 @@
 
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %3 Block
 OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %4 0 Offset 0
 OpDecorate %1 NonWritable
 OpDecorate %1 Binding 0
 OpDecorate %1 DescriptorSet 0
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "b_block"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "b_block"
 OpMemberName %3 0 "inner"
 OpName %4 "A"
 OpMemberName %4 0 "a"
 OpName %1 "b"
 OpName %8 "unused_entry_point"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeInt 32 1
 %4 = OpTypeStruct %5
 %3 = OpTypeStruct %4
 %2 = OpTypePointer StorageBuffer %3
@@ -447,7 +447,7 @@
 
     ASSERT_TRUE(b.Build());
 
-    EXPECT_EQ(DumpInstructions(b.annots()),
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()),
               R"(OpDecorate %3 Block
 OpMemberDecorate %3 0 Offset 0
 OpMemberDecorate %4 0 Offset 0
@@ -457,7 +457,7 @@
 OpDecorate %6 DescriptorSet 1
 OpDecorate %6 Binding 0
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "b_block"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %3 "b_block"
 OpMemberName %3 0 "inner"
 OpName %4 "A"
 OpMemberName %4 0 "a"
@@ -465,7 +465,7 @@
 OpName %6 "c"
 OpName %9 "unused_entry_point"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeInt 32 1
 %4 = OpTypeStruct %5
 %3 = OpTypeStruct %4
 %2 = OpTypePointer StorageBuffer %3
@@ -486,13 +486,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
+    EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %1 NonReadable
 OpDecorate %1 Binding 0
 OpDecorate %1 DescriptorSet 0
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeInt 32 0
 %3 = OpTypeImage %4 2D 0 0 0 2 R32ui
 %2 = OpTypePointer UniformConstant %3
 %1 = OpVariable %2 UniformConstant
@@ -518,12 +518,12 @@
     std::unique_ptr<spirv::Builder> b =
         std::make_unique<spirv::Builder>(program.get(), kZeroInitializeWorkgroupMemory);
 
-    EXPECT_TRUE(b->GenerateGlobalVariable(var_scalar)) << b->error();
-    EXPECT_TRUE(b->GenerateGlobalVariable(var_array)) << b->error();
-    EXPECT_TRUE(b->GenerateGlobalVariable(var_struct)) << b->error();
-    ASSERT_FALSE(b->has_error()) << b->error();
+    EXPECT_TRUE(b->GenerateGlobalVariable(var_scalar)) << b->Diagnostics();
+    EXPECT_TRUE(b->GenerateGlobalVariable(var_array)) << b->Diagnostics();
+    EXPECT_TRUE(b->GenerateGlobalVariable(var_struct)) << b->Diagnostics();
+    ASSERT_FALSE(b->has_error()) << b->Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b->types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b->Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Workgroup %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Workgroup %4
diff --git a/src/tint/writer/spirv/builder_ident_expression_test.cc b/src/tint/writer/spirv/builder_ident_expression_test.cc
index 7c56ba1..3917028 100644
--- a/src/tint/writer/spirv/builder_ident_expression_test.cc
+++ b/src/tint/writer/spirv/builder_ident_expression_test.cc
@@ -32,10 +32,10 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"()");
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"()");
 
     EXPECT_EQ(b.GenerateIdentifierExpression(expr), 0u);
 }
@@ -48,11 +48,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -71,10 +71,10 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
@@ -91,16 +91,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "var"
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(v)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "var"
 )");
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Function %3
 %4 = OpConstantNull %3
 )");
 
-    const auto& func = b.functions()[0];
+    const auto& func = b.CurrentFunction();
     EXPECT_EQ(DumpInstructions(func.variables()),
               R"(%1 = OpVariable %2 Function %4
 )");
@@ -115,16 +115,16 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 7u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 7u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%5 = OpLoad %3 %1
 %6 = OpLoad %3 %1
 %7 = OpIAdd %3 %5 %6
@@ -138,14 +138,14 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateFunctionVariable(let)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateFunctionVariable(let)) << b.Diagnostics();
 
-    EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 3u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(b.GenerateBinaryExpression(expr->As<ast::BinaryExpression>()), 3u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%3 = OpIAdd %1 %2 %2
 )");
 }
diff --git a/src/tint/writer/spirv/builder_if_test.cc b/src/tint/writer/spirv/builder_if_test.cc
index 031ed68..3bd4bb2 100644
--- a/src/tint/writer/spirv/builder_if_test.cc
+++ b/src/tint/writer/spirv/builder_if_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "src/tint/writer/spirv/spv_dump.h"
 #include "src/tint/writer/spirv/test_helper.h"
 
@@ -30,13 +31,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpSelectionMerge %3 None
 OpBranchConditional %2 %4 %3
 %4 = OpLabel
@@ -56,11 +57,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_FALSE(b.GenerateIfStatement(expr)) << b.error();
+    tint::SetInternalCompilerErrorReporter(nullptr);
+
+    EXPECT_FALSE(b.GenerateIfStatement(expr)) << b.Diagnostics();
     EXPECT_TRUE(b.has_error());
-    EXPECT_EQ(b.error(),
-              "Internal error: trying to add SPIR-V instruction 247 outside a "
-              "function");
+    EXPECT_THAT(b.Diagnostics().str(),
+                ::testing::HasSubstr(
+                    "Internal error: trying to add SPIR-V instruction 247 outside a function"));
 }
 
 TEST_F(BuilderTest, If_WithStatements) {
@@ -75,11 +78,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -87,7 +90,7 @@
 %6 = OpConstantTrue %5
 %9 = OpConstant %3 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpSelectionMerge %7 None
 OpBranchConditional %6 %8 %7
 %8 = OpLabel
@@ -113,11 +116,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -126,7 +129,7 @@
 %10 = OpConstant %3 2
 %11 = OpConstant %3 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpSelectionMerge %7 None
 OpBranchConditional %6 %8 %9
 %8 = OpLabel
@@ -155,11 +158,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -168,7 +171,7 @@
 %10 = OpConstant %3 2
 %13 = OpConstant %3 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpSelectionMerge %7 None
 OpBranchConditional %6 %8 %9
 %8 = OpLabel
@@ -211,11 +214,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateIfStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
@@ -227,7 +230,7 @@
 %19 = OpConstant %3 4
 %20 = OpConstant %3 5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpSelectionMerge %7 None
 OpBranchConditional %6 %8 %9
 %8 = OpLabel
@@ -274,13 +277,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -316,13 +319,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -358,13 +361,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -403,13 +406,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -441,13 +444,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeBool
 %6 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpSelectionMerge %7 None
 OpBranchConditional %6 %8 %7
 %8 = OpLabel
@@ -471,13 +474,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeFunction %2
 %5 = OpConstantTrue %2
 %8 = OpConstantNull %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpSelectionMerge %6 None
 OpBranchConditional %5 %7 %6
 %7 = OpLabel
@@ -503,13 +506,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeFunction %2
 %5 = OpConstantTrue %2
 %9 = OpConstantNull %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpSelectionMerge %6 None
 OpBranchConditional %5 %7 %8
 %7 = OpLabel
@@ -541,13 +544,13 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %1 = OpTypeFunction %2
 %5 = OpConstantTrue %2
 %8 = OpConstantNull %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpSelectionMerge %6 None
 OpBranchConditional %5 %7 %6
 %7 = OpLabel
@@ -570,16 +573,16 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeBool
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeBool
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %6 = OpTypeVoid
 %5 = OpTypeFunction %6
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(%9 = OpLoad %3 %1
 OpSelectionMerge %10 None
 OpBranchConditional %9 %11 %10
@@ -602,14 +605,14 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %5 = OpTypeBool
 %6 = OpConstantNull %5
 %10 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpSelectionMerge %7 None
 OpBranchConditional %6 %8 %9
 %8 = OpLabel
@@ -643,14 +646,14 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeBool
 %10 = OpConstantNull %9
 %14 = OpConstantTrue %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
               R"(OpBranch %5
 %5 = OpLabel
 OpLoopMerge %6 %7 None
diff --git a/src/tint/writer/spirv/builder_literal_test.cc b/src/tint/writer/spirv/builder_literal_test.cc
index 3a9ec29..0bfc7b3 100644
--- a/src/tint/writer/spirv/builder_literal_test.cc
+++ b/src/tint/writer/spirv/builder_literal_test.cc
@@ -28,10 +28,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateLiteralIfNeeded(b_true);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(2u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 )");
 }
@@ -43,10 +43,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateLiteralIfNeeded(b_false);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(2u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantFalse %1
 )");
 }
@@ -59,13 +59,13 @@
     spirv::Builder& b = Build();
 
     ASSERT_NE(b.GenerateLiteralIfNeeded(b_true), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     ASSERT_NE(b.GenerateLiteralIfNeeded(b_false), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     ASSERT_NE(b.GenerateLiteralIfNeeded(b_true), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeBool
 %2 = OpConstantTrue %1
 %3 = OpConstantFalse %1
 )");
@@ -77,10 +77,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateLiteralIfNeeded(i);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(2u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 -23
 )");
 }
@@ -94,9 +94,9 @@
 
     ASSERT_NE(b.GenerateLiteralIfNeeded(i1), 0u);
     ASSERT_NE(b.GenerateLiteralIfNeeded(i2), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 1
 %2 = OpConstant %1 -23
 )");
 }
@@ -108,10 +108,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateLiteralIfNeeded(i);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(2u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 23
 )");
 }
@@ -125,9 +125,9 @@
 
     ASSERT_NE(b.GenerateLiteralIfNeeded(i1), 0u);
     ASSERT_NE(b.GenerateLiteralIfNeeded(i2), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeInt 32 0
 %2 = OpConstant %1 23
 )");
 }
@@ -139,10 +139,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateLiteralIfNeeded(i);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(2u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 23.2450008
 )");
 }
@@ -156,9 +156,9 @@
 
     ASSERT_NE(b.GenerateLiteralIfNeeded(i1), 0u);
     ASSERT_NE(b.GenerateLiteralIfNeeded(i2), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 32
 %2 = OpConstant %1 23.2450008
 )");
 }
@@ -172,10 +172,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateLiteralIfNeeded(i);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(2u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1.73cp+4
 )");
 }
@@ -191,9 +191,9 @@
 
     ASSERT_NE(b.GenerateLiteralIfNeeded(i1), 0u);
     ASSERT_NE(b.GenerateLiteralIfNeeded(i2), 0u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 16
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%1 = OpTypeFloat 16
 %2 = OpConstant %1 0x1.73cp+4
 )");
 }
diff --git a/src/tint/writer/spirv/builder_loop_test.cc b/src/tint/writer/spirv/builder_loop_test.cc
index 5fc15b8..380b2b2 100644
--- a/src/tint/writer/spirv/builder_loop_test.cc
+++ b/src/tint/writer/spirv/builder_loop_test.cc
@@ -32,10 +32,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -63,17 +63,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %9 = OpConstant %3 2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %5
 %5 = OpLabel
 OpLoopMerge %6 %7 None
@@ -106,18 +106,18 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
+    b.PushFunctionForTesting();
+    ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.Diagnostics();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %9 = OpConstant %3 2
 %10 = OpConstant %3 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %5
 %5 = OpLabel
 OpLoopMerge %6 %7 None
@@ -150,15 +150,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%7 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%7 = OpTypeInt 32 1
 %6 = OpTypePointer Function %7
 %8 = OpConstantNull %7
 %9 = OpConstant %7 3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -184,10 +184,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -215,10 +215,10 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -244,13 +244,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantTrue %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -275,13 +275,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -309,15 +309,15 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeBool
 %6 = OpConstantTrue %5
 %8 = OpTypePointer Function %5
 %9 = OpConstantNull %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
@@ -355,13 +355,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateLoopStatement(outer_loop)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%9 = OpTypeBool
+    EXPECT_TRUE(b.GenerateLoopStatement(outer_loop)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%9 = OpTypeBool
 %10 = OpConstantTrue %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpBranch %1
 %1 = OpLabel
 OpLoopMerge %2 %3 None
diff --git a/src/tint/writer/spirv/builder_return_test.cc b/src/tint/writer/spirv/builder_return_test.cc
index 825529e..13a65d6 100644
--- a/src/tint/writer/spirv/builder_return_test.cc
+++ b/src/tint/writer/spirv/builder_return_test.cc
@@ -28,11 +28,11 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateReturnStatement(ret));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpReturn
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()), R"(OpReturn
 )");
 }
 
@@ -44,17 +44,17 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
     EXPECT_TRUE(b.GenerateReturnStatement(ret));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 %3 = OpConstant %2 1
 %4 = OpConstant %2 3
 %5 = OpConstantComposite %1 %3 %3 %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpReturnValue %5
 )");
 }
@@ -67,19 +67,19 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-    EXPECT_TRUE(b.GenerateReturnStatement(ret)) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
+    EXPECT_TRUE(b.GenerateReturnStatement(ret)) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypePointer Function %3
 %4 = OpConstantNull %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%1 = OpVariable %2 Function %4
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%5 = OpLoad %3 %1
 OpReturnValue %5
 )");
diff --git a/src/tint/writer/spirv/builder_switch_test.cc b/src/tint/writer/spirv/builder_switch_test.cc
index 7e0bd18..a66ecd8 100644
--- a/src/tint/writer/spirv/builder_switch_test.cc
+++ b/src/tint/writer/spirv/builder_switch_test.cc
@@ -32,13 +32,13 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
+    b.PushFunctionForTesting();
 
-    EXPECT_TRUE(b.GenerateSwitchStatement(expr)) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_TRUE(b.GenerateSwitchStatement(expr)) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpConstant %2 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(OpSelectionMerge %1 None
 OpSwitch %3 %4
 %4 = OpLabel
@@ -69,9 +69,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
@@ -126,9 +126,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
@@ -181,9 +181,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
@@ -235,9 +235,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
@@ -294,9 +294,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
@@ -352,9 +352,9 @@
 
     spirv::Builder& b = Build();
 
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
+    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.Diagnostics();
+    ASSERT_TRUE(b.GenerateFunction(func)) << b.Diagnostics();
 
     EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
@@ -413,7 +413,7 @@
 
     spirv::Builder& b = Build();
 
-    EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
+    EXPECT_TRUE(b.GenerateFunction(fn)) << b.Diagnostics();
     EXPECT_EQ(DumpBuilder(b), R"(OpName %3 "f"
 %2 = OpTypeInt 32 1
 %1 = OpTypeFunction %2
diff --git a/src/tint/writer/spirv/builder_test.cc b/src/tint/writer/spirv/builder_test.cc
index 1c79988..718f246 100644
--- a/src/tint/writer/spirv/builder_test.cc
+++ b/src/tint/writer/spirv/builder_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/writer/spirv/spv_dump.h"
 #include "src/tint/writer/spirv/test_helper.h"
 
 namespace tint::writer::spirv {
@@ -38,33 +37,5 @@
               R"(12:34 error: SPIR-V backend does not support extension 'undefined')");
 }
 
-TEST_F(BuilderTest, TracksIdBounds) {
-    spirv::Builder& b = Build();
-
-    for (size_t i = 0; i < 5; i++) {
-        EXPECT_EQ(b.next_id(), i + 1);
-    }
-
-    EXPECT_EQ(6u, b.id_bound());
-}
-
-TEST_F(BuilderTest, Capabilities_Dedup) {
-    spirv::Builder& b = Build();
-
-    b.push_capability(SpvCapabilityShader);
-    b.push_capability(SpvCapabilityShader);
-    b.push_capability(SpvCapabilityShader);
-
-    EXPECT_EQ(DumpInstructions(b.capabilities()), "OpCapability Shader\n");
-}
-
-TEST_F(BuilderTest, DeclareExtension) {
-    spirv::Builder& b = Build();
-
-    b.push_extension("SPV_KHR_integer_dot_product");
-
-    EXPECT_EQ(DumpInstructions(b.extensions()), "OpExtension \"SPV_KHR_integer_dot_product\"\n");
-}
-
 }  // namespace
 }  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/builder_type_test.cc b/src/tint/writer/spirv/builder_type_test.cc
index 89ba34e..87c4cf3 100644
--- a/src/tint/writer/spirv/builder_type_test.cc
+++ b/src/tint/writer/spirv/builder_type_test.cc
@@ -36,10 +36,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(type));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeRuntimeArray %2
 )");
 }
@@ -55,9 +55,9 @@
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(type)), 1u);
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(type)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeRuntimeArray %2
 )");
 }
@@ -69,10 +69,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(type));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 4
 %1 = OpTypeArray %2 %4
@@ -86,13 +86,13 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(ty));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id);
 
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 ArrayStride 16
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpDecorate %1 ArrayStride 16
 )");
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 4
 %1 = OpTypeArray %2 %4
@@ -107,9 +107,9 @@
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpTypeInt 32 0
 %4 = OpConstant %3 4
 %1 = OpTypeArray %2 %4
@@ -122,11 +122,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(bool_);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    ASSERT_EQ(b.types().size(), 1u);
-    EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeBool
+    ASSERT_EQ(b.Module().Types().size(), 1u);
+    EXPECT_EQ(DumpInstruction(b.Module().Types()[0]), R"(%1 = OpTypeBool
 )");
 }
 
@@ -137,11 +137,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateF32) {
@@ -150,11 +150,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(f32);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    ASSERT_EQ(b.types().size(), 1u);
-    EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeFloat 32
+    ASSERT_EQ(b.Module().Types().size(), 1u);
+    EXPECT_EQ(DumpInstruction(b.Module().Types()[0]), R"(%1 = OpTypeFloat 32
 )");
 }
 
@@ -165,11 +165,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateF16) {
@@ -178,11 +178,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(f16);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    ASSERT_EQ(b.types().size(), 1u);
-    EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeFloat 16
+    ASSERT_EQ(b.Module().Types().size(), 1u);
+    EXPECT_EQ(DumpInstruction(b.Module().Types()[0]), R"(%1 = OpTypeFloat 16
 )");
 }
 
@@ -193,11 +193,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(f16), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(f16), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateI32) {
@@ -206,11 +206,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(i32);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    ASSERT_EQ(b.types().size(), 1u);
-    EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 1
+    ASSERT_EQ(b.Module().Types().size(), 1u);
+    EXPECT_EQ(DumpInstruction(b.Module().Types()[0]), R"(%1 = OpTypeInt 32 1
 )");
 }
 
@@ -221,11 +221,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateMatrix) {
@@ -236,11 +236,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(mat2x3);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(b.types().size(), 3u);
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32
+    EXPECT_EQ(b.Module().Types().size(), 3u);
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 32
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 2
 )");
@@ -254,11 +254,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 3u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateF16Matrix) {
@@ -269,11 +269,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(mat2x3);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(b.types().size(), 3u);
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 16
+    EXPECT_EQ(b.Module().Types().size(), 3u);
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%3 = OpTypeFloat 16
 %2 = OpTypeVector %3 3
 %1 = OpTypeMatrix %2 2
 )");
@@ -287,11 +287,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(f16), 3u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GeneratePtr) {
@@ -302,10 +302,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(ptr);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypePointer Output %2
 )");
 }
@@ -329,14 +329,14 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpTypeFloat 16
 %1 = OpTypeStruct %2 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "my_struct"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "my_struct"
 OpMemberName %1 0 "a"
 OpMemberName %1 1 "b"
 )");
@@ -355,20 +355,20 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpTypeFloat 16
 %1 = OpTypeStruct %2 %2 %3 %3
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "S"
 OpMemberName %1 0 "a"
 OpMemberName %1 1 "b"
 OpMemberName %1 2 "c"
 OpMemberName %1 3 "d"
 )");
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
 OpMemberDecorate %1 1 Offset 8
 OpMemberDecorate %1 2 Offset 16
 OpMemberDecorate %1 3 Offset 18
@@ -391,10 +391,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 2
 %2 = OpTypeMatrix %3 2
 %6 = OpTypeVector %4 3
@@ -410,7 +410,7 @@
 %14 = OpTypeMatrix %15 4
 %1 = OpTypeStruct %2 %5 %7 %9 %12 %14
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "S"
 OpMemberName %1 0 "mat2x2_f32"
 OpMemberName %1 1 "mat2x3_f32"
 OpMemberName %1 2 "mat4x4_f32"
@@ -418,7 +418,7 @@
 OpMemberName %1 4 "mat2x3_f16"
 OpMemberName %1 5 "mat4x4_f16"
 )");
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
 OpMemberDecorate %1 0 ColMajor
 OpMemberDecorate %1 0 MatrixStride 8
 OpMemberDecorate %1 1 Offset 64
@@ -462,10 +462,10 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(program->TypeOf(s));
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%5 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%5 = OpTypeFloat 32
 %4 = OpTypeVector %5 2
 %3 = OpTypeMatrix %4 2
 %6 = OpTypeInt 32 0
@@ -489,14 +489,14 @@
 %21 = OpTypeRuntimeArray %22
 %1 = OpTypeStruct %2 %8 %12 %17 %21
 )");
-    EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %1 "S"
+    EXPECT_EQ(DumpInstructions(b.Module().Debug()), R"(OpName %1 "S"
 OpMemberName %1 0 "arr_mat2x2_f32"
 OpMemberName %1 1 "arr_mat2x2_f16"
 OpMemberName %1 2 "arr_arr_mat2x3_f32"
 OpMemberName %1 3 "arr_arr_mat2x3_f16"
 OpMemberName %1 4 "rtarr_mat4x4"
 )");
-    EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %1 0 Offset 0
+    EXPECT_EQ(DumpInstructions(b.Module().Annots()), R"(OpMemberDecorate %1 0 Offset 0
 OpMemberDecorate %1 0 ColMajor
 OpMemberDecorate %1 0 MatrixStride 8
 OpDecorate %2 ArrayStride 16
@@ -527,11 +527,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(u32);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    ASSERT_EQ(b.types().size(), 1u);
-    EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeInt 32 0
+    ASSERT_EQ(b.Module().Types().size(), 1u);
+    EXPECT_EQ(DumpInstruction(b.Module().Types()[0]), R"(%1 = OpTypeInt 32 0
 )");
 }
 
@@ -542,11 +542,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateVector) {
@@ -555,11 +555,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(vec);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    EXPECT_EQ(b.types().size(), 2u);
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(b.Module().Types().size(), 2u);
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeVector %2 3
 )");
 }
@@ -571,11 +571,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 TEST_F(BuilderTest_Type, GenerateVoid) {
@@ -584,11 +584,11 @@
     spirv::Builder& b = Build();
 
     auto id = b.GenerateTypeIfNeeded(void_);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(id, 1u);
 
-    ASSERT_EQ(b.types().size(), 1u);
-    EXPECT_EQ(DumpInstruction(b.types()[0]), R"(%1 = OpTypeVoid
+    ASSERT_EQ(b.Module().Types().size(), 1u);
+    EXPECT_EQ(DumpInstruction(b.Module().Types()[0]), R"(%1 = OpTypeVoid
 )");
 }
 
@@ -599,11 +599,11 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 }
 
 struct PtrData {
@@ -643,10 +643,10 @@
     spirv::Builder& b = Build();
 
     auto id_two_d = b.GenerateTypeIfNeeded(two_d);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id_two_d);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 0 1 Unknown
 )");
 }
@@ -657,10 +657,10 @@
     spirv::Builder& b = Build();
 
     auto id_two_d_array = b.GenerateTypeIfNeeded(two_d_array);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id_two_d_array);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 1 0 1 Unknown
 )");
 }
@@ -671,13 +671,13 @@
     spirv::Builder& b = Build();
 
     auto id_cube = b.GenerateTypeIfNeeded(cube);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id_cube);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 Cube 0 0 0 1 Unknown
 )");
-    EXPECT_EQ(DumpInstructions(b.capabilities()), "");
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()), "");
 }
 
 TEST_F(BuilderTest_Type, DepthTexture_Generate_CubeArray) {
@@ -686,13 +686,13 @@
     spirv::Builder& b = Build();
 
     auto id_cube_array = b.GenerateTypeIfNeeded(cube_array);
-    ASSERT_FALSE(b.has_error()) << b.error();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
     EXPECT_EQ(1u, id_cube_array);
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 Cube 0 1 0 1 Unknown
 )");
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
               R"(OpCapability SampledCubeArray
 )");
 }
@@ -704,8 +704,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(1u, b.GenerateTypeIfNeeded(ms));
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeImage %2 2D 0 0 1 1 Unknown
 )");
 }
@@ -717,8 +717,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(ms), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeInt 32 0
 %1 = OpTypeImage %2 2D 0 0 1 1 Unknown
 )");
@@ -731,8 +731,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(ms), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 1 1 Unknown
 )");
@@ -744,13 +744,13 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeInt 32 1
 %1 = OpTypeImage %2 1D 0 0 0 1 Unknown
 )");
 
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
               R"(OpCapability Sampled1D
 )");
 }
@@ -762,13 +762,13 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeInt 32 0
 %1 = OpTypeImage %2 1D 0 0 0 1 Unknown
 )");
 
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
               R"(OpCapability Sampled1D
 )");
 }
@@ -780,13 +780,13 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 1D 0 0 0 1 Unknown
 )");
 
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
               R"(OpCapability Sampled1D
 )");
 }
@@ -798,8 +798,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 0 1 Unknown
 )");
@@ -812,8 +812,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 1 0 1 Unknown
 )");
@@ -826,8 +826,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 3D 0 0 0 1 Unknown
 )");
@@ -840,12 +840,12 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 Cube 0 0 0 1 Unknown
 )");
-    EXPECT_EQ(DumpInstructions(b.capabilities()), "");
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()), "");
 }
 
 TEST_F(BuilderTest_Type, SampledTexture_Generate_CubeArray) {
@@ -855,12 +855,12 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(s), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()),
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()),
               R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 Cube 0 1 0 1 Unknown
 )");
-    EXPECT_EQ(DumpInstructions(b.capabilities()),
+    EXPECT_EQ(DumpInstructions(b.Module().Capabilities()),
               R"(OpCapability SampledCubeArray
 )");
 }
@@ -874,8 +874,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 1D 0 0 0 2 R32f
 )");
 }
@@ -889,8 +889,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 0 2 R32f
 )");
 }
@@ -904,8 +904,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 1 0 2 R32f
 )");
 }
@@ -919,8 +919,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 3D 0 0 0 2 R32f
 )");
 }
@@ -934,8 +934,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %1 = OpTypeImage %2 2D 0 0 0 2 R32f
 )");
 }
@@ -949,8 +949,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %1 = OpTypeImage %2 2D 0 0 0 2 R32i
 )");
 }
@@ -964,8 +964,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(program->TypeOf(ty)), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 0
 %1 = OpTypeImage %2 2D 0 0 0 2 R32ui
 )");
 }
@@ -976,8 +976,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(sampler), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), "%1 = OpTypeSampler\n");
 }
 
 TEST_F(BuilderTest_Type, ComparisonSampler) {
@@ -986,8 +986,8 @@
     spirv::Builder& b = Build();
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(sampler), 1u);
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), "%1 = OpTypeSampler\n");
 }
 
 TEST_F(BuilderTest_Type, Dedup_Sampler_And_ComparisonSampler) {
@@ -1000,8 +1000,8 @@
 
     EXPECT_EQ(b.GenerateTypeIfNeeded(sampler), 1u);
 
-    ASSERT_FALSE(b.has_error()) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), "%1 = OpTypeSampler\n");
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), "%1 = OpTypeSampler\n");
 }
 
 }  // namespace
diff --git a/src/tint/writer/spirv/builder_unary_op_expression_test.cc b/src/tint/writer/spirv/builder_unary_op_expression_test.cc
index e28eb78..3998371 100644
--- a/src/tint/writer/spirv/builder_unary_op_expression_test.cc
+++ b/src/tint/writer/spirv/builder_unary_op_expression_test.cc
@@ -28,12 +28,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    b.PushFunctionForTesting();
+    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpConstant %2 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpSNegate %2 %3
 )");
 }
@@ -44,12 +44,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
+    b.PushFunctionForTesting();
+    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeFloat 32
 %3 = OpConstant %2 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpFNegate %2 %3
 )");
 }
@@ -60,12 +60,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
+    b.PushFunctionForTesting();
+    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeInt 32 1
 %3 = OpConstant %2 1
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpNot %2 %3
 )");
 }
@@ -76,12 +76,12 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
+    b.PushFunctionForTesting();
+    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.Diagnostics();
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeBool
 %3 = OpConstantNull %2
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%1 = OpLogicalNot %2 %3
 )");
 }
@@ -94,20 +94,20 @@
 
     spirv::Builder& b = Build();
 
-    b.push_function(Function{});
-    EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
-    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 6u) << b.error();
-    ASSERT_FALSE(b.has_error()) << b.error();
+    b.PushFunctionForTesting();
+    EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.Diagnostics();
+    EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 6u) << b.Diagnostics();
+    ASSERT_FALSE(b.has_error()) << b.Diagnostics();
 
-    EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeFloat 32
+    EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%4 = OpTypeFloat 32
 %3 = OpTypeVector %4 3
 %2 = OpTypePointer Function %3
 %5 = OpConstantNull %3
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().variables()),
               R"(%1 = OpVariable %2 Function %5
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
+    EXPECT_EQ(DumpInstructions(b.CurrentFunction().instructions()),
               R"(%7 = OpLoad %3 %1
 %6 = OpFNegate %3 %7
 )");
diff --git a/src/tint/writer/spirv/function.cc b/src/tint/writer/spirv/function.cc
index 871ef3f..8f2499d 100644
--- a/src/tint/writer/spirv/function.cc
+++ b/src/tint/writer/spirv/function.cc
@@ -25,6 +25,8 @@
 
 Function::Function(const Function& other) = default;
 
+Function& Function::operator=(const Function& other) = default;
+
 Function::~Function() = default;
 
 void Function::iterate(std::function<void(const Instruction&)> cb) const {
diff --git a/src/tint/writer/spirv/function.h b/src/tint/writer/spirv/function.h
index 05012f2..ef611c6 100644
--- a/src/tint/writer/spirv/function.h
+++ b/src/tint/writer/spirv/function.h
@@ -38,6 +38,11 @@
     /// Copy constructor
     /// @param other the function to copy
     Function(const Function& other);
+    /// Copy assignment operator
+    /// @param other the function to copy
+    /// @returns the new Function
+    Function& operator=(const Function& other);
+    /// Destructor
     ~Function();
 
     /// Iterates over the function call the cb on each instruction
@@ -84,6 +89,9 @@
         return size;
     }
 
+    /// @returns true if the function has a valid declaration
+    explicit operator bool() const { return declaration_.opcode() == spv::Op::OpFunction; }
+
   private:
     Instruction declaration_;
     Operand label_op_;
diff --git a/src/tint/writer/spirv/generator.cc b/src/tint/writer/spirv/generator.cc
index 93ac6e1..de6178b 100644
--- a/src/tint/writer/spirv/generator.cc
+++ b/src/tint/writer/spirv/generator.cc
@@ -17,6 +17,10 @@
 #include <utility>
 
 #include "src/tint/writer/spirv/generator_impl.h"
+#if TINT_BUILD_IR
+#include "src/tint/ir/from_program.h"                 // nogncheck
+#include "src/tint/writer/spirv/generator_impl_ir.h"  // nogncheck
+#endif                                                // TINT_BUILD_IR
 
 namespace tint::writer::spirv {
 
@@ -31,23 +35,41 @@
         return result;
     }
 
-    // Sanitize the program.
-    auto sanitized_result = Sanitize(program, options);
-    if (!sanitized_result.program.IsValid()) {
-        result.success = false;
-        result.error = sanitized_result.program.Diagnostics().str();
-        return result;
-    }
-
-    // Generate the SPIR-V code.
     bool zero_initialize_workgroup_memory =
         !options.disable_workgroup_init && options.use_zero_initialize_workgroup_memory_extension;
 
-    auto impl = std::make_unique<GeneratorImpl>(&sanitized_result.program,
-                                                zero_initialize_workgroup_memory);
-    result.success = impl->Generate();
-    result.error = impl->error();
-    result.spirv = std::move(impl->result());
+#if TINT_BUILD_IR
+    if (options.use_tint_ir) {
+        // Convert the AST program to an IR module.
+        auto ir = ir::FromProgram(program);
+        if (!ir) {
+            result.error = "IR converter: " + ir.Failure();
+            return result;
+        }
+
+        // Generate the SPIR-V code.
+        auto impl = std::make_unique<GeneratorImplIr>(&ir.Get(), zero_initialize_workgroup_memory);
+        result.success = impl->Generate();
+        result.error = impl->Diagnostics().str();
+        result.spirv = std::move(impl->Result());
+    } else  // NOLINT(readability/braces)
+#endif
+    {
+        // Sanitize the program.
+        auto sanitized_result = Sanitize(program, options);
+        if (!sanitized_result.program.IsValid()) {
+            result.success = false;
+            result.error = sanitized_result.program.Diagnostics().str();
+            return result;
+        }
+
+        // Generate the SPIR-V code.
+        auto impl = std::make_unique<GeneratorImpl>(&sanitized_result.program,
+                                                    zero_initialize_workgroup_memory);
+        result.success = impl->Generate();
+        result.error = impl->Diagnostics().str();
+        result.spirv = std::move(impl->Result());
+    }
 
     return result;
 }
diff --git a/src/tint/writer/spirv/generator.h b/src/tint/writer/spirv/generator.h
index d24d938..0200426 100644
--- a/src/tint/writer/spirv/generator.h
+++ b/src/tint/writer/spirv/generator.h
@@ -60,6 +60,11 @@
     /// VK_KHR_zero_initialize_workgroup_memory is enabled.
     bool use_zero_initialize_workgroup_memory_extension = false;
 
+#if TINT_BUILD_IR
+    /// Set to `true` to generate SPIR-V via the Tint IR instead of from the AST.
+    bool use_tint_ir = false;
+#endif
+
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(disable_robustness,
                  emit_vertex_point_size,
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index 80485c9..88bd39a 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -119,7 +119,7 @@
         polyfills.quantize_to_vec_f16 = true;  // crbug.com/tint/1741
         polyfills.workgroup_uniform_load = true;
         data.Add<transform::BuiltinPolyfill::Config>(polyfills);
-        manager.Add<transform::BuiltinPolyfill>();
+        manager.Add<transform::BuiltinPolyfill>();  // Must come before DirectVariableAccess
     }
 
     bool disable_workgroup_init_in_sanitizer =
@@ -175,23 +175,12 @@
 
 bool GeneratorImpl::Generate() {
     if (builder_.Build()) {
-        writer_.WriteHeader(builder_.id_bound());
-        writer_.WriteBuilder(&builder_);
+        auto& module = builder_.Module();
+        writer_.WriteHeader(module.IdBound());
+        writer_.WriteModule(&module);
         return true;
     }
     return false;
 }
 
-const std::vector<uint32_t>& GeneratorImpl::result() const {
-    return writer_.result();
-}
-
-std::vector<uint32_t>& GeneratorImpl::result() {
-    return writer_.result();
-}
-
-std::string GeneratorImpl::error() const {
-    return builder_.error();
-}
-
 }  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl.h b/src/tint/writer/spirv/generator_impl.h
index c83f144..3fd745d 100644
--- a/src/tint/writer/spirv/generator_impl.h
+++ b/src/tint/writer/spirv/generator_impl.h
@@ -49,13 +49,10 @@
     bool Generate();
 
     /// @returns the result data
-    const std::vector<uint32_t>& result() const;
+    const std::vector<uint32_t>& Result() const { return writer_.result(); }
 
-    /// @returns the result data
-    std::vector<uint32_t>& result();
-
-    /// @returns the error
-    std::string error() const;
+    /// @returns the list of diagnostics raised by the generator
+    diag::List Diagnostics() const { return builder_.Diagnostics(); }
 
   private:
     Builder builder_;
diff --git a/src/tint/writer/spirv/generator_impl_function_test.cc b/src/tint/writer/spirv/generator_impl_function_test.cc
new file mode 100644
index 0000000..e4cb71b
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_function_test.cc
@@ -0,0 +1,50 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/test_helper_ir.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, Function_Empty) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("foo");
+    func->return_type = ir.types.Get<type::Void>();
+
+    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
+)");
+}
+
+// Test that we do not emit the same function type more than once.
+TEST_F(SpvGeneratorImplTest, Function_DeduplicateType) {
+    auto* func = CreateFunction();
+    func->return_type = ir.types.Get<type::Void>();
+
+    generator_.EmitFunction(func);
+    generator_.EmitFunction(func);
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_ir.cc b/src/tint/writer/spirv/generator_impl_ir.cc
new file mode 100644
index 0000000..855662a
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_ir.cc
@@ -0,0 +1,127 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/generator_impl_ir.h"
+
+#include "spirv/unified1/spirv.h"
+#include "src/tint/ir/module.h"
+#include "src/tint/switch.h"
+#include "src/tint/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/type.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/void.h"
+#include "src/tint/writer/spirv/module.h"
+
+namespace tint::writer::spirv {
+
+GeneratorImplIr::GeneratorImplIr(const ir::Module* module, bool zero_init_workgroup_mem)
+    : ir_(module), zero_init_workgroup_memory_(zero_init_workgroup_mem) {}
+
+bool GeneratorImplIr::Generate() {
+    // TODO(crbug.com/tint/1906): Check supported extensions.
+
+    module_.PushCapability(SpvCapabilityShader);
+    module_.PushMemoryModel(spv::Op::OpMemoryModel, {U32Operand(SpvAddressingModelLogical),
+                                                     U32Operand(SpvMemoryModelGLSL450)});
+
+    // TODO(crbug.com/tint/1906): Emit extensions.
+
+    // TODO(crbug.com/tint/1906): Emit variables.
+    (void)zero_init_workgroup_memory_;
+
+    // Emit functions.
+    for (auto* func : ir_->functions) {
+        EmitFunction(func);
+    }
+
+    // Serialize the module into binary SPIR-V.
+    writer_.WriteHeader(module_.IdBound());
+    writer_.WriteModule(&module_);
+
+    return true;
+}
+
+uint32_t GeneratorImplIr::Type(const type::Type* ty) {
+    return types_.GetOrCreate(ty, [&]() {
+        auto id = module_.NextId();
+        Switch(
+            ty,  //
+            [&](const type::Void*) { module_.PushType(spv::Op::OpTypeVoid, {id}); },
+            [&](const type::Bool*) { module_.PushType(spv::Op::OpTypeBool, {id}); },
+            [&](const type::I32*) {
+                module_.PushType(spv::Op::OpTypeInt, {id, 32u, 1u});
+            },
+            [&](const type::U32*) {
+                module_.PushType(spv::Op::OpTypeInt, {id, 32u, 0u});
+            },
+            [&](const type::F32*) {
+                module_.PushType(spv::Op::OpTypeFloat, {id, 32u});
+            },
+            [&](const type::F16*) {
+                module_.PushType(spv::Op::OpTypeFloat, {id, 16u});
+            },
+            [&](Default) {
+                TINT_ICE(Writer, diagnostics_) << "unhandled type: " << ty->FriendlyName();
+            });
+        return id;
+    });
+}
+
+void GeneratorImplIr::EmitFunction(const ir::Function* func) {
+    // Make an ID for the function.
+    auto id = module_.NextId();
+
+    // Emit the function name.
+    module_.PushDebug(spv::Op::OpName, {id, Operand(func->name.Name())});
+
+    // TODO(jrprice): Emit OpEntryPoint and OpExecutionMode declarations if needed.
+
+    // Get the ID for the return type.
+    auto return_type_id = Type(func->return_type);
+
+    // Get the ID for the function type (creating it if needed).
+    // TODO(jrprice): Add the parameter types when they are supported in the IR.
+    FunctionType function_type{return_type_id, {}};
+    auto function_type_id = function_types_.GetOrCreate(function_type, [&]() {
+        auto func_ty_id = module_.NextId();
+        OperandList operands = {func_ty_id, return_type_id};
+        operands.insert(operands.end(), function_type.param_type_ids.begin(),
+                        function_type.param_type_ids.end());
+        module_.PushType(spv::Op::OpTypeFunction, operands);
+        return func_ty_id;
+    });
+
+    // Declare the function.
+    auto decl =
+        Instruction{spv::Op::OpFunction,
+                    {return_type_id, id, U32Operand(SpvFunctionControlMaskNone), function_type_id}};
+
+    // Create a function that we will add instructions to.
+    // TODO(jrprice): Add the parameter declarations when they are supported in the IR.
+    auto entry_block = module_.NextId();
+    Function current_function_(decl, entry_block, {});
+
+    // TODO(jrprice): Emit the body of the function.
+
+    // TODO(jrprice): Remove this when we start emitting OpReturn for branches to the terminator.
+    current_function_.push_inst(spv::Op::OpReturn, {});
+
+    // Add the function to the module.
+    module_.PushFunction(current_function_);
+}
+
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_ir.h b/src/tint/writer/spirv/generator_impl_ir.h
new file mode 100644
index 0000000..d9aab3c
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_ir.h
@@ -0,0 +1,109 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_GENERATOR_IMPL_IR_H_
+#define SRC_TINT_WRITER_SPIRV_GENERATOR_IMPL_IR_H_
+
+#include <vector>
+
+#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/utils/hashmap.h"
+#include "src/tint/utils/vector.h"
+#include "src/tint/writer/spirv/binary_writer.h"
+#include "src/tint/writer/spirv/module.h"
+
+// Forward declarations
+namespace tint::ir {
+class Function;
+class Module;
+}  // namespace tint::ir
+namespace tint::type {
+class Type;
+}  // namespace tint::type
+
+namespace tint::writer::spirv {
+
+/// Implementation class for SPIR-V generator
+class GeneratorImplIr {
+  public:
+    /// Constructor
+    /// @param module the Tint IR module to generate
+    /// @param zero_init_workgroup_memory `true` to initialize all the variables in the Workgroup
+    ///                                   storage class with OpConstantNull
+    GeneratorImplIr(const ir::Module* module, bool zero_init_workgroup_memory);
+
+    /// @returns true on successful generation; false otherwise
+    bool Generate();
+
+    /// @returns the module that this generator has produced
+    spirv::Module& Module() { return module_; }
+
+    /// @returns the generated SPIR-V binary data
+    const std::vector<uint32_t>& Result() const { return writer_.result(); }
+
+    /// @returns the list of diagnostics raised by the generator
+    diag::List Diagnostics() const { return diagnostics_; }
+
+    /// Get the result ID of the type `ty`, emitting a type declaration instruction if necessary.
+    /// @param ty the type to get the ID for
+    /// @returns the result ID of the type
+    uint32_t Type(const type::Type* ty);
+
+    /// Emit a function.
+    /// @param func the function to emit
+    void EmitFunction(const ir::Function* func);
+
+  private:
+    const ir::Module* ir_;
+    spirv::Module module_;
+    BinaryWriter writer_;
+    diag::List diagnostics_;
+
+    /// A function type used for an OpTypeFunction declaration.
+    struct FunctionType {
+        uint32_t return_type_id;
+        utils::Vector<uint32_t, 4> param_type_ids;
+
+        /// Hasher provides a hash function for the FunctionType.
+        struct Hasher {
+            /// @param ft the FunctionType to create a hash for
+            /// @return the hash value
+            inline std::size_t operator()(const FunctionType& ft) const {
+                size_t hash = utils::Hash(ft.return_type_id);
+                for (auto& p : ft.param_type_ids) {
+                    hash = utils::HashCombine(hash, p);
+                }
+                return hash;
+            }
+        };
+
+        /// Equality operator for FunctionType.
+        bool operator==(const FunctionType& other) const {
+            return (param_type_ids == other.param_type_ids) &&
+                   (return_type_id == other.return_type_id);
+        }
+    };
+
+    /// The map of types to their result IDs.
+    utils::Hashmap<const type::Type*, uint32_t, 8> types_;
+
+    /// The map of function types to their result IDs.
+    utils::Hashmap<FunctionType, uint32_t, 8, FunctionType::Hasher> function_types_;
+
+    bool zero_init_workgroup_memory_ = false;
+};
+
+}  // namespace tint::writer::spirv
+
+#endif  // SRC_TINT_WRITER_SPIRV_GENERATOR_IMPL_IR_H_
diff --git a/src/tint/writer/spirv/generator_impl_ir_test.cc b/src/tint/writer/spirv/generator_impl_ir_test.cc
new file mode 100644
index 0000000..a202eea
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_ir_test.cc
@@ -0,0 +1,29 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/test_helper_ir.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, ModuleHeader) {
+    ASSERT_TRUE(generator_.Generate()) << generator_.Diagnostics().str();
+    auto got = Disassemble(generator_.Result());
+    EXPECT_EQ(got, R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_type_test.cc b/src/tint/writer/spirv/generator_impl_type_test.cc
new file mode 100644
index 0000000..c8a2e8e
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_type_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/type.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/void.h"
+#include "src/tint/writer/spirv/test_helper_ir.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, Type_Void) {
+    auto id = generator_.Type(ir.types.Get<type::Void>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeVoid\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_Bool) {
+    auto id = generator_.Type(ir.types.Get<type::Bool>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeBool\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_I32) {
+    auto id = generator_.Type(ir.types.Get<type::I32>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 1\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_U32) {
+    auto id = generator_.Type(ir.types.Get<type::U32>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeInt 32 0\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_F32) {
+    auto id = generator_.Type(ir.types.Get<type::F32>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeFloat 32\n");
+}
+
+TEST_F(SpvGeneratorImplTest, Type_F16) {
+    auto id = generator_.Type(ir.types.Get<type::F16>());
+    EXPECT_EQ(id, 1u);
+    EXPECT_EQ(DumpTypes(), "%1 = OpTypeFloat 16\n");
+}
+
+// Test that we do can emit multiple types.
+// Includes types with the same opcode but different parameters.
+TEST_F(SpvGeneratorImplTest, Type_Multiple) {
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::I32>()), 1u);
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::U32>()), 2u);
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::F32>()), 3u);
+    EXPECT_EQ(generator_.Type(ir.types.Get<type::F16>()), 4u);
+    EXPECT_EQ(DumpTypes(), R"(%1 = OpTypeInt 32 1
+%2 = OpTypeInt 32 0
+%3 = OpTypeFloat 32
+%4 = OpTypeFloat 16
+)");
+}
+
+// Test that we do not emit the same type more than once.
+TEST_F(SpvGeneratorImplTest, Type_Deduplicate) {
+    auto* i32 = ir.types.Get<type::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");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/instruction.cc b/src/tint/writer/spirv/instruction.cc
index 8b883c1..e2caac6 100644
--- a/src/tint/writer/spirv/instruction.cc
+++ b/src/tint/writer/spirv/instruction.cc
@@ -23,6 +23,8 @@
 
 Instruction::Instruction(const Instruction&) = default;
 
+Instruction& Instruction::operator=(const Instruction&) = default;
+
 Instruction::~Instruction() = default;
 
 uint32_t Instruction::word_length() const {
diff --git a/src/tint/writer/spirv/instruction.h b/src/tint/writer/spirv/instruction.h
index 2beae59..3664335 100644
--- a/src/tint/writer/spirv/instruction.h
+++ b/src/tint/writer/spirv/instruction.h
@@ -31,6 +31,11 @@
     Instruction(spv::Op op, OperandList operands);
     /// Copy Constructor
     Instruction(const Instruction&);
+    /// Copy assignment operator
+    /// @param other the instruction to copy
+    /// @returns the new Instruction
+    Instruction& operator=(const Instruction& other);
+    /// Destructor
     ~Instruction();
 
     /// @returns the instructions op
diff --git a/src/tint/writer/spirv/module.cc b/src/tint/writer/spirv/module.cc
new file mode 100644
index 0000000..3084c79
--- /dev/null
+++ b/src/tint/writer/spirv/module.cc
@@ -0,0 +1,101 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/module.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+/// Helper to return the size in words of an instruction list when serialized.
+/// @param instructions the instruction list
+/// @returns the number of words needed to serialize the list
+uint32_t SizeOf(const InstructionList& instructions) {
+    uint32_t size = 0;
+    for (const auto& inst : instructions) {
+        size += inst.word_length();
+    }
+    return size;
+}
+
+}  // namespace
+
+Module::Module() {}
+
+Module::~Module() = default;
+
+uint32_t Module::TotalSize() const {
+    // The 5 covers the magic, version, generator, id bound and reserved.
+    uint32_t size = 5;
+
+    size += SizeOf(capabilities_);
+    size += SizeOf(extensions_);
+    size += SizeOf(ext_imports_);
+    size += SizeOf(memory_model_);
+    size += SizeOf(entry_points_);
+    size += SizeOf(execution_modes_);
+    size += SizeOf(debug_);
+    size += SizeOf(annotations_);
+    size += SizeOf(types_);
+    for (const auto& func : functions_) {
+        size += func.word_length();
+    }
+
+    return size;
+}
+
+void Module::Iterate(std::function<void(const Instruction&)> cb) const {
+    for (const auto& inst : capabilities_) {
+        cb(inst);
+    }
+    for (const auto& inst : extensions_) {
+        cb(inst);
+    }
+    for (const auto& inst : ext_imports_) {
+        cb(inst);
+    }
+    for (const auto& inst : memory_model_) {
+        cb(inst);
+    }
+    for (const auto& inst : entry_points_) {
+        cb(inst);
+    }
+    for (const auto& inst : execution_modes_) {
+        cb(inst);
+    }
+    for (const auto& inst : debug_) {
+        cb(inst);
+    }
+    for (const auto& inst : annotations_) {
+        cb(inst);
+    }
+    for (const auto& inst : types_) {
+        cb(inst);
+    }
+    for (const auto& func : functions_) {
+        func.iterate(cb);
+    }
+}
+
+void Module::PushCapability(uint32_t cap) {
+    if (capability_set_.count(cap) == 0) {
+        capability_set_.insert(cap);
+        capabilities_.push_back(Instruction{spv::Op::OpCapability, {Operand(cap)}});
+    }
+}
+
+void Module::PushExtension(const char* extension) {
+    extensions_.push_back(Instruction{spv::Op::OpExtension, {Operand(extension)}});
+}
+
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/module.h b/src/tint/writer/spirv/module.h
new file mode 100644
index 0000000..00caad8
--- /dev/null
+++ b/src/tint/writer/spirv/module.h
@@ -0,0 +1,161 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_MODULE_H_
+#define SRC_TINT_WRITER_SPIRV_MODULE_H_
+
+#include <cstdint>
+#include <functional>
+#include <unordered_set>
+#include <vector>
+
+#include "src/tint/writer/spirv/function.h"
+#include "src/tint/writer/spirv/instruction.h"
+
+namespace tint::writer::spirv {
+
+/// A SPIR-V module.
+class Module {
+  public:
+    /// Constructor
+    Module();
+
+    /// Destructor
+    ~Module();
+
+    /// @returns the number of uint32_t's needed to make up the results
+    uint32_t TotalSize() const;
+
+    /// @returns the id bound for this program
+    uint32_t IdBound() const { return next_id_; }
+
+    /// @returns the next id to be used
+    uint32_t NextId() {
+        auto id = next_id_;
+        next_id_ += 1;
+        return id;
+    }
+
+    /// Iterates over all the instructions in the correct order and calls the given callback.
+    /// @param cb the callback to execute
+    void Iterate(std::function<void(const Instruction&)> cb) const;
+
+    /// Add an instruction to the list of capabilities, if the capability hasn't already been added.
+    /// @param cap the capability to set
+    void PushCapability(uint32_t cap);
+
+    /// @returns the capabilities
+    const InstructionList& Capabilities() const { return capabilities_; }
+
+    /// Add an instruction to the list of extensions.
+    /// @param extension the name of the extension
+    void PushExtension(const char* extension);
+
+    /// @returns the extensions
+    const InstructionList& Extensions() const { return extensions_; }
+
+    /// Add an instruction to the list of imported extension instructions.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushExtImport(spv::Op op, const OperandList& operands) {
+        ext_imports_.push_back(Instruction{op, operands});
+    }
+
+    /// @returns the ext imports
+    const InstructionList& ExtImports() const { return ext_imports_; }
+
+    /// Add an instruction to the memory model.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushMemoryModel(spv::Op op, const OperandList& operands) {
+        memory_model_.push_back(Instruction{op, operands});
+    }
+
+    /// @returns the memory model
+    const InstructionList& MemoryModel() const { return memory_model_; }
+
+    /// Add an instruction to the list pf entry points.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushEntryPoint(spv::Op op, const OperandList& operands) {
+        entry_points_.push_back(Instruction{op, operands});
+    }
+    /// @returns the entry points
+    const InstructionList& EntryPoints() const { return entry_points_; }
+
+    /// Add an instruction to the execution mode declarations.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushExecutionMode(spv::Op op, const OperandList& operands) {
+        execution_modes_.push_back(Instruction{op, operands});
+    }
+
+    /// @returns the execution modes
+    const InstructionList& ExecutionModes() const { return execution_modes_; }
+
+    /// Add an instruction to the debug declarations.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushDebug(spv::Op op, const OperandList& operands) {
+        debug_.push_back(Instruction{op, operands});
+    }
+
+    /// @returns the debug instructions
+    const InstructionList& Debug() const { return debug_; }
+
+    /// Add an instruction to the type declarations.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushType(spv::Op op, const OperandList& operands) {
+        types_.push_back(Instruction{op, operands});
+    }
+
+    /// @returns the type instructions
+    const InstructionList& Types() const { return types_; }
+
+    /// Add an instruction to the annotations.
+    /// @param op the op to set
+    /// @param operands the operands for the instruction
+    void PushAnnot(spv::Op op, const OperandList& operands) {
+        annotations_.push_back(Instruction{op, operands});
+    }
+
+    /// @returns the annotations
+    const InstructionList& Annots() const { return annotations_; }
+
+    /// Add a function to the module.
+    /// @param func the function to add
+    void PushFunction(const Function& func) { functions_.push_back(func); }
+
+    /// @returns the functions
+    const std::vector<Function>& Functions() const { return functions_; }
+
+  private:
+    uint32_t next_id_ = 1;
+    InstructionList capabilities_;
+    InstructionList extensions_;
+    InstructionList ext_imports_;
+    InstructionList memory_model_;
+    InstructionList entry_points_;
+    InstructionList execution_modes_;
+    InstructionList debug_;
+    InstructionList types_;
+    InstructionList annotations_;
+    std::vector<Function> functions_;
+    std::unordered_set<uint32_t> capability_set_;
+};
+
+}  // namespace tint::writer::spirv
+
+#endif  // SRC_TINT_WRITER_SPIRV_MODULE_H_
diff --git a/src/tint/writer/spirv/module_test.cc b/src/tint/writer/spirv/module_test.cc
new file mode 100644
index 0000000..07fef6d
--- /dev/null
+++ b/src/tint/writer/spirv/module_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/spv_dump.h"
+#include "src/tint/writer/spirv/test_helper.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+using SpvModuleTest = TestHelper;
+
+TEST_F(SpvModuleTest, TracksIdBounds) {
+    spirv::Module m;
+
+    for (size_t i = 0; i < 5; i++) {
+        EXPECT_EQ(m.NextId(), i + 1);
+    }
+
+    EXPECT_EQ(6u, m.IdBound());
+}
+
+TEST_F(SpvModuleTest, Capabilities_Dedup) {
+    spirv::Module m;
+
+    m.PushCapability(SpvCapabilityShader);
+    m.PushCapability(SpvCapabilityShader);
+    m.PushCapability(SpvCapabilityShader);
+
+    EXPECT_EQ(DumpInstructions(m.Capabilities()), "OpCapability Shader\n");
+}
+
+TEST_F(SpvModuleTest, DeclareExtension) {
+    spirv::Module m;
+
+    m.PushExtension("SPV_KHR_integer_dot_product");
+
+    EXPECT_EQ(DumpInstructions(m.Extensions()), "OpExtension \"SPV_KHR_integer_dot_product\"\n");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/spv_dump.cc b/src/tint/writer/spirv/spv_dump.cc
index 0f95efe..9a63786 100644
--- a/src/tint/writer/spirv/spv_dump.cc
+++ b/src/tint/writer/spirv/spv_dump.cc
@@ -18,7 +18,6 @@
 #include "src/tint/writer/spirv/binary_writer.h"
 
 namespace tint::writer::spirv {
-namespace {
 
 std::string Disassemble(const std::vector<uint32_t>& data) {
     std::string spv_errors;
@@ -56,12 +55,14 @@
     return result;
 }
 
-}  // namespace
-
 std::string DumpBuilder(Builder& builder) {
+    return DumpModule(builder.Module());
+}
+
+std::string DumpModule(Module& module) {
     BinaryWriter writer;
-    writer.WriteHeader(builder.id_bound());
-    writer.WriteBuilder(&builder);
+    writer.WriteHeader(module.IdBound());
+    writer.WriteModule(&module);
     return Disassemble(writer.result());
 }
 
diff --git a/src/tint/writer/spirv/spv_dump.h b/src/tint/writer/spirv/spv_dump.h
index 2147d1d..359f0cb 100644
--- a/src/tint/writer/spirv/spv_dump.h
+++ b/src/tint/writer/spirv/spv_dump.h
@@ -22,11 +22,21 @@
 
 namespace tint::writer::spirv {
 
+/// Disassembles SPIR-V binary data into its textual form.
+/// @param data the SPIR-V binary data
+/// @returns the disassembled SPIR-V string
+std::string Disassemble(const std::vector<uint32_t>& data);
+
 /// Dumps the given builder to a SPIR-V disassembly string
 /// @param builder the builder to convert
 /// @returns the builder as a SPIR-V disassembly string
 std::string DumpBuilder(Builder& builder);
 
+/// Dumps the given module to a SPIR-V disassembly string
+/// @param module the module to convert
+/// @returns the module as a SPIR-V disassembly string
+std::string DumpModule(Module& module);
+
 /// Dumps the given instruction to a SPIR-V disassembly string
 /// @param inst the instruction to dump
 /// @returns the instruction as a SPIR-V disassembly string
diff --git a/src/tint/writer/spirv/test_helper.h b/src/tint/writer/spirv/test_helper.h
index 028d363..b9a90be 100644
--- a/src/tint/writer/spirv/test_helper.h
+++ b/src/tint/writer/spirv/test_helper.h
@@ -94,8 +94,8 @@
     /// @param b the spirv::Builder containing the built SPIR-V module
     void Validate(spirv::Builder& b) {
         BinaryWriter writer;
-        writer.WriteHeader(b.id_bound());
-        writer.WriteBuilder(&b);
+        writer.WriteHeader(b.Module().IdBound());
+        writer.WriteModule(&b.Module());
         auto binary = writer.result();
 
         std::string spv_errors;
diff --git a/src/tint/writer/spirv/test_helper_ir.h b/src/tint/writer/spirv/test_helper_ir.h
new file mode 100644
index 0000000..6de6f48
--- /dev/null
+++ b/src/tint/writer/spirv/test_helper_ir.h
@@ -0,0 +1,48 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_WRITER_SPIRV_TEST_HELPER_IR_H_
+#define SRC_TINT_WRITER_SPIRV_TEST_HELPER_IR_H_
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "src/tint/ir/builder.h"
+#include "src/tint/writer/spirv/generator_impl_ir.h"
+#include "src/tint/writer/spirv/spv_dump.h"
+
+namespace tint::writer::spirv {
+
+/// Base helper class for testing the SPIR-V generator implementation.
+template <typename BASE>
+class SpvGeneratorTestHelperBase : public ir::Builder, public BASE {
+  public:
+    SpvGeneratorTestHelperBase() : generator_(&ir, false) {}
+
+  protected:
+    /// The SPIR-V generator.
+    GeneratorImplIr generator_;
+
+    /// @returns the disassembled types from the generated module.
+    std::string DumpTypes() { return DumpInstructions(generator_.Module().Types()); }
+};
+
+using SpvGeneratorImplTest = SpvGeneratorTestHelperBase<testing::Test>;
+
+template <typename T>
+using SpvGeneratorImplTestWithParam = SpvGeneratorTestHelperBase<testing::TestWithParam<T>>;
+
+}  // namespace tint::writer::spirv
+
+#endif  // SRC_TINT_WRITER_SPIRV_TEST_HELPER_IR_H_