Split Program into Program and ProgramBuilder

Program is now immutable*, and remains part of the public Tint
interface.

ProgramBuilder is the mutable builder for Programs, and is not part of
the public Tint interface. ast::Builder has been folded into
ProgramBuilder.

Immutable Programs can be cloned into a mutable ProgramBuilder with
Program::CloneAsBuilder().

Mutable ProgramBuilders can be moved into immutable Programs.

* - mostly immutable. It still has a move constructor and move
  assignment operator - required for practical usage - and the
  semantic information on AST nodes is still mutable.

Bug: tint:390
Change-Id: Ia856c50b1880c2f95c91467a9eef5024cbc380c6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/38240
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 20d18ba..2ba7325 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -246,8 +246,6 @@
     "src/ast/bool_literal.h",
     "src/ast/break_statement.cc",
     "src/ast/break_statement.h",
-    "src/ast/builder.cc",
-    "src/ast/builder.h",
     "src/ast/builtin.cc",
     "src/ast/builtin.h",
     "src/ast/builtin_decoration.cc",
@@ -373,6 +371,8 @@
     "src/inspector/scalar.h",
     "src/namer.cc",
     "src/namer.h",
+    "src/program_builder.cc",
+    "src/program_builder.h",
     "src/program.cc",
     "src/program.h",
     "src/reader/reader.cc",
@@ -792,6 +792,7 @@
     "src/ast/loop_statement_test.cc",
     "src/ast/member_accessor_expression_test.cc",
     "src/ast/module_clone_test.cc",
+    "src/ast/module_test.cc",
     "src/ast/null_literal_test.cc",
     "src/ast/return_statement_test.cc",
     "src/ast/scalar_constructor_expression_test.cc",
diff --git a/fuzzers/tint_ast_clone_fuzzer.cc b/fuzzers/tint_ast_clone_fuzzer.cc
index bbb3f60..0e4cee6 100644
--- a/fuzzers/tint_ast_clone_fuzzer.cc
+++ b/fuzzers/tint_ast_clone_fuzzer.cc
@@ -56,7 +56,7 @@
   auto src = parser.program();
 
   // Clone the src program to dst
-  auto dst = src.Clone();
+  tint::Program dst(src.Clone());
 
   // Expect the demangled AST printed with to_str() to match
   tint::Demangler d;
diff --git a/fuzzers/tint_common_fuzzer.cc b/fuzzers/tint_common_fuzzer.cc
index 4c83b6a..fb5408a 100644
--- a/fuzzers/tint_common_fuzzer.cc
+++ b/fuzzers/tint_common_fuzzer.cc
@@ -19,6 +19,10 @@
 #include <utility>
 #include <vector>
 
+#include "src/ast/module.h"
+#include "src/program.h"
+#include "src/program_builder.h"
+
 namespace tint {
 namespace fuzzers {
 
@@ -81,9 +85,13 @@
     return 0;
   }
 
-  TypeDeterminer td(&program);
-  if (!td.Determine()) {
-    return 0;
+  {
+    ProgramBuilder builder = program.CloneAsBuilder();
+    TypeDeterminer td(&builder);
+    if (!td.Determine()) {
+      return 0;
+    }
+    program = Program(std::move(builder));
   }
 
   Validator v;
diff --git a/samples/main.cc b/samples/main.cc
index 9035b09..2e5264d 100644
--- a/samples/main.cc
+++ b/samples/main.cc
@@ -502,9 +502,10 @@
     return 1;
   }
 
-  tint::TypeDeterminer td(&program);
-  if (!td.Determine()) {
-    std::cerr << "Type Determination: " << td.error() << std::endl;
+  auto diags = tint::TypeDeterminer::Run(&program);
+  if (diags.contains_errors()) {
+    std::cerr << "Type Determination: ";
+    diag_formatter.format(reader->diagnostics(), diag_printer.get());
     return 1;
   }
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 638f26a..7000c71 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -60,8 +60,6 @@
   ast/bool_literal.h
   ast/break_statement.cc
   ast/break_statement.h
-  ast/builder.cc
-  ast/builder.h
   ast/builtin_decoration.cc
   ast/builtin_decoration.h
   ast/builtin.cc
@@ -187,6 +185,8 @@
   inspector/scalar.h
   namer.cc
   namer.h
+  program_builder.cc
+  program_builder.h
   program.cc
   program.h
   reader/reader.cc
@@ -422,6 +422,7 @@
     ast/loop_statement_test.cc
     ast/member_accessor_expression_test.cc
     ast/module_clone_test.cc
+    ast/module_test.cc
     ast/null_literal_test.cc
     ast/return_statement_test.cc
     ast/scalar_constructor_expression_test.cc
diff --git a/src/ast/access_decoration.cc b/src/ast/access_decoration.cc
index 996c3c7..c535160 100644
--- a/src/ast/access_decoration.cc
+++ b/src/ast/access_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/access_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::AccessDecoration);
 
diff --git a/src/ast/array_accessor_expression.cc b/src/ast/array_accessor_expression.cc
index 6a9e6be..be763db 100644
--- a/src/ast/array_accessor_expression.cc
+++ b/src/ast/array_accessor_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/array_accessor_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::ArrayAccessorExpression);
 
diff --git a/src/ast/assignment_statement.cc b/src/ast/assignment_statement.cc
index a0b1a2b..a92c6e3 100644
--- a/src/ast/assignment_statement.cc
+++ b/src/ast/assignment_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/assignment_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::AssignmentStatement);
 
diff --git a/src/ast/binary_expression.cc b/src/ast/binary_expression.cc
index de5065d..5c59cbd 100644
--- a/src/ast/binary_expression.cc
+++ b/src/ast/binary_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/binary_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BinaryExpression);
 
diff --git a/src/ast/binding_decoration.cc b/src/ast/binding_decoration.cc
index 05a0d86..10a6bff 100644
--- a/src/ast/binding_decoration.cc
+++ b/src/ast/binding_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/binding_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BindingDecoration);
 
diff --git a/src/ast/bitcast_expression.cc b/src/ast/bitcast_expression.cc
index 85c42a5..26e5230 100644
--- a/src/ast/bitcast_expression.cc
+++ b/src/ast/bitcast_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/bitcast_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BitcastExpression);
 
diff --git a/src/ast/block_statement.cc b/src/ast/block_statement.cc
index 2ce6b34..07ff30f 100644
--- a/src/ast/block_statement.cc
+++ b/src/ast/block_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/block_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BlockStatement);
 
diff --git a/src/ast/bool_literal.cc b/src/ast/bool_literal.cc
index a8004e6..e162d1c 100644
--- a/src/ast/bool_literal.cc
+++ b/src/ast/bool_literal.cc
@@ -15,7 +15,7 @@
 #include "src/ast/bool_literal.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BoolLiteral);
 
diff --git a/src/ast/break_statement.cc b/src/ast/break_statement.cc
index 1a7cc9d..6bf034c 100644
--- a/src/ast/break_statement.cc
+++ b/src/ast/break_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/break_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BreakStatement);
 
diff --git a/src/ast/builder.cc b/src/ast/builder.cc
deleted file mode 100644
index 9e3062e..0000000
--- a/src/ast/builder.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2020 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/ast/builder.h"
-
-namespace tint {
-namespace ast {
-
-TypesBuilder::TypesBuilder(Program* p)
-    : program_(p) {}
-
-Builder::Builder(Program* p) : program(p), ty(p), mod(p) {}
-
-Builder::~Builder() = default;
-
-Variable* Builder::Var(const std::string& name,
-                       StorageClass storage,
-                       type::Type* type) {
-  return Var(name, storage, type, nullptr, {});
-}
-
-Variable* Builder::Var(const std::string& name,
-                       StorageClass storage,
-                       type::Type* type,
-                       Expression* constructor,
-                       VariableDecorationList decorations) {
-  auto* var = create<Variable>(program->Symbols().Register(name), storage, type,
-                               false, constructor, decorations);
-  OnVariableBuilt(var);
-  return var;
-}
-
-Variable* Builder::Var(const Source& source,
-                       const std::string& name,
-                       StorageClass storage,
-                       type::Type* type,
-                       Expression* constructor,
-                       VariableDecorationList decorations) {
-  auto* var = create<Variable>(source, program->Symbols().Register(name),
-                               storage, type, false, constructor, decorations);
-  OnVariableBuilt(var);
-  return var;
-}
-
-Variable* Builder::Const(const std::string& name,
-                         StorageClass storage,
-                         type::Type* type) {
-  return Const(name, storage, type, nullptr, {});
-}
-
-Variable* Builder::Const(const std::string& name,
-                         StorageClass storage,
-                         type::Type* type,
-                         Expression* constructor,
-                         VariableDecorationList decorations) {
-  auto* var = create<Variable>(program->Symbols().Register(name), storage, type,
-                               true, constructor, decorations);
-  OnVariableBuilt(var);
-  return var;
-}
-
-Variable* Builder::Const(const Source& source,
-                         const std::string& name,
-                         StorageClass storage,
-                         type::Type* type,
-                         Expression* constructor,
-                         VariableDecorationList decorations) {
-  auto* var = create<Variable>(source, program->Symbols().Register(name),
-                               storage, type, true, constructor, decorations);
-  OnVariableBuilt(var);
-  return var;
-}
-
-BuilderWithProgram::BuilderWithProgram() : Builder(new Program()) {}
-
-BuilderWithProgram::~BuilderWithProgram() {
-  delete program;
-}
-
-}  // namespace ast
-}  // namespace tint
diff --git a/src/ast/builder.h b/src/ast/builder.h
deleted file mode 100644
index d77b0b9..0000000
--- a/src/ast/builder.h
+++ /dev/null
@@ -1,804 +0,0 @@
-// Copyright 2020 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_AST_BUILDER_H_
-#define SRC_AST_BUILDER_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "src/ast/array_accessor_expression.h"
-#include "src/ast/binary_expression.h"
-#include "src/ast/bool_literal.h"
-#include "src/ast/call_expression.h"
-#include "src/ast/expression.h"
-#include "src/ast/float_literal.h"
-#include "src/ast/identifier_expression.h"
-#include "src/ast/member_accessor_expression.h"
-#include "src/ast/scalar_constructor_expression.h"
-#include "src/ast/sint_literal.h"
-#include "src/ast/struct.h"
-#include "src/ast/struct_member.h"
-#include "src/ast/struct_member_offset_decoration.h"
-#include "src/ast/type_constructor_expression.h"
-#include "src/ast/uint_literal.h"
-#include "src/ast/variable.h"
-#include "src/program.h"
-#include "src/type/alias_type.h"
-#include "src/type/array_type.h"
-#include "src/type/bool_type.h"
-#include "src/type/f32_type.h"
-#include "src/type/i32_type.h"
-#include "src/type/matrix_type.h"
-#include "src/type/pointer_type.h"
-#include "src/type/struct_type.h"
-#include "src/type/u32_type.h"
-#include "src/type/vector_type.h"
-#include "src/type/void_type.h"
-
-namespace tint {
-namespace ast {
-
-/// TypesBuilder holds basic `tint` types and methods for constructing
-/// complex types.
-class TypesBuilder {
- public:
-  /// Constructor
-  /// @param program the program
-  explicit TypesBuilder(Program* program);
-
-  /// @return the tint AST type for the C type `T`.
-  template <typename T>
-  type::Type* Of() const {
-    return CToAST<T>::get(this);
-  }
-
-  /// @returns A boolean type
-  type::Bool* bool_() const { return program_->create<type::Bool>(); }
-
-  /// @returns A f32 type
-  type::F32* f32() const { return program_->create<type::F32>(); }
-
-  /// @returns A i32 type
-  type::I32* i32() const { return program_->create<type::I32>(); }
-
-  /// @returns A u32 type
-  type::U32* u32() const { return program_->create<type::U32>(); }
-
-  /// @returns A void type
-  type::Void* void_() const { return program_->create<type::Void>(); }
-
-  /// @return the tint AST type for a 2-element vector of the C type `T`.
-  template <typename T>
-  type::Vector* vec2() const {
-    return program_->create<type::Vector>(Of<T>(), 2);
-  }
-
-  /// @return the tint AST type for a 3-element vector of the C type `T`.
-  template <typename T>
-  type::Vector* vec3() const {
-    return program_->create<type::Vector>(Of<T>(), 3);
-  }
-
-  /// @return the tint AST type for a 4-element vector of the C type `T`.
-  template <typename T>
-  type::Type* vec4() const {
-    return program_->create<type::Vector>(Of<T>(), 4);
-  }
-
-  /// @return the tint AST type for a 2x3 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat2x2() const {
-    return program_->create<type::Matrix>(Of<T>(), 2, 2);
-  }
-
-  /// @return the tint AST type for a 2x3 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat2x3() const {
-    return program_->create<type::Matrix>(Of<T>(), 3, 2);
-  }
-
-  /// @return the tint AST type for a 2x4 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat2x4() const {
-    return program_->create<type::Matrix>(Of<T>(), 4, 2);
-  }
-
-  /// @return the tint AST type for a 3x2 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat3x2() const {
-    return program_->create<type::Matrix>(Of<T>(), 2, 3);
-  }
-
-  /// @return the tint AST type for a 3x3 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat3x3() const {
-    return program_->create<type::Matrix>(Of<T>(), 3, 3);
-  }
-
-  /// @return the tint AST type for a 3x4 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat3x4() const {
-    return program_->create<type::Matrix>(Of<T>(), 4, 3);
-  }
-
-  /// @return the tint AST type for a 4x2 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat4x2() const {
-    return program_->create<type::Matrix>(Of<T>(), 2, 4);
-  }
-
-  /// @return the tint AST type for a 4x3 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat4x3() const {
-    return program_->create<type::Matrix>(Of<T>(), 3, 4);
-  }
-
-  /// @return the tint AST type for a 4x4 matrix of the C type `T`.
-  template <typename T>
-  type::Matrix* mat4x4() const {
-    return program_->create<type::Matrix>(Of<T>(), 4, 4);
-  }
-
-  /// @param subtype the array element type
-  /// @param n the array size. 0 represents a runtime-array.
-  /// @return the tint AST type for a array of size `n` of type `T`
-  type::Array* array(type::Type* subtype, uint32_t n) const {
-    return program_->create<type::Array>(subtype, n, ArrayDecorationList{});
-  }
-
-  /// @return the tint AST type for an array of size `N` of type `T`
-  template <typename T, int N = 0>
-  type::Array* array() const {
-    return array(Of<T>(), N);
-  }
-
-  /// Creates an alias type
-  /// @param name the alias name
-  /// @param type the alias type
-  /// @returns the alias pointer
-  type::Alias* alias(const std::string& name, type::Type* type) const {
-    return program_->create<type::Alias>(program_->Symbols().Register(name),
-                                         type);
-  }
-
-  /// @return the tint AST pointer to type `T` with the given StorageClass.
-  /// @param storage_class the storage class of the pointer
-  template <typename T>
-  type::Pointer* pointer(StorageClass storage_class) const {
-    return program_->create<type::Pointer>(Of<T>(), storage_class);
-  }
-
-  /// @param name the struct name
-  /// @param impl the struct implementation
-  /// @returns a struct pointer
-  type::Struct* struct_(const std::string& name, ast::Struct* impl) const {
-    return program_->create<type::Struct>(program_->Symbols().Register(name),
-                                          impl);
-  }
-
- private:
-  /// CToAST<T> is specialized for various `T` types and each specialization
-  /// contains a single static `get()` method for obtaining the corresponding
-  /// AST type for the C type `T`.
-  /// `get()` has the signature:
-  ///    `static type::Type* get(Types* t)`
-  template <typename T>
-  struct CToAST {};
-
-  Program* const program_;
-};
-
-/// Helper for building common AST constructs.
-class Builder {
- public:
-  /// `i32` is a type alias to `int`.
-  /// Useful for passing to template methods such as `vec2<i32>()` to imitate
-  /// WGSL syntax.
-  /// Note: this is intentionally not aliased to uint32_t as we want integer
-  /// literals passed to the builder to match WGSL's integer literal types.
-  using i32 = decltype(1);
-  /// `u32` is a type alias to `unsigned int`.
-  /// Useful for passing to template methods such as `vec2<u32>()` to imitate
-  /// WGSL syntax.
-  /// Note: this is intentionally not aliased to uint32_t as we want integer
-  /// literals passed to the builder to match WGSL's integer literal types.
-  using u32 = decltype(1u);
-  /// `f32` is a type alias to `float`
-  /// Useful for passing to template methods such as `vec2<f32>()` to imitate
-  /// WGSL syntax.
-  using f32 = float;
-
-  /// Constructor
-  /// @param program the program to build
-  explicit Builder(Program* program);
-  virtual ~Builder();
-
-  /// @param expr the expression
-  /// @return expr
-  Expression* Expr(Expression* expr) { return expr; }
-
-  /// @param name the identifier name
-  /// @return an IdentifierExpression with the given name
-  IdentifierExpression* Expr(const std::string& name) {
-    return create<IdentifierExpression>(program->Symbols().Register(name));
-  }
-
-  /// @param source the source information
-  /// @param name the identifier name
-  /// @return an IdentifierExpression with the given name
-  IdentifierExpression* Expr(const Source& source, const std::string& name) {
-    return create<IdentifierExpression>(source,
-                                        program->Symbols().Register(name));
-  }
-
-  /// @param name the identifier name
-  /// @return an IdentifierExpression with the given name
-  IdentifierExpression* Expr(const char* name) {
-    return create<IdentifierExpression>(program->Symbols().Register(name));
-  }
-
-  /// @param value the boolean value
-  /// @return a Scalar constructor for the given value
-  ScalarConstructorExpression* Expr(bool value) {
-    return create<ScalarConstructorExpression>(Literal(value));
-  }
-
-  /// @param value the float value
-  /// @return a Scalar constructor for the given value
-  ScalarConstructorExpression* Expr(f32 value) {
-    return create<ScalarConstructorExpression>(Literal(value));
-  }
-
-  /// @param value the integer value
-  /// @return a Scalar constructor for the given value
-  ScalarConstructorExpression* Expr(i32 value) {
-    return create<ScalarConstructorExpression>(Literal(value));
-  }
-
-  /// @param value the unsigned int value
-  /// @return a Scalar constructor for the given value
-  ScalarConstructorExpression* Expr(u32 value) {
-    return create<ScalarConstructorExpression>(Literal(value));
-  }
-
-  /// Converts `arg` to an `Expression` using `Expr()`, then appends it to
-  /// `list`.
-  /// @param list the list to append too
-  /// @param arg the arg to create
-  template <typename ARG>
-  void Append(ExpressionList& list, ARG&& arg) {
-    list.emplace_back(Expr(std::forward<ARG>(arg)));
-  }
-
-  /// Converts `arg0` and `args` to `Expression`s using `Expr()`,
-  /// then appends them to `list`.
-  /// @param list the list to append too
-  /// @param arg0 the first argument
-  /// @param args the rest of the arguments
-  template <typename ARG0, typename... ARGS>
-  void Append(ExpressionList& list, ARG0&& arg0, ARGS&&... args) {
-    Append(list, std::forward<ARG0>(arg0));
-    Append(list, std::forward<ARGS>(args)...);
-  }
-
-  /// @return an empty list of expressions
-  ExpressionList ExprList() { return {}; }
-
-  /// @param args the list of expressions
-  /// @return the list of expressions converted to `Expression`s using
-  /// `Expr()`,
-  template <typename... ARGS>
-  ExpressionList ExprList(ARGS&&... args) {
-    ExpressionList list;
-    list.reserve(sizeof...(args));
-    Append(list, std::forward<ARGS>(args)...);
-    return list;
-  }
-
-  /// @param list the list of expressions
-  /// @return `list`
-  ExpressionList ExprList(ExpressionList list) { return list; }
-
-  /// @param val the boolan value
-  /// @return a boolean literal with the given value
-  BoolLiteral* Literal(bool val) {
-    return create<BoolLiteral>(ty.bool_(), val);
-  }
-
-  /// @param val the float value
-  /// @return a float literal with the given value
-  FloatLiteral* Literal(f32 val) { return create<FloatLiteral>(ty.f32(), val); }
-
-  /// @param val the unsigned int value
-  /// @return a UintLiteral with the given value
-  UintLiteral* Literal(u32 val) { return create<UintLiteral>(ty.u32(), val); }
-
-  /// @param val the integer value
-  /// @return the SintLiteral with the given value
-  SintLiteral* Literal(i32 val) { return create<SintLiteral>(ty.i32(), val); }
-
-  /// @param args the arguments for the type constructor
-  /// @return an `TypeConstructorExpression` of type `ty`, with the values
-  /// of `args` converted to `Expression`s using `Expr()`
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* Construct(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.Of<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param type the type to construct
-  /// @param args the arguments for the constructor
-  /// @return an `TypeConstructorExpression` of `type` constructed with the
-  /// values `args`.
-  template <typename... ARGS>
-  TypeConstructorExpression* Construct(type::Type* type, ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        type, ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @return an `TypeConstructorExpression` of a 2-element vector of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* vec2(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.vec2<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @return an `TypeConstructorExpression` of a 3-element vector of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* vec3(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.vec3<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the vector constructor
-  /// @return an `TypeConstructorExpression` of a 4-element vector of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* vec4(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.vec4<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 2x2 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat2x2(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat2x2<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 2x3 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat2x3(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat2x3<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 2x4 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat2x4(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat2x4<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 3x2 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat3x2(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat3x2<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 3x3 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat3x3(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat3x3<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 3x4 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat3x4(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat3x4<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 4x2 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat4x2(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat4x2<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 4x3 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat4x3(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat4x3<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the matrix constructor
-  /// @return an `TypeConstructorExpression` of a 4x4 matrix of type
-  /// `T`, constructed with the values `args`.
-  template <typename T, typename... ARGS>
-  TypeConstructorExpression* mat4x4(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.mat4x4<T>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param args the arguments for the array constructor
-  /// @return an `TypeConstructorExpression` of an array with element type
-  /// `T`, constructed with the values `args`.
-  template <typename T, int N = 0, typename... ARGS>
-  TypeConstructorExpression* array(ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.array<T, N>(), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param subtype the array element type
-  /// @param n the array size. 0 represents a runtime-array.
-  /// @param args the arguments for the array constructor
-  /// @return an `TypeConstructorExpression` of an array with element type
-  /// `subtype`, constructed with the values `args`.
-  template <typename... ARGS>
-  TypeConstructorExpression* array(type::Type* subtype,
-                                   uint32_t n,
-                                   ARGS&&... args) {
-    return create<TypeConstructorExpression>(
-        ty.array(subtype, n), ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param name the variable name
-  /// @param storage the variable storage class
-  /// @param type the variable type
-  /// @returns a `Variable` with the given name, storage and type. The variable
-  /// will be built with a nullptr constructor and no decorations.
-  Variable* Var(const std::string& name,
-                StorageClass storage,
-                type::Type* type);
-
-  /// @param name the variable name
-  /// @param storage the variable storage class
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param decorations variable decorations
-  /// @returns a `Variable` with the given name, storage and type
-  Variable* Var(const std::string& name,
-                StorageClass storage,
-                type::Type* type,
-                Expression* constructor,
-                VariableDecorationList decorations);
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param storage the variable storage class
-  /// @param type the variable type
-  /// @param constructor constructor expression
-  /// @param decorations variable decorations
-  /// @returns a `Variable` with the given name, storage and type
-  Variable* Var(const Source& source,
-                const std::string& name,
-                StorageClass storage,
-                type::Type* type,
-                Expression* constructor,
-                VariableDecorationList decorations);
-
-  /// @param name the variable name
-  /// @param storage the variable storage class
-  /// @param type the variable type
-  /// @returns a constant `Variable` with the given name, storage and type. The
-  /// variable will be built with a nullptr constructor and no decorations.
-  Variable* Const(const std::string& name,
-                  StorageClass storage,
-                  type::Type* type);
-
-  /// @param name the variable name
-  /// @param storage the variable storage class
-  /// @param type the variable type
-  /// @param constructor optional constructor expression
-  /// @param decorations optional variable decorations
-  /// @returns a constant `Variable` with the given name, storage and type
-  Variable* Const(const std::string& name,
-                  StorageClass storage,
-                  type::Type* type,
-                  Expression* constructor,
-                  VariableDecorationList decorations);
-
-  /// @param source the variable source
-  /// @param name the variable name
-  /// @param storage the variable storage class
-  /// @param type the variable type
-  /// @param constructor optional constructor expression
-  /// @param decorations optional variable decorations
-  /// @returns a constant `Variable` with the given name, storage and type
-  Variable* Const(const Source& source,
-                  const std::string& name,
-                  StorageClass storage,
-                  type::Type* type,
-                  Expression* constructor,
-                  VariableDecorationList decorations);
-
-  /// @param func the function name
-  /// @param args the function call arguments
-  /// @returns a `CallExpression` to the function `func`, with the
-  /// arguments of `args` converted to `Expression`s using `Expr()`.
-  template <typename NAME, typename... ARGS>
-  CallExpression* Call(NAME&& func, ARGS&&... args) {
-    return create<CallExpression>(Expr(func),
-                                  ExprList(std::forward<ARGS>(args)...));
-  }
-
-  /// @param lhs the left hand argument to the addition operation
-  /// @param rhs the right hand argument to the addition operation
-  /// @returns a `BinaryExpression` summing the arguments `lhs` and `rhs`
-  template <typename LHS, typename RHS>
-  Expression* Add(LHS&& lhs, RHS&& rhs) {
-    return create<BinaryExpression>(BinaryOp::kAdd,
-                                    Expr(std::forward<LHS>(lhs)),
-                                    Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the subtraction operation
-  /// @param rhs the right hand argument to the subtraction operation
-  /// @returns a `BinaryExpression` subtracting `rhs` from `lhs`
-  template <typename LHS, typename RHS>
-  Expression* Sub(LHS&& lhs, RHS&& rhs) {
-    return create<BinaryExpression>(BinaryOp::kSubtract,
-                                    Expr(std::forward<LHS>(lhs)),
-                                    Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param lhs the left hand argument to the multiplication operation
-  /// @param rhs the right hand argument to the multiplication operation
-  /// @returns a `BinaryExpression` multiplying `rhs` from `lhs`
-  template <typename LHS, typename RHS>
-  Expression* Mul(LHS&& lhs, RHS&& rhs) {
-    return create<BinaryExpression>(BinaryOp::kMultiply,
-                                    Expr(std::forward<LHS>(lhs)),
-                                    Expr(std::forward<RHS>(rhs)));
-  }
-
-  /// @param arr the array argument for the array accessor expression
-  /// @param idx the index argument for the array accessor expression
-  /// @returns a `ArrayAccessorExpression` that indexes `arr` with `idx`
-  template <typename ARR, typename IDX>
-  Expression* IndexAccessor(ARR&& arr, IDX&& idx) {
-    return create<ArrayAccessorExpression>(Expr(std::forward<ARR>(arr)),
-                                           Expr(std::forward<IDX>(idx)));
-  }
-
-  /// @param obj the object for the member accessor expression
-  /// @param idx the index argument for the array accessor expression
-  /// @returns a `MemberAccessorExpression` that indexes `obj` with `idx`
-  template <typename OBJ, typename IDX>
-  Expression* MemberAccessor(OBJ&& obj, IDX&& idx) {
-    return create<MemberAccessorExpression>(Expr(std::forward<OBJ>(obj)),
-                                            Expr(std::forward<IDX>(idx)));
-  }
-
-  /// Creates a StructMemberOffsetDecoration
-  /// @param val the offset value
-  /// @returns the offset decoration pointer
-  StructMemberOffsetDecoration* MemberOffset(uint32_t val) {
-    return program->create<StructMemberOffsetDecoration>(source_, val);
-  }
-
-  /// Creates a Function
-  /// @param source the source information
-  /// @param name the function name
-  /// @param params the function parameters
-  /// @param type the function return type
-  /// @param body the function body
-  /// @param decorations the function decorations
-  /// @returns the function pointer
-  Function* Func(Source source,
-                 std::string name,
-                 ast::VariableList params,
-                 type::Type* type,
-                 ast::StatementList body,
-                 ast::FunctionDecorationList decorations) {
-    return program->create<ast::Function>(
-        source, program->Symbols().Register(name), params, type,
-        create<ast::BlockStatement>(body), decorations);
-  }
-
-  /// Creates a Function
-  /// @param name the function name
-  /// @param params the function parameters
-  /// @param type the function return type
-  /// @param body the function body
-  /// @param decorations the function decorations
-  /// @returns the function pointer
-  Function* Func(std::string name,
-                 ast::VariableList params,
-                 type::Type* type,
-                 ast::StatementList body,
-                 ast::FunctionDecorationList decorations) {
-    return create<ast::Function>(program->Symbols().Register(name), params,
-                                 type, create<ast::BlockStatement>(body),
-                                 decorations);
-  }
-
-  /// Creates a StructMember
-  /// @param source the source information
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @returns the struct member pointer
-  StructMember* Member(const Source& source,
-                       const std::string& name,
-                       type::Type* type) {
-    return program->create<StructMember>(source,
-                                         program->Symbols().Register(name),
-                                         type, StructMemberDecorationList{});
-  }
-
-  /// Creates a StructMember
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @returns the struct member pointer
-  StructMember* Member(const std::string& name, type::Type* type) {
-    return program->create<StructMember>(source_,
-                                         program->Symbols().Register(name),
-                                         type, StructMemberDecorationList{});
-  }
-
-  /// Creates a StructMember
-  /// @param name the struct member name
-  /// @param type the struct member type
-  /// @param decos the struct member decorations
-  /// @returns the struct member pointer
-  StructMember* Member(const std::string& name,
-                       type::Type* type,
-                       StructMemberDecorationList decos) {
-    return program->create<StructMember>(
-        source_, program->Symbols().Register(name), type, decos);
-  }
-
-  /// Creates a new Node owned by the Module, with the explicit Source.
-  /// When the Module is destructed, the `Node` will also be destructed.
-  /// @param source the source to apply to the Node
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, Node>* create(const Source& source,
-                                          ARGS&&... args) {
-    return program->create<T>(source, std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new Node owned by the Module, with the explicit Source.
-  /// When the Module is destructed, the `Node` will also be destructed.
-  /// @param source the source to apply to the Node
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, Node>* create(Source&& source, ARGS&&... args) {
-    return program->create<T>(std::move(source), std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new type::Type owned by the Module, using the Builder's
-  /// current Source. When the Module is destructed, the `Node` will also be
-  /// destructed.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, Node>* create(ARGS&&... args) {
-    return program->create<T>(source_, std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new type::Type owned by the Module.
-  /// When the Module is destructed, owned Module and the returned `Type` will
-  /// also be destructed. Types are unique (de-aliased), and so calling create()
-  /// for the same `T` and arguments will return the same pointer.
-  /// @warning Use this method to acquire a type only if all of its type
-  /// information is provided in the constructor arguments `args`.<br>
-  /// If the type requires additional configuration after construction that
-  /// affect its fundamental type, build the type with `std::make_unique`, make
-  /// any necessary alterations and then call unique_type() instead.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the de-aliased type pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, type::Type>* create(ARGS&&... args) {
-    static_assert(std::is_base_of<type::Type, T>::value,
-                  "T does not derive from type::Type");
-    return program->create<T>(std::forward<ARGS>(args)...);
-  }
-
-  /// Sets the current builder source to `src`
-  /// @param src the Source used for future create() calls
-  void SetSource(const Source& src) { source_ = src; }
-
-  /// Sets the current builder source to `loc`
-  /// @param loc the Source used for future create() calls
-  void SetSource(const Source::Location& loc) { source_ = Source(loc); }
-
-  /// @returns true if all required fields in the AST are present.
-  bool IsValid() const { return mod->IsValid(); }
-
-  /// @returns a reference to the program's AST root Module
-  ast::Module& AST() { return mod->AST(); }
-
-  /// @returns a reference to the program's SymbolTable
-  SymbolTable& Symbols() { return mod->Symbols(); }
-
-  /// The builder program
-  Program* const program;
-  /// The builder types
-  const TypesBuilder ty;
-
-  /// [DEPRECATED] Temporary alias to #program
-  Program* const mod;
-
- protected:
-  /// Called whenever a new variable is built with `Var()`.
-  virtual void OnVariableBuilt(Variable*) {}
-
-  /// The source to use when creating AST nodes.
-  Source source_;
-};
-
-/// BuilderWithProgram is a `Builder` that constructs and owns its `Program`.
-class BuilderWithProgram : public Builder {
- public:
-  BuilderWithProgram();
-  ~BuilderWithProgram() override;
-};
-
-//! @cond Doxygen_Suppress
-// Various template specializations for TypesBuilder::CToAST.
-template <>
-struct TypesBuilder::CToAST<Builder::i32> {
-  static type::Type* get(const TypesBuilder* t) { return t->i32(); }
-};
-template <>
-struct TypesBuilder::CToAST<Builder::u32> {
-  static type::Type* get(const TypesBuilder* t) { return t->u32(); }
-};
-template <>
-struct TypesBuilder::CToAST<Builder::f32> {
-  static type::Type* get(const TypesBuilder* t) { return t->f32(); }
-};
-template <>
-struct TypesBuilder::CToAST<bool> {
-  static type::Type* get(const TypesBuilder* t) { return t->bool_(); }
-};
-template <>
-struct TypesBuilder::CToAST<void> {
-  static type::Type* get(const TypesBuilder* t) { return t->void_(); }
-};
-//! @endcond
-
-}  // namespace ast
-}  // namespace tint
-
-#endif  // SRC_AST_BUILDER_H_
diff --git a/src/ast/builtin_decoration.cc b/src/ast/builtin_decoration.cc
index d0db918..1c33150 100644
--- a/src/ast/builtin_decoration.cc
+++ b/src/ast/builtin_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/builtin_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::BuiltinDecoration);
 
diff --git a/src/ast/call_expression.cc b/src/ast/call_expression.cc
index 61e5316..674350a 100644
--- a/src/ast/call_expression.cc
+++ b/src/ast/call_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/call_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::CallExpression);
 
diff --git a/src/ast/call_statement.cc b/src/ast/call_statement.cc
index 82eedc8..66b94e5 100644
--- a/src/ast/call_statement.cc
+++ b/src/ast/call_statement.cc
@@ -16,7 +16,7 @@
 
 #include "src/ast/call_expression.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::CallStatement);
 
diff --git a/src/ast/case_statement.cc b/src/ast/case_statement.cc
index 84e6b21..ac1a267 100644
--- a/src/ast/case_statement.cc
+++ b/src/ast/case_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/case_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::CaseStatement);
 
diff --git a/src/ast/constant_id_decoration.cc b/src/ast/constant_id_decoration.cc
index c2bb255..463e5b3 100644
--- a/src/ast/constant_id_decoration.cc
+++ b/src/ast/constant_id_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/constant_id_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::ConstantIdDecoration);
 
diff --git a/src/ast/continue_statement.cc b/src/ast/continue_statement.cc
index 98557bc..58edfba 100644
--- a/src/ast/continue_statement.cc
+++ b/src/ast/continue_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/continue_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::ContinueStatement);
 
diff --git a/src/ast/discard_statement.cc b/src/ast/discard_statement.cc
index 1a522cc..3d4ae66 100644
--- a/src/ast/discard_statement.cc
+++ b/src/ast/discard_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/discard_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::DiscardStatement);
 
diff --git a/src/ast/else_statement.cc b/src/ast/else_statement.cc
index 67c52f2..63201a3 100644
--- a/src/ast/else_statement.cc
+++ b/src/ast/else_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/else_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::ElseStatement);
 
diff --git a/src/ast/fallthrough_statement.cc b/src/ast/fallthrough_statement.cc
index f18ea60..ede2ca6 100644
--- a/src/ast/fallthrough_statement.cc
+++ b/src/ast/fallthrough_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/fallthrough_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::FallthroughStatement);
 
diff --git a/src/ast/float_literal.cc b/src/ast/float_literal.cc
index 734706f..6e48199 100644
--- a/src/ast/float_literal.cc
+++ b/src/ast/float_literal.cc
@@ -18,7 +18,7 @@
 #include <sstream>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::FloatLiteral);
 
diff --git a/src/ast/function.cc b/src/ast/function.cc
index 9123896..ceb2d3d 100644
--- a/src/ast/function.cc
+++ b/src/ast/function.cc
@@ -20,7 +20,7 @@
 #include "src/ast/variable.h"
 #include "src/ast/workgroup_decoration.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/multisampled_texture_type.h"
 #include "src/type/sampled_texture_type.h"
 #include "src/type/texture_type.h"
diff --git a/src/ast/function_test.cc b/src/ast/function_test.cc
index fac3dd1..1181efa 100644
--- a/src/ast/function_test.cc
+++ b/src/ast/function_test.cc
@@ -36,7 +36,7 @@
 
   auto* f = Func("func", params, ty.void_(), StatementList{},
                  FunctionDecorationList{});
-  EXPECT_EQ(f->symbol(), Symbols().Register("func"));
+  EXPECT_EQ(f->symbol(), Symbols().Get("func"));
   ASSERT_EQ(f->params().size(), 1u);
   EXPECT_EQ(f->return_type(), ty.void_());
   EXPECT_EQ(f->params()[0], var);
@@ -151,7 +151,7 @@
   auto* f = Func("func", VariableList{}, ty.void_(), StatementList{},
                  FunctionDecorationList{});
 
-  auto main_sym = Symbols().Register("main");
+  auto main_sym = Symbols().Get("main");
   f->add_ancestor_entry_point(main_sym);
   ASSERT_EQ(1u, f->ancestor_entry_points().size());
   EXPECT_EQ(main_sym, f->ancestor_entry_points()[0]);
diff --git a/src/ast/group_decoration.cc b/src/ast/group_decoration.cc
index 738882c..019cc36 100644
--- a/src/ast/group_decoration.cc
+++ b/src/ast/group_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/group_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::GroupDecoration);
 
diff --git a/src/ast/identifier_expression.cc b/src/ast/identifier_expression.cc
index e10c664..c6c0779 100644
--- a/src/ast/identifier_expression.cc
+++ b/src/ast/identifier_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/identifier_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::IdentifierExpression);
 
diff --git a/src/ast/if_statement.cc b/src/ast/if_statement.cc
index e7e0609..16a677d 100644
--- a/src/ast/if_statement.cc
+++ b/src/ast/if_statement.cc
@@ -16,7 +16,7 @@
 
 #include "src/ast/else_statement.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::IfStatement);
 
diff --git a/src/ast/intrinsic_texture_helper_test.cc b/src/ast/intrinsic_texture_helper_test.cc
index ac48c3e..56fc77a 100644
--- a/src/ast/intrinsic_texture_helper_test.cc
+++ b/src/ast/intrinsic_texture_helper_test.cc
@@ -14,8 +14,8 @@
 
 #include "src/ast/intrinsic_texture_helper_test.h"
 
-#include "src/ast/builder.h"
 #include "src/ast/type_constructor_expression.h"
+#include "src/program_builder.h"
 #include "src/type/access_control_type.h"
 #include "src/type/depth_texture_type.h"
 #include "src/type/multisampled_texture_type.h"
@@ -27,9 +27,9 @@
 namespace intrinsic {
 namespace test {
 
-using u32 = ast::Builder::u32;
-using i32 = ast::Builder::i32;
-using f32 = ast::Builder::f32;
+using u32 = ProgramBuilder::u32;
+using i32 = ProgramBuilder::i32;
+using f32 = ProgramBuilder::f32;
 
 TextureOverloadCase::TextureOverloadCase(
     ValidTextureOverload o,
@@ -39,7 +39,7 @@
     type::TextureDimension dims,
     TextureDataType datatype,
     const char* f,
-    std::function<ExpressionList(Builder*)> a)
+    std::function<ExpressionList(ProgramBuilder*)> a)
     : overload(o),
       description(desc),
       texture_kind(tk),
@@ -55,7 +55,7 @@
     type::TextureDimension dims,
     TextureDataType datatype,
     const char* f,
-    std::function<ExpressionList(Builder*)> a)
+    std::function<ExpressionList(ProgramBuilder*)> a)
     : overload(o),
       description(desc),
       texture_kind(tk),
@@ -71,7 +71,7 @@
     type::TextureDimension dims,
     TextureDataType datatype,
     const char* f,
-    std::function<ExpressionList(Builder*)> a)
+    std::function<ExpressionList(ProgramBuilder*)> a)
     : overload(o),
       description(d),
       texture_kind(TextureKind::kStorage),
@@ -136,7 +136,7 @@
 }
 
 type::Type* TextureOverloadCase::resultVectorComponentType(
-    ast::Builder* b) const {
+    ProgramBuilder* b) const {
   switch (texture_data_type) {
     case ast::intrinsic::test::TextureDataType::kF32:
       return b->ty.f32();
@@ -151,7 +151,7 @@
 }
 
 ast::Variable* TextureOverloadCase::buildTextureVariable(
-    ast::Builder* b) const {
+    ProgramBuilder* b) const {
   auto* datatype = resultVectorComponentType(b);
 
   VariableDecorationList decos = {
@@ -192,7 +192,7 @@
 }
 
 ast::Variable* TextureOverloadCase::buildSamplerVariable(
-    ast::Builder* b) const {
+    ProgramBuilder* b) const {
   VariableDecorationList decos = {
       b->create<ast::GroupDecoration>(0),
       b->create<ast::BindingDecoration>(1),
@@ -211,7 +211,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensions1dArray,
@@ -221,7 +221,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensions2d,
@@ -231,7 +231,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensions2dLevel,
@@ -242,7 +242,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensions2dArray,
@@ -252,7 +252,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensions2dArrayLevel,
@@ -263,7 +263,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensions3d,
@@ -273,7 +273,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensions3dLevel,
@@ -284,7 +284,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsCube,
@@ -294,7 +294,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsCubeLevel,
@@ -305,7 +305,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsCubeArray,
@@ -315,7 +315,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsCubeArrayLevel,
@@ -326,7 +326,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsMultisampled2d,
@@ -336,7 +336,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsMultisampled2dArray,
@@ -347,7 +347,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsDepth2d,
@@ -357,7 +357,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsDepth2dLevel,
@@ -368,7 +368,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsDepth2dArray,
@@ -378,7 +378,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsDepth2dArrayLevel,
@@ -389,7 +389,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsDepthCube,
@@ -399,7 +399,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsDepthCubeLevel,
@@ -410,7 +410,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsDepthCubeArray,
@@ -420,7 +420,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsDepthCubeArrayLevel,
@@ -431,7 +431,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture", 1); },
+          [](ProgramBuilder* b) { return b->ExprList("texture", 1); },
       },
       {
           ValidTextureOverload::kDimensionsStorageRO1d,
@@ -441,7 +441,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageRO1dArray,
@@ -452,7 +452,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageRO2d,
@@ -463,7 +463,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageRO2dArray,
@@ -474,7 +474,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageRO3d,
@@ -485,7 +485,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageWO1d,
@@ -495,7 +495,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageWO1dArray,
@@ -506,7 +506,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageWO2d,
@@ -517,7 +517,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageWO2dArray,
@@ -528,7 +528,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kDimensionsStorageWO3d,
@@ -539,7 +539,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureDimensions",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayers1dArray,
@@ -549,7 +549,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayers2dArray,
@@ -559,7 +559,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayersCubeArray,
@@ -569,7 +569,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayersMultisampled2dArray,
@@ -579,7 +579,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayersDepth2dArray,
@@ -589,7 +589,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayersDepthCubeArray,
@@ -599,7 +599,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayersStorageWO1dArray,
@@ -609,7 +609,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLayersStorageWO2dArray,
@@ -619,7 +619,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumLayers",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevels2d,
@@ -629,7 +629,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevels2dArray,
@@ -639,7 +639,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevels3d,
@@ -649,7 +649,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevelsCube,
@@ -659,7 +659,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevelsCubeArray,
@@ -669,7 +669,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevelsDepth2d,
@@ -679,7 +679,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevelsDepth2dArray,
@@ -689,7 +689,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevelsDepthCube,
@@ -699,7 +699,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumLevelsDepthCubeArray,
@@ -709,7 +709,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureNumLevels",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumSamplesMultisampled2d,
@@ -719,7 +719,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureNumSamples",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kNumSamplesMultisampled2dArray,
@@ -729,7 +729,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureNumSamples",
-          [](Builder* b) { return b->ExprList("texture"); },
+          [](ProgramBuilder* b) { return b->ExprList("texture"); },
       },
       {
           ValidTextureOverload::kSample1dF32,
@@ -741,7 +741,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                "sampler",  // s
                                1.0f);      // coords
@@ -758,7 +758,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                "sampler",  // s
                                1.0f,       // coords
@@ -775,7 +775,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                // t
                                "sampler",                // s
                                b->vec2<f32>(1.f, 2.f));  // coords
@@ -792,7 +792,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -810,7 +810,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -829,7 +829,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -847,7 +847,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                     // t
                                "sampler",                     // s
                                b->vec3<f32>(1.f, 2.f, 3.f));  // coords
@@ -864,7 +864,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -881,7 +881,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                     // t
                                "sampler",                     // s
                                b->vec3<f32>(1.f, 2.f, 3.f));  // coords
@@ -898,7 +898,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -915,7 +915,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                // t
                                "sampler",                // s
                                b->vec2<f32>(1.f, 2.f));  // coords
@@ -932,7 +932,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -950,7 +950,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -969,7 +969,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -987,7 +987,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                     // t
                                "sampler",                     // s
                                b->vec3<f32>(1.f, 2.f, 3.f));  // coords
@@ -1004,7 +1004,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSample",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1022,7 +1022,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1041,7 +1041,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1061,7 +1061,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1082,7 +1082,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1102,7 +1102,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1121,7 +1121,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1140,7 +1140,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1159,7 +1159,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSampleBias",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1178,7 +1178,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1197,7 +1197,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1217,7 +1217,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1238,7 +1238,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1258,7 +1258,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1277,7 +1277,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1296,7 +1296,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1315,7 +1315,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1334,7 +1334,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1353,7 +1353,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1373,7 +1373,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1394,7 +1394,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1414,7 +1414,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1433,7 +1433,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSampleLevel",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1453,7 +1453,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                  // t
                                "sampler",                  // s
                                b->vec2<f32>(1.0f, 2.0f),   // coords
@@ -1474,7 +1474,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1496,7 +1496,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                // t
                                "sampler",                // s
                                b->vec2<f32>(1.f, 2.f),   // coords
@@ -1519,7 +1519,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1541,7 +1541,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                     // t
                                "sampler",                     // s
                                b->vec3<f32>(1.f, 2.f, 3.f),   // coords
@@ -1562,7 +1562,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1583,7 +1583,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                     // t
                                "sampler",                     // s
                                b->vec3<f32>(1.f, 2.f, 3.f),   // coords
@@ -1604,7 +1604,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSampleGrad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                      // t
                                "sampler",                      // s
                                b->vec3<f32>(1.f, 2.f, 3.f),    // coords
@@ -1624,7 +1624,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleCompare",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1643,7 +1643,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureSampleCompare",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1663,7 +1663,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleCompare",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1684,7 +1684,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureSampleCompare",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                "sampler",               // s
                                b->vec2<f32>(1.f, 2.f),  // coords
@@ -1704,7 +1704,7 @@
           type::TextureDimension::kCube,
           TextureDataType::kF32,
           "textureSampleCompare",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1723,7 +1723,7 @@
           type::TextureDimension::kCubeArray,
           TextureDataType::kF32,
           "textureSampleCompare",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                    // t
                                "sampler",                    // s
                                b->vec3<f32>(1.f, 2.f, 3.f),  // coords
@@ -1739,7 +1739,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1);         // coords
           },
@@ -1752,7 +1752,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1);         // coords
           },
@@ -1765,7 +1765,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1);         // coords
           },
@@ -1779,7 +1779,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1,          // coords
                                2);         // array_index
@@ -1794,7 +1794,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1,          // coords
                                2);         // array_index
@@ -1809,7 +1809,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1,          // coords
                                2);         // array_index
@@ -1823,7 +1823,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // coords
           },
@@ -1836,7 +1836,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // coords
           },
@@ -1849,7 +1849,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // coords
           },
@@ -1863,7 +1863,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // level
@@ -1878,7 +1878,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // level
@@ -1893,7 +1893,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // level
@@ -1908,7 +1908,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // array_index
@@ -1923,7 +1923,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // array_index
@@ -1938,7 +1938,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // array_index
@@ -1954,7 +1954,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -1971,7 +1971,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -1988,7 +1988,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -2003,7 +2003,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                b->vec3<i32>(1, 2, 3));  // coords
           },
@@ -2016,7 +2016,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                b->vec3<i32>(1, 2, 3));  // coords
           },
@@ -2029,7 +2029,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                b->vec3<i32>(1, 2, 3));  // coords
           },
@@ -2043,7 +2043,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",              // t
                                b->vec3<i32>(1, 2, 3),  // coords
                                4);                     // level
@@ -2058,7 +2058,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",              // t
                                b->vec3<i32>(1, 2, 3),  // coords
                                4);                     // level
@@ -2073,7 +2073,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",              // t
                                b->vec3<i32>(1, 2, 3),  // coords
                                4);                     // level
@@ -2088,7 +2088,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // sample_index
@@ -2103,7 +2103,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // sample_index
@@ -2118,7 +2118,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // sample_index
@@ -2134,7 +2134,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -2151,7 +2151,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -2168,7 +2168,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -2183,7 +2183,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // coords
           },
@@ -2197,7 +2197,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // level
@@ -2212,7 +2212,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // array_index
@@ -2228,7 +2228,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -2244,7 +2244,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1);         // coords
           },
@@ -2260,7 +2260,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1,          // coords
                                2);         // array_index
@@ -2275,7 +2275,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2289,7 +2289,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2303,7 +2303,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2317,7 +2317,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2331,7 +2331,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2345,7 +2345,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2359,7 +2359,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2373,7 +2373,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2387,7 +2387,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2401,7 +2401,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2415,7 +2415,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2429,7 +2429,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2443,7 +2443,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2457,7 +2457,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kU32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2471,7 +2471,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kI32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2485,7 +2485,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",            // t
                                b->vec2<i32>(1, 2));  // array_index
           },
@@ -2501,7 +2501,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3);                  // array_index
@@ -2516,7 +2516,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureLoad",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",               // t
                                b->vec3<i32>(1, 2, 3));  // coords
           },
@@ -2531,7 +2531,7 @@
           type::TextureDimension::k1d,
           TextureDataType::kF32,
           "textureStore",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                          // t
                                1,                                  // coords
                                b->vec4<f32>(2.f, 3.f, 4.f, 5.f));  // value
@@ -2548,7 +2548,7 @@
           type::TextureDimension::k1dArray,
           TextureDataType::kF32,
           "textureStore",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",  // t
                                1,          // coords
                                2,          // array_index
@@ -2565,7 +2565,7 @@
           type::TextureDimension::k2d,
           TextureDataType::kF32,
           "textureStore",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                          // t
                                b->vec2<i32>(1, 2),                 // coords
                                b->vec4<f32>(3.f, 4.f, 5.f, 6.f));  // value
@@ -2582,7 +2582,7 @@
           type::TextureDimension::k2dArray,
           TextureDataType::kF32,
           "textureStore",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",           // t
                                b->vec2<i32>(1, 2),  // coords
                                3,                   // array_index
@@ -2599,7 +2599,7 @@
           type::TextureDimension::k3d,
           TextureDataType::kF32,
           "textureStore",
-          [](Builder* b) {
+          [](ProgramBuilder* b) {
             return b->ExprList("texture",                          // t
                                b->vec3<i32>(1, 2, 3),              // coords
                                b->vec4<f32>(4.f, 5.f, 6.f, 7.f));  // value
diff --git a/src/ast/intrinsic_texture_helper_test.h b/src/ast/intrinsic_texture_helper_test.h
index f8a4b53..c405c16 100644
--- a/src/ast/intrinsic_texture_helper_test.h
+++ b/src/ast/intrinsic_texture_helper_test.h
@@ -19,7 +19,7 @@
 #include <vector>
 
 #include "src/ast/access_control.h"
-#include "src/ast/builder.h"
+#include "src/program_builder.h"
 #include "src/type/sampler_type.h"
 #include "src/type/storage_texture_type.h"
 #include "src/type/texture_type.h"
@@ -211,7 +211,7 @@
                       type::TextureDimension,
                       TextureDataType,
                       const char*,
-                      std::function<ExpressionList(Builder*)>);
+                      std::function<ExpressionList(ProgramBuilder*)>);
   /// Constructor for textureLoad() functions with non-storage textures
   TextureOverloadCase(ValidTextureOverload,
                       const char*,
@@ -219,7 +219,7 @@
                       type::TextureDimension,
                       TextureDataType,
                       const char*,
-                      std::function<ExpressionList(Builder*)>);
+                      std::function<ExpressionList(ProgramBuilder*)>);
   /// Constructor for textureLoad() with storage textures
   TextureOverloadCase(ValidTextureOverload,
                       const char*,
@@ -228,7 +228,7 @@
                       type::TextureDimension,
                       TextureDataType,
                       const char*,
-                      std::function<ExpressionList(Builder*)>);
+                      std::function<ExpressionList(ProgramBuilder*)>);
   /// Copy constructor
   TextureOverloadCase(const TextureOverloadCase&);
   /// Destructor
@@ -240,13 +240,13 @@
 
   /// @param builder the AST builder used for the test
   /// @returns the vector component type of the texture function return value
-  type::Type* resultVectorComponentType(ast::Builder* builder) const;
+  type::Type* resultVectorComponentType(ProgramBuilder* builder) const;
   /// @param builder the AST builder used for the test
   /// @returns a Variable holding the test texture
-  ast::Variable* buildTextureVariable(ast::Builder* builder) const;
+  ast::Variable* buildTextureVariable(ProgramBuilder* builder) const;
   /// @param builder the AST builder used for the test
   /// @returns a Variable holding the test sampler
-  ast::Variable* buildSamplerVariable(ast::Builder* builder) const;
+  ast::Variable* buildSamplerVariable(ProgramBuilder* builder) const;
 
   /// The enumerator for this overload
   ValidTextureOverload const overload;
@@ -270,7 +270,7 @@
   /// Name of the function. e.g. `textureSample`, `textureSampleGrad`, etc
   const char* const function;
   /// A function that builds the AST arguments for the overload
-  std::function<ExpressionList(Builder*)> const args;
+  std::function<ExpressionList(ProgramBuilder*)> const args;
 };
 
 std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data);
diff --git a/src/ast/location_decoration.cc b/src/ast/location_decoration.cc
index 7b23c2a..1eff8a6 100644
--- a/src/ast/location_decoration.cc
+++ b/src/ast/location_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/location_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::LocationDecoration);
 
diff --git a/src/ast/loop_statement.cc b/src/ast/loop_statement.cc
index cee9f23..68730c4 100644
--- a/src/ast/loop_statement.cc
+++ b/src/ast/loop_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/loop_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::LoopStatement);
 
diff --git a/src/ast/member_accessor_expression.cc b/src/ast/member_accessor_expression.cc
index d25c6e8..20e6ee6 100644
--- a/src/ast/member_accessor_expression.cc
+++ b/src/ast/member_accessor_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/member_accessor_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::MemberAccessorExpression);
 
diff --git a/src/ast/module.cc b/src/ast/module.cc
index e39e048..9d99c17 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -18,8 +18,7 @@
 #include <string>
 #include <utility>
 
-#include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/alias_type.h"
 #include "src/type/struct_type.h"
 
@@ -28,12 +27,13 @@
 namespace tint {
 namespace ast {
 
-Module::Module() : Base(Source{}) {}
+Module::Module(const Source& source) : Base(source) {}
 
-Module::Module(std::vector<type::Type*> constructed_types,
+Module::Module(const Source& source,
+               std::vector<type::Type*> constructed_types,
                FunctionList functions,
                VariableList global_variables)
-    : Base(Source{}),
+    : Base(source),
       constructed_types_(std::move(constructed_types)),
       functions_(std::move(functions)),
       global_variables_(std::move(global_variables)) {}
diff --git a/src/ast/module.h b/src/ast/module.h
index 7227131..bbc3c0b 100644
--- a/src/ast/module.h
+++ b/src/ast/module.h
@@ -31,13 +31,16 @@
 class Module : public Castable<Module, Node> {
  public:
   /// Constructor
-  Module();
+  /// @param source the source of the module
+  explicit Module(const Source& source);
 
   /// Constructor
+  /// @param source the source of the module
   /// @param constructed_types the list of types explicitly declared in the AST
   /// @param functions the list of program functions
   /// @param global_variables the list of global variables
-  Module(std::vector<type::Type*> constructed_types,
+  Module(const Source& source,
+         std::vector<type::Type*> constructed_types,
          FunctionList functions,
          VariableList global_variables);
 
diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc
index 995b1e5..a3bc10f 100644
--- a/src/ast/module_clone_test.cc
+++ b/src/ast/module_clone_test.cc
@@ -16,7 +16,10 @@
 
 #include "gtest/gtest.h"
 #include "src/ast/case_statement.h"
+#include "src/ast/module.h"
 #include "src/demangler.h"
+#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/reader/wgsl/parser.h"
 #include "src/writer/wgsl/generator.h"
 
@@ -114,12 +117,12 @@
   auto src = parser.program();
 
   // Clone the src program to dst
-  auto dst = src.Clone();
+  Program dst(src.Clone());
 
   // Expect the AST printed with to_str() to match
   Demangler demanger;
-  EXPECT_EQ(demanger.Demangle(src.Symbols(), src.to_str()),
-            demanger.Demangle(dst.Symbols(), dst.to_str()));
+  EXPECT_EQ(demanger.Demangle(src.Symbols(), src.AST().to_str()),
+            demanger.Demangle(dst.Symbols(), dst.AST().to_str()));
 
   // Check that none of the AST nodes or type pointers in dst are found in src
   std::unordered_set<ast::Node*> src_nodes;
@@ -142,8 +145,8 @@
   // comparison.
   std::string src_wgsl;
   {
-    tint::writer::wgsl::Generator src_gen(&src);
-    ASSERT_TRUE(src_gen.Generate());
+    writer::wgsl::Generator src_gen(&src);
+    ASSERT_TRUE(src_gen.Generate()) << src_gen.error();
     src_wgsl = src_gen.result();
 
     // Move the src program to a temporary that'll be dropped, so that the src
@@ -154,8 +157,8 @@
     auto tmp = std::move(src);
   }
 
-  // Print the dst program, check it matches the original source
-  tint::writer::wgsl::Generator dst_gen(&dst);
+  // Print the dst module, check it matches the original source
+  writer::wgsl::Generator dst_gen(&dst);
   ASSERT_TRUE(dst_gen.Generate());
   auto dst_wgsl = dst_gen.result();
   ASSERT_EQ(src_wgsl, dst_wgsl);
diff --git a/src/ast/module_test.cc b/src/ast/module_test.cc
new file mode 100644
index 0000000..f987023
--- /dev/null
+++ b/src/ast/module_test.cc
@@ -0,0 +1,139 @@
+// Copyright 2020 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/ast/module.h"
+
+#include <sstream>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "src/ast/function.h"
+#include "src/ast/test_helper.h"
+#include "src/ast/variable.h"
+#include "src/type/alias_type.h"
+#include "src/type/f32_type.h"
+#include "src/type/struct_type.h"
+
+namespace tint {
+namespace ast {
+namespace {
+
+using ModuleTest = TestHelper;
+
+TEST_F(ModuleTest, Creation) {
+  EXPECT_EQ(Program(std::move(*this)).AST().Functions().size(), 0u);
+}
+
+TEST_F(ModuleTest, ToStrEmitsPreambleAndPostamble) {
+  const auto str = Program(std::move(*this)).AST().to_str();
+  auto* const expected = "Module{\n}\n";
+  EXPECT_EQ(str, expected);
+}
+
+TEST_F(ModuleTest, LookupFunction) {
+  auto* func = Func("main", VariableList{}, ty.f32(), StatementList{},
+                    ast::FunctionDecorationList{});
+  AST().Functions().Add(func);
+  Program program(std::move(*this));
+  EXPECT_EQ(func,
+            program.AST().Functions().Find(program.Symbols().Get("main")));
+}
+
+TEST_F(ModuleTest, LookupFunctionMissing) {
+  Program program(std::move(*this));
+  EXPECT_EQ(nullptr,
+            program.AST().Functions().Find(program.Symbols().Get("Missing")));
+}
+
+TEST_F(ModuleTest, IsValid_Empty) {
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_GlobalVariable) {
+  auto* var = Var("var", StorageClass::kInput, ty.f32());
+  AST().AddGlobalVariable(var);
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Null_GlobalVariable) {
+  AST().AddGlobalVariable(nullptr);
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Invalid_GlobalVariable) {
+  auto* var = Var("var", StorageClass::kInput, nullptr);
+  AST().AddGlobalVariable(var);
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Alias) {
+  auto* alias = ty.alias("alias", ty.f32());
+  AST().AddConstructedType(alias);
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Null_Alias) {
+  AST().AddConstructedType(nullptr);
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Struct) {
+  auto* st = ty.struct_("name", {});
+  auto* alias = ty.alias("name", st);
+  AST().AddConstructedType(alias);
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Struct_EmptyName) {
+  auto* st = ty.struct_("", {});
+  auto* alias = ty.alias("name", st);
+  AST().AddConstructedType(alias);
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Function) {
+  auto* func = Func("main", VariableList(), ty.f32(), StatementList{},
+                    ast::FunctionDecorationList{});
+
+  AST().Functions().Add(func);
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Null_Function) {
+  AST().Functions().Add(nullptr);
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.AST().IsValid());
+}
+
+TEST_F(ModuleTest, IsValid_Invalid_Function) {
+  auto* func = Func("main", VariableList{}, nullptr, StatementList{},
+                    ast::FunctionDecorationList{});
+
+  AST().Functions().Add(func);
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.AST().IsValid());
+}
+
+}  // namespace
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/null_literal.cc b/src/ast/null_literal.cc
index 7a52a7f..edcf896 100644
--- a/src/ast/null_literal.cc
+++ b/src/ast/null_literal.cc
@@ -15,7 +15,7 @@
 #include "src/ast/null_literal.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::NullLiteral);
 
diff --git a/src/ast/return_statement.cc b/src/ast/return_statement.cc
index 8602460..38133c2 100644
--- a/src/ast/return_statement.cc
+++ b/src/ast/return_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/return_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::ReturnStatement);
 
diff --git a/src/ast/scalar_constructor_expression.cc b/src/ast/scalar_constructor_expression.cc
index dfa4ae3..ebbecc6 100644
--- a/src/ast/scalar_constructor_expression.cc
+++ b/src/ast/scalar_constructor_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/scalar_constructor_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::ScalarConstructorExpression);
 
diff --git a/src/ast/sint_literal.cc b/src/ast/sint_literal.cc
index 4957286..bc78d50 100644
--- a/src/ast/sint_literal.cc
+++ b/src/ast/sint_literal.cc
@@ -15,7 +15,7 @@
 #include "src/ast/sint_literal.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::SintLiteral);
 
diff --git a/src/ast/stage_decoration.cc b/src/ast/stage_decoration.cc
index e359ac4..53cb362 100644
--- a/src/ast/stage_decoration.cc
+++ b/src/ast/stage_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/stage_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::StageDecoration);
 
diff --git a/src/ast/stride_decoration.cc b/src/ast/stride_decoration.cc
index 1473d02..0dd1990 100644
--- a/src/ast/stride_decoration.cc
+++ b/src/ast/stride_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/stride_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::StrideDecoration);
 
diff --git a/src/ast/struct.cc b/src/ast/struct.cc
index 92e01f7..cf696c5 100644
--- a/src/ast/struct.cc
+++ b/src/ast/struct.cc
@@ -16,7 +16,7 @@
 
 #include "src/ast/struct_block_decoration.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::Struct);
 
diff --git a/src/ast/struct_block_decoration.cc b/src/ast/struct_block_decoration.cc
index 55af859..2e1cf92 100644
--- a/src/ast/struct_block_decoration.cc
+++ b/src/ast/struct_block_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/struct_block_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::StructBlockDecoration);
 
diff --git a/src/ast/struct_member.cc b/src/ast/struct_member.cc
index 4ecd79d..0cc7a71 100644
--- a/src/ast/struct_member.cc
+++ b/src/ast/struct_member.cc
@@ -16,7 +16,7 @@
 
 #include "src/ast/struct_member_offset_decoration.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::StructMember);
 
diff --git a/src/ast/struct_member_offset_decoration.cc b/src/ast/struct_member_offset_decoration.cc
index 71fbfc9..09ebef6 100644
--- a/src/ast/struct_member_offset_decoration.cc
+++ b/src/ast/struct_member_offset_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/struct_member_offset_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::StructMemberOffsetDecoration);
 
diff --git a/src/ast/switch_statement.cc b/src/ast/switch_statement.cc
index 3e536cd..4bc34b3 100644
--- a/src/ast/switch_statement.cc
+++ b/src/ast/switch_statement.cc
@@ -16,7 +16,7 @@
 
 #include "src/ast/case_statement.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::SwitchStatement);
 
diff --git a/src/ast/test_helper.h b/src/ast/test_helper.h
index 8ae08f5..5621a32 100644
--- a/src/ast/test_helper.h
+++ b/src/ast/test_helper.h
@@ -20,16 +20,15 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
 #include "src/demangler.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 namespace tint {
 namespace ast {
 
 /// Helper class for testing
 template <typename BASE>
-class TestHelperBase : public BASE, public BuilderWithProgram {
+class TestHelperBase : public BASE, public ProgramBuilder {
  public:
   /// Demangles the given string
   /// @param s the string to demangle
diff --git a/src/ast/type_constructor_expression.cc b/src/ast/type_constructor_expression.cc
index b5807b4..2afde7b 100644
--- a/src/ast/type_constructor_expression.cc
+++ b/src/ast/type_constructor_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/type_constructor_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::TypeConstructorExpression);
 
diff --git a/src/ast/uint_literal.cc b/src/ast/uint_literal.cc
index 00d414b..076b896 100644
--- a/src/ast/uint_literal.cc
+++ b/src/ast/uint_literal.cc
@@ -15,7 +15,7 @@
 #include "src/ast/uint_literal.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::UintLiteral);
 
diff --git a/src/ast/unary_op_expression.cc b/src/ast/unary_op_expression.cc
index 21c6a05..890e1ed 100644
--- a/src/ast/unary_op_expression.cc
+++ b/src/ast/unary_op_expression.cc
@@ -15,7 +15,7 @@
 #include "src/ast/unary_op_expression.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::UnaryOpExpression);
 
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
index 0e4c336..52e869b 100644
--- a/src/ast/variable.cc
+++ b/src/ast/variable.cc
@@ -18,7 +18,7 @@
 
 #include "src/ast/constant_id_decoration.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::Variable);
 
diff --git a/src/ast/variable_decl_statement.cc b/src/ast/variable_decl_statement.cc
index 91a56d1..8bdddcb 100644
--- a/src/ast/variable_decl_statement.cc
+++ b/src/ast/variable_decl_statement.cc
@@ -15,7 +15,7 @@
 #include "src/ast/variable_decl_statement.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::VariableDeclStatement);
 
diff --git a/src/ast/workgroup_decoration.cc b/src/ast/workgroup_decoration.cc
index 57ea5e4..914278a 100644
--- a/src/ast/workgroup_decoration.cc
+++ b/src/ast/workgroup_decoration.cc
@@ -15,7 +15,7 @@
 #include "src/ast/workgroup_decoration.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::ast::WorkgroupDecoration);
 
diff --git a/src/clone_context.cc b/src/clone_context.cc
index b5866cb..618148e 100644
--- a/src/clone_context.cc
+++ b/src/clone_context.cc
@@ -14,11 +14,14 @@
 
 #include "src/clone_context.h"
 
+#include "src/ast/function.h"
+#include "src/ast/module.h"
 #include "src/program.h"
+#include "src/program_builder.h"
 
 namespace tint {
 
-CloneContext::CloneContext(Program* to, Program const* from)
+CloneContext::CloneContext(ProgramBuilder* to, Program const* from)
     : dst(to), src(from) {}
 CloneContext::~CloneContext() = default;
 
@@ -27,7 +30,15 @@
 }
 
 void CloneContext::Clone() {
-  src->Clone(this);
+  for (auto* ty : src->AST().ConstructedTypes()) {
+    dst->AST().AddConstructedType(Clone(ty));
+  }
+  for (auto* var : src->AST().GlobalVariables()) {
+    dst->AST().AddGlobalVariable(Clone(var));
+  }
+  for (auto* func : src->AST().Functions()) {
+    dst->AST().Functions().Add(Clone(func));
+  }
 }
 
 ast::FunctionList CloneContext::Clone(const ast::FunctionList& v) {
diff --git a/src/clone_context.h b/src/clone_context.h
index 7d82532..17109e2 100644
--- a/src/clone_context.h
+++ b/src/clone_context.h
@@ -19,7 +19,6 @@
 #include <unordered_map>
 #include <vector>
 
-#include "src/ast/function.h"
 #include "src/castable.h"
 #include "src/source.h"
 #include "src/symbol.h"
@@ -29,26 +28,34 @@
 
 // Forward declarations
 class Program;
+class ProgramBuilder;
+
+namespace ast {
+
+class FunctionList;
+
+}  // namespace ast
 
 /// CloneContext holds the state used while cloning AST nodes and types.
 class CloneContext {
  public:
   /// Constructor
-  /// @param to the target program to clone into
-  /// @param from the source program to clone from
-  CloneContext(Program* to, Program const* from);
+  /// @param to the target ProgramBuilder to clone into
+  /// @param from the source Program to clone from
+  CloneContext(ProgramBuilder* to, Program const* from);
 
   /// Destructor
   ~CloneContext();
 
-  /// Clones the Node or type::Type `a` into the program #dst if `a` is not
-  /// null. If `a` is null, then Clone() returns null. If `a` has been cloned
-  /// already by this CloneContext then the same cloned pointer is returned.
+  /// Clones the Node or type::Type `a` into the ProgramBuilder #dst if `a` is
+  /// not null. If `a` is null, then Clone() returns null. If `a` has been
+  /// cloned already by this CloneContext then the same cloned pointer is
+  /// returned.
   ///
   /// Clone() may use a function registered with ReplaceAll() to create a
   /// transformed version of the object. See ReplaceAll() for more information.
   ///
-  /// The Node or type::Type `a` must be owned by the program #src.
+  /// The Node or type::Type `a` must be owned by the Program #src.
   ///
   /// @note Semantic information such as resolved expression type and intrinsic
   /// information is not cloned.
@@ -85,15 +92,16 @@
 
   /// Clones the Symbol `s` into `dst`
   ///
-  /// The Symbol `s` must be owned by the program #src.
+  /// The Symbol `s` must be owned by the Program #src.
   ///
   /// @param s the Symbol to clone
   /// @return the cloned source
   Symbol Clone(const Symbol& s) const;
 
-  /// Clones each of the elements of the vector `v` into the program #dst.
+  /// Clones each of the elements of the vector `v` into the ProgramBuilder
+  /// #dst.
   ///
-  /// All the elements of the vector `v` must be owned by the program #src.
+  /// All the elements of the vector `v` must be owned by the Program #src.
   ///
   /// @param v the vector to clone
   /// @return the cloned vector
@@ -107,7 +115,8 @@
     return out;
   }
 
-  /// Clones each of the elements of the vector `v` into the Program #dst.
+  /// Clones each of the elements of the vector `v` into the ProgramBuilder
+  /// #dst.
   ///
   /// All the elements of the vector `v` must be owned by the Program #src.
   ///
@@ -134,9 +143,10 @@
   ///   // Replace all ast::UintLiterals with the number 42
   ///   CloneCtx ctx(&out, in)
   ///     .ReplaceAll([&] (CloneContext* ctx, ast::UintLiteral* l) {
-  ///       return ctx->dst->create<ast::UintLiteral>(ctx->Clone(l->source()),
-  ///                                                 ctx->Clone(l->type()),
-  ///                                                 42);
+  ///       return ctx->dst->create<ast::UintLiteral>(
+  ///           ctx->Clone(l->source()),
+  ///           ctx->Clone(l->type()),
+  ///           42);
   ///     }).Clone();
   /// ```
   ///
@@ -167,13 +177,13 @@
     return *this;
   }
 
-  /// Clone performs the clone of the entire program #src to #dst.
+  /// Clone performs the clone of the entire Program #src to #dst.
   void Clone();
 
-  /// The target program to clone into.
-  Program* const dst;
+  /// The target ProgramBuilder to clone into.
+  ProgramBuilder* const dst;
 
-  /// The source program to clone from.
+  /// The source Program to clone from.
   Program const* const src;
 
  private:
diff --git a/src/clone_context_test.cc b/src/clone_context_test.cc
index ff6a781..6460eae 100644
--- a/src/clone_context_test.cc
+++ b/src/clone_context_test.cc
@@ -14,15 +14,17 @@
 
 #include "src/clone_context.h"
 
+#include <utility>
+
 #include "gtest/gtest.h"
 
-#include "src/program.h"
+#include "src/program_builder.h"
 
 namespace tint {
 namespace {
 
 struct Cloneable : public Castable<Cloneable, ast::Node> {
-  Cloneable() : Base(Source{}) {}
+  explicit Cloneable(const Source& source) : Base(source) {}
 
   Cloneable* a = nullptr;
   Cloneable* b = nullptr;
@@ -40,18 +42,23 @@
   void to_str(std::ostream&, size_t) const override {}
 };
 
-struct Replaceable : public Castable<Replaceable, Cloneable> {};
-struct Replacement : public Castable<Replacement, Replaceable> {};
+struct Replaceable : public Castable<Replaceable, Cloneable> {
+  explicit Replaceable(const Source& source) : Base(source) {}
+};
+struct Replacement : public Castable<Replacement, Replaceable> {
+  explicit Replacement(const Source& source) : Base(source) {}
+};
 
 TEST(CloneContext, Clone) {
-  Program original;
-  auto* original_root = original.create<Cloneable>();
-  original_root->a = original.create<Cloneable>();
-  original_root->a->b = original.create<Cloneable>();
-  original_root->b = original.create<Cloneable>();
+  ProgramBuilder builder;
+  auto* original_root = builder.create<Cloneable>();
+  original_root->a = builder.create<Cloneable>();
+  original_root->a->b = builder.create<Cloneable>();
+  original_root->b = builder.create<Cloneable>();
   original_root->b->a = original_root->a;  // Aliased
-  original_root->b->b = original.create<Cloneable>();
+  original_root->b->b = builder.create<Cloneable>();
   original_root->c = original_root->b;  // Aliased
+  Program original(std::move(builder));
 
   //                          root
   //        ╭──────────────────┼──────────────────╮
@@ -63,7 +70,7 @@
   //
   // C: Clonable
 
-  Program cloned;
+  ProgramBuilder cloned;
   auto* cloned_root = CloneContext(&cloned, &original).Clone(original_root);
 
   EXPECT_NE(cloned_root->a, nullptr);
@@ -87,14 +94,15 @@
   EXPECT_EQ(cloned_root->c, cloned_root->b);     // Aliased
 }
 
-TEST(CloneContext, CloneWithReplaceAll) {
-  Program original;
-  auto* original_root = original.create<Cloneable>();
-  original_root->a = original.create<Cloneable>();
-  original_root->a->b = original.create<Replaceable>();
-  original_root->b = original.create<Replaceable>();
+TEST(CloneContext, CloneWithReplacements) {
+  ProgramBuilder builder;
+  auto* original_root = builder.create<Cloneable>();
+  original_root->a = builder.create<Cloneable>();
+  original_root->a->b = builder.create<Replaceable>();
+  original_root->b = builder.create<Replaceable>();
   original_root->b->a = original_root->a;  // Aliased
   original_root->c = original_root->b;     // Aliased
+  Program original(std::move(builder));
 
   //                          root
   //        ╭──────────────────┼──────────────────╮
@@ -107,7 +115,7 @@
   // C: Clonable
   // R: Replaceable
 
-  Program cloned;
+  ProgramBuilder cloned;
   auto* cloned_root = CloneContext(&cloned, &original)
                           .ReplaceAll([&](CloneContext* ctx, Replaceable* in) {
                             auto* out = cloned.create<Replacement>();
@@ -161,18 +169,19 @@
 }
 
 TEST(CloneContext, CloneWithReplace) {
-  Program original;
-  auto* original_root = original.create<Cloneable>();
-  original_root->a = original.create<Cloneable>();
-  original_root->b = original.create<Cloneable>();
-  original_root->c = original.create<Cloneable>();
+  ProgramBuilder builder;
+  auto* original_root = builder.create<Cloneable>();
+  original_root->a = builder.create<Cloneable>();
+  original_root->b = builder.create<Cloneable>();
+  original_root->c = builder.create<Cloneable>();
+  Program original(std::move(builder));
 
   //                          root
   //        ╭──────────────────┼──────────────────╮
   //       (a)                (b)                (c)
   //                        Replaced
 
-  Program cloned;
+  ProgramBuilder cloned;
   auto* replacement = cloned.create<Cloneable>();
 
   auto* cloned_root = CloneContext(&cloned, &original)
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index 4919970..3c2e7a4 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -22,11 +22,13 @@
 #include "src/ast/constructor_expression.h"
 #include "src/ast/float_literal.h"
 #include "src/ast/function.h"
+#include "src/ast/module.h"
 #include "src/ast/null_literal.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable.h"
+#include "src/program.h"
 #include "src/type/access_control_type.h"
 #include "src/type/array_type.h"
 #include "src/type/f32_type.h"
@@ -43,27 +45,27 @@
 namespace tint {
 namespace inspector {
 
-Inspector::Inspector(const Program* program) : program_(*program) {}
+Inspector::Inspector(const Program* program) : program_(program) {}
 
 Inspector::~Inspector() = default;
 
 std::vector<EntryPoint> Inspector::GetEntryPoints() {
   std::vector<EntryPoint> result;
 
-  for (auto* func : program_.AST().Functions()) {
+  for (auto* func : program_->AST().Functions()) {
     if (!func->IsEntryPoint()) {
       continue;
     }
 
     EntryPoint entry_point;
-    entry_point.name = program_.Symbols().NameFor(func->symbol());
-    entry_point.remapped_name = program_.Symbols().NameFor(func->symbol());
+    entry_point.name = program_->Symbols().NameFor(func->symbol());
+    entry_point.remapped_name = program_->Symbols().NameFor(func->symbol());
     entry_point.stage = func->pipeline_stage();
     std::tie(entry_point.workgroup_size_x, entry_point.workgroup_size_y,
              entry_point.workgroup_size_z) = func->workgroup_size();
 
     for (auto* var : func->referenced_module_variables()) {
-      auto name = program_.Symbols().NameFor(var->symbol());
+      auto name = program_->Symbols().NameFor(var->symbol());
       if (var->HasBuiltinDecoration()) {
         continue;
       }
@@ -106,7 +108,7 @@
 
 std::map<uint32_t, Scalar> Inspector::GetConstantIDs() {
   std::map<uint32_t, Scalar> result;
-  for (auto* var : program_.AST().GlobalVariables()) {
+  for (auto* var : program_->AST().GlobalVariables()) {
     if (!var->HasConstantIdDecoration()) {
       continue;
     }
@@ -283,7 +285,7 @@
 }
 
 ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
-  auto* func = program_.AST().Functions().Find(program_.Symbols().Get(name));
+  auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name));
   if (!func) {
     error_ += name + " was not found!";
     return nullptr;
diff --git a/src/inspector/inspector.h b/src/inspector/inspector.h
index 5ba70be..a3f0906 100644
--- a/src/inspector/inspector.h
+++ b/src/inspector/inspector.h
@@ -129,7 +129,7 @@
       const std::string& entry_point);
 
  private:
-  const Program& program_;
+  const Program* program_;
   std::string error_;
 
   /// @param name name of the entry point to find
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 569086b..8f53a60 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -19,7 +19,6 @@
 #include "gtest/gtest.h"
 #include "src/ast/assignment_statement.h"
 #include "src/ast/bool_literal.h"
-#include "src/ast/builder.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/constant_id_decoration.h"
@@ -44,6 +43,7 @@
 #include "src/ast/variable_decl_statement.h"
 #include "src/ast/variable_decoration.h"
 #include "src/ast/workgroup_decoration.h"
+#include "src/program_builder.h"
 #include "src/type/access_control_type.h"
 #include "src/type/array_type.h"
 #include "src/type/bool_type.h"
@@ -67,11 +67,10 @@
 namespace inspector {
 namespace {
 
-class InspectorHelper : public ast::BuilderWithProgram {
+class InspectorHelper : public ProgramBuilder {
  public:
   InspectorHelper()
-      : td_(std::make_unique<TypeDeterminer>(mod)),
-        inspector_(std::make_unique<Inspector>(mod)),
+      : td_(std::make_unique<TypeDeterminer>(this)),
         sampler_type_(type::SamplerKind::kSampler),
         comparison_sampler_type_(type::SamplerKind::kComparisonSampler) {}
 
@@ -611,8 +610,16 @@
     return vec_type(base_type, 3);
   }
 
+  Inspector& Build() {
+    if (inspector_) {
+      return *inspector_;
+    }
+    program_ = std::make_unique<Program>(std::move(*this));
+    inspector_ = std::make_unique<Inspector>(program_.get());
+    return *inspector_;
+  }
+
   TypeDeterminer* td() { return td_.get(); }
-  Inspector* inspector() { return inspector_.get(); }
 
   type::Array* u32_array_type(uint32_t count) {
     if (array_type_memo_.find(count) == array_type_memo_.end()) {
@@ -637,8 +644,8 @@
 
  private:
   std::unique_ptr<TypeDeterminer> td_;
+  std::unique_ptr<Program> program_;
   std::unique_ptr<Inspector> inspector_;
-
   type::Sampler sampler_type_;
   type::Sampler comparison_sampler_type_;
   std::map<uint32_t, type::Array*> array_type_memo_;
@@ -694,8 +701,10 @@
       public testing::TestWithParam<GetMultisampledTextureTestParams> {};
 
 TEST_F(InspectorGetEntryPointTest, NoFunctions) {
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   EXPECT_EQ(0u, result.size());
 }
@@ -703,8 +712,10 @@
 TEST_F(InspectorGetEntryPointTest, NoEntryPoints) {
   AST().Functions().Add(MakeEmptyBodyFunction("foo", {}));
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   EXPECT_EQ(0u, result.size());
 }
@@ -718,8 +729,10 @@
 
   // TODO(dsinclair): Update to run the namer transform when available.
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ("foo", result[0].name);
@@ -742,8 +755,10 @@
 
   // TODO(dsinclair): Update to run the namer transform when available.
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(2u, result.size());
   EXPECT_EQ("foo", result[0].name);
@@ -774,8 +789,10 @@
 
   // TODO(dsinclair): Update to run the namer transform when available.
 
-  auto result = inspector()->GetEntryPoints();
-  EXPECT_FALSE(inspector()->has_error());
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  EXPECT_FALSE(inspector.has_error());
 
   ASSERT_EQ(2u, result.size());
   EXPECT_EQ("foo", result[0].name);
@@ -794,8 +811,10 @@
       });
   AST().Functions().Add(foo);
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   uint32_t x, y, z;
@@ -813,8 +832,10 @@
              });
   AST().Functions().Add(foo);
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   uint32_t x, y, z;
@@ -835,8 +856,10 @@
       });
   AST().Functions().Add(foo);
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].input_variables.size());
@@ -855,8 +878,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -886,8 +911,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -917,8 +944,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -944,8 +973,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -981,8 +1012,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -1022,10 +1055,13 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  // TODO(dsinclair): Update to run the namer transform when available.
+  // TODO(dsinclair): Update to run the namer transform when
+  // available.
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(2u, result.size());
 
@@ -1075,10 +1111,13 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  // TODO(dsinclair): Update to run the namer transform when available.
+  // TODO(dsinclair): Update to run the namer transform when
+  // available.
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(2u, result.size());
 
@@ -1136,8 +1175,10 @@
 
   // TODO(dsinclair): Update to run the namer transform when available.
 
-  auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetEntryPoints();
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -1153,8 +1194,10 @@
 // TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass
 // through
 TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoFunctions) {
-  auto result = inspector()->GetRemappedNameForEntryPoint("foo");
-  ASSERT_TRUE(inspector()->has_error());
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetRemappedNameForEntryPoint("foo");
+  ASSERT_TRUE(inspector.has_error());
 
   EXPECT_EQ("", result);
 }
@@ -1164,8 +1207,10 @@
 TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoEntryPoints) {
   AST().Functions().Add(MakeEmptyBodyFunction("foo", {}));
 
-  auto result = inspector()->GetRemappedNameForEntryPoint("foo");
-  ASSERT_TRUE(inspector()->has_error());
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetRemappedNameForEntryPoint("foo");
+  ASSERT_TRUE(inspector.has_error());
 
   EXPECT_EQ("", result);
 }
@@ -1179,10 +1224,13 @@
              });
   AST().Functions().Add(foo);
 
-  // TODO(dsinclair): Update to run the namer transform when available.
+  // TODO(dsinclair): Update to run the namer transform when
+  // available.
 
-  auto result = inspector()->GetRemappedNameForEntryPoint("foo");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetRemappedNameForEntryPoint("foo");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   EXPECT_EQ("foo", result);
 }
@@ -1197,7 +1245,8 @@
              });
   AST().Functions().Add(foo);
 
-  // TODO(dsinclair): Update to run the namer transform when available.
+  // TODO(dsinclair): Update to run the namer transform when
+  // available.
 
   auto* bar = MakeEmptyBodyFunction(
       "bar", ast::FunctionDecorationList{
@@ -1205,14 +1254,16 @@
              });
   AST().Functions().Add(bar);
 
+  Inspector& inspector = Build();
+
   {
-    auto result = inspector()->GetRemappedNameForEntryPoint("foo");
-    ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+    auto result = inspector.GetRemappedNameForEntryPoint("foo");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
     EXPECT_EQ("foo", result);
   }
   {
-    auto result = inspector()->GetRemappedNameForEntryPoint("bar");
-    ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+    auto result = inspector.GetRemappedNameForEntryPoint("bar");
+    ASSERT_FALSE(inspector.has_error()) << inspector.error();
     EXPECT_EQ("bar", result);
   }
 }
@@ -1224,7 +1275,9 @@
   AddConstantID<bool>("bar", 20, ty.bool_(), &val_true);
   AddConstantID<bool>("baz", 300, ty.bool_(), &val_false);
 
-  auto result = inspector()->GetConstantIDs();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
   ASSERT_EQ(3u, result.size());
 
   ASSERT_TRUE(result.find(1) != result.end());
@@ -1244,7 +1297,9 @@
   AddConstantID<uint32_t>("foo", 1, ty.u32(), nullptr);
   AddConstantID<uint32_t>("bar", 20, ty.u32(), &val);
 
-  auto result = inspector()->GetConstantIDs();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
   ASSERT_EQ(2u, result.size());
 
   ASSERT_TRUE(result.find(1) != result.end());
@@ -1262,7 +1317,9 @@
   AddConstantID<int32_t>("bar", 20, ty.i32(), &val_neg);
   AddConstantID<int32_t>("baz", 300, ty.i32(), &val_pos);
 
-  auto result = inspector()->GetConstantIDs();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
   ASSERT_EQ(3u, result.size());
 
   ASSERT_TRUE(result.find(1) != result.end());
@@ -1286,7 +1343,9 @@
   AddConstantID<float>("baz", 300, ty.f32(), &val_neg);
   AddConstantID<float>("x", 4000, ty.f32(), &val_pos);
 
-  auto result = inspector()->GetConstantIDs();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetConstantIDs();
   ASSERT_EQ(4u, result.size());
 
   ASSERT_TRUE(result.find(1) != result.end());
@@ -1306,9 +1365,11 @@
 }
 
 TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingEntryPoint) {
-  auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_TRUE(inspector()->has_error());
-  std::string error = inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_TRUE(inspector.has_error());
+  std::string error = inspector.error();
   EXPECT_TRUE(error.find("not found") != std::string::npos);
 }
 
@@ -1332,8 +1393,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetUniformBufferResourceBindings("ub_func");
-  std::string error = inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ub_func");
+  std::string error = inspector.error();
   EXPECT_TRUE(error.find("not an entry point") != std::string::npos);
 }
 
@@ -1360,8 +1423,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   EXPECT_EQ(0u, result.size());
 }
 
@@ -1385,8 +1450,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1414,8 +1481,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1458,8 +1527,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(3u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1495,8 +1566,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetUniformBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1524,8 +1597,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1553,8 +1628,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1600,8 +1677,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(3u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1637,8 +1716,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1666,8 +1747,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1695,8 +1778,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(0u, result.size());
 }
 
@@ -1720,9 +1805,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result =
-      inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1769,9 +1855,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result =
-      inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(3u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1807,9 +1894,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result =
-      inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1838,9 +1926,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result =
-      inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1868,9 +1957,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result =
-      inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_EQ(0u, result.size());
 }
 
@@ -1890,8 +1980,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1907,8 +1999,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(0u, result.size());
 }
@@ -1933,8 +2027,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1957,8 +2053,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSamplerResourceBindings("foo");
-  ASSERT_TRUE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("foo");
+  ASSERT_TRUE(inspector.has_error()) << inspector.error();
 }
 
 TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) {
@@ -1977,8 +2075,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(0u, result.size());
 }
@@ -1999,8 +2099,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetComparisonSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -2016,8 +2118,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetComparisonSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(0u, result.size());
 }
@@ -2043,8 +2147,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetComparisonSamplerResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -2067,8 +2173,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSamplerResourceBindings("foo");
-  ASSERT_TRUE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSamplerResourceBindings("foo");
+  ASSERT_TRUE(inspector.has_error()) << inspector.error();
 }
 
 TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) {
@@ -2087,8 +2195,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetComparisonSamplerResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetComparisonSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(0u, result.size());
 }
@@ -2100,8 +2210,10 @@
              });
   AST().Functions().Add(foo);
 
-  auto result = inspector()->GetSampledTextureResourceBindings("foo");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("foo");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   EXPECT_EQ(0u, result.size());
 }
@@ -2125,8 +2237,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -2137,8 +2251,8 @@
   // Prove that sampled and multi-sampled bindings are accounted
   // for separately.
   auto multisampled_result =
-      inspector()->GetMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+      inspector.GetMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_TRUE(multisampled_result.empty());
 }
 
@@ -2216,8 +2330,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetSampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -2287,8 +2403,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
@@ -2299,8 +2417,8 @@
   // Prove that sampled and multi-sampled bindings are accounted
   // for separately.
   auto single_sampled_result =
-      inspector()->GetSampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+      inspector.GetSampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
   ASSERT_TRUE(single_sampled_result.empty());
 }
 
@@ -2340,8 +2458,10 @@
              });
   AST().Functions().Add(foo);
 
-  auto result = inspector()->GetSampledTextureResourceBindings("foo");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetSampledTextureResourceBindings("foo");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   EXPECT_EQ(0u, result.size());
 }
@@ -2367,8 +2487,10 @@
 
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
-  auto result = inspector()->GetMultisampledTextureResourceBindings("ep");
-  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+  Inspector& inspector = Build();
+
+  auto result = inspector.GetMultisampledTextureResourceBindings("ep");
+  ASSERT_FALSE(inspector.has_error()) << inspector.error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].bind_group);
diff --git a/src/program.cc b/src/program.cc
index d5a86cf..be8ceec 100644
--- a/src/program.cc
+++ b/src/program.cc
@@ -15,44 +15,72 @@
 #include "src/program.h"
 
 #include <sstream>
+#include <utility>
 
+#include "src/ast/module.h"
 #include "src/clone_context.h"
-#include "src/type/struct_type.h"
+#include "src/program_builder.h"
 
 namespace tint {
 
-Program::Program() : ast_(nodes_.Create<ast::Module>()) {}
+Program::Program() = default;
 
-Program::Program(Program&& rhs) = default;
+Program::Program(Program&& program)
+    : types_(std::move(program.types_)),
+      nodes_(std::move(program.nodes_)),
+      ast_(std::move(program.ast_)),
+      symbols_(std::move(program.symbols_)) {
+  program.AssertNotMoved();
+  program.moved_ = true;
+}
 
-Program& Program::operator=(Program&& rhs) = default;
+Program::Program(ProgramBuilder&& builder)
+    : types_(std::move(builder.Types())),
+      nodes_(std::move(builder.Nodes())),
+      ast_(nodes_.Create<ast::Module>(Source{},
+                                      builder.AST().ConstructedTypes(),
+                                      builder.AST().Functions(),
+                                      builder.AST().GlobalVariables())),
+      symbols_(std::move(builder.Symbols())) {
+  builder.MarkAsMoved();
+}
 
 Program::~Program() = default;
 
+Program& Program::operator=(Program&& program) {
+  program.AssertNotMoved();
+  program.moved_ = true;
+  types_ = std::move(program.types_);
+  nodes_ = std::move(program.nodes_);
+  ast_ = std::move(program.ast_);
+  symbols_ = std::move(program.symbols_);
+  return *this;
+}
+
 Program Program::Clone() const {
-  Program out;
+  AssertNotMoved();
+  return Program(CloneAsBuilder());
+}
+
+ProgramBuilder Program::CloneAsBuilder() const {
+  AssertNotMoved();
+  ProgramBuilder out;
   CloneContext(&out, this).Clone();
   return out;
 }
 
-void Program::Clone(CloneContext* ctx) const {
-  for (auto* ty : AST().ConstructedTypes()) {
-    ctx->dst->AST().AddConstructedType(ctx->Clone(ty));
-  }
-  for (auto* var : AST().GlobalVariables()) {
-    ctx->dst->AST().AddGlobalVariable(ctx->Clone(var));
-  }
-  for (auto* func : AST().Functions()) {
-    ctx->dst->AST().Functions().Add(ctx->Clone(func));
-  }
-}
-
 bool Program::IsValid() const {
+  AssertNotMoved();
   return ast_->IsValid();
 }
 
 std::string Program::to_str() const {
+  AssertNotMoved();
   return ast_->to_str();
 }
 
+void Program::AssertNotMoved() const {
+  assert(!moved_);
+}
+
 }  // namespace tint
diff --git a/src/program.h b/src/program.h
index d6cc6d4..09b5ec7 100644
--- a/src/program.h
+++ b/src/program.h
@@ -15,26 +15,24 @@
 #ifndef SRC_PROGRAM_H_
 #define SRC_PROGRAM_H_
 
-#include <functional>
-#include <memory>
 #include <string>
-#include <type_traits>
-#include <unordered_map>
-#include <utility>
-#include <vector>
 
 #include "src/ast/function.h"
-#include "src/ast/module.h"
-#include "src/ast/variable.h"
-#include "src/block_allocator.h"
 #include "src/symbol_table.h"
-#include "src/traits.h"
-#include "src/type/alias_type.h"
 #include "src/type/type_manager.h"
 
 namespace tint {
 
-/// Represents all the source in a given program.
+// Forward declarations
+class CloneContext;
+
+namespace ast {
+
+class Module;
+
+}  // namespace ast
+
+/// Program holds the AST, Type information and SymbolTable for a tint program.
 class Program {
  public:
   /// ASTNodes is an alias to BlockAllocator<ast::Node>
@@ -47,84 +45,65 @@
   /// @param rhs the Program to move
   Program(Program&& rhs);
 
+  /// Move constructor from builder
+  /// @param builder the builder used to construct the program
+  explicit Program(ProgramBuilder&& builder);
+
+  /// Destructor
+  ~Program();
+
   /// Move assignment operator
   /// @param rhs the Program to move
   /// @return this Program
   Program& operator=(Program&& rhs);
 
-  /// Destructor
-  ~Program();
-
   /// @returns a reference to the program's types
-  const type::Manager& Types() const { return types_; }
+  const type::Manager& Types() const {
+    AssertNotMoved();
+    return types_;
+  }
 
   /// @returns a reference to the program's AST nodes storage
-  const ASTNodes& Nodes() const { return nodes_; }
+  const ASTNodes& Nodes() const {
+    AssertNotMoved();
+    return nodes_;
+  }
 
   /// @returns a reference to the program's AST root Module
-  const ast::Module& AST() const { return *ast_; }
-
-  /// @returns a reference to the program's AST root Module
-  ast::Module& AST() { return *ast_; }
+  const ast::Module& AST() const {
+    AssertNotMoved();
+    return *ast_;
+  }
 
   /// @returns a reference to the program's SymbolTable
-  const SymbolTable& Symbols() const { return symbols_; }
-
-  /// @returns a reference to the program's SymbolTable
-  SymbolTable& Symbols() { return symbols_; }
+  const SymbolTable& Symbols() const {
+    AssertNotMoved();
+    return symbols_;
+  }
 
   /// @return a deep copy of this program
   Program Clone() const;
 
-  /// Clone this program into `ctx->dst` using the provided CloneContext
-  /// The program will be cloned in this order:
-  /// * Constructed types
-  /// * Global variables
-  /// * Functions
-  /// @param ctx the clone context
-  void Clone(CloneContext* ctx) const;
+  /// @return a deep copy of this Program, as a ProgramBuilder
+  ProgramBuilder CloneAsBuilder() const;
 
-  /// @returns true if all required fields in the AST are present.
+  /// @returns true if the program not missing information
   bool IsValid() const;
 
-  /// @returns a string representation of the program
+  /// @returns a string describing this program.
   std::string to_str() const;
 
-  /// Creates a new Node owned by the Program. When the Program is
-  /// destructed, the Node will also be destructed.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the node pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, ast::Node>* create(ARGS&&... args) {
-    return nodes_.Create<T>(std::forward<ARGS>(args)...);
-  }
-
-  /// Creates a new type::Type owned by the Program.
-  /// When the Program is destructed, owned Program and the returned
-  /// `Type` will also be destructed.
-  /// Types are unique (de-aliased), and so calling create() for the same `T`
-  /// and arguments will return the same pointer.
-  /// @warning Use this method to acquire a type only if all of its type
-  /// information is provided in the constructor arguments `args`.<br>
-  /// If the type requires additional configuration after construction that
-  /// affect its fundamental type, build the type with `std::make_unique`, make
-  /// any necessary alterations and then call unique_type() instead.
-  /// @param args the arguments to pass to the type constructor
-  /// @returns the de-aliased type pointer
-  template <typename T, typename... ARGS>
-  traits::EnableIfIsType<T, type::Type>* create(ARGS&&... args) {
-    static_assert(std::is_base_of<type::Type, T>::value,
-                  "T does not derive from type::Type");
-    return types_.Get<T>(std::forward<ARGS>(args)...);
-  }
-
  private:
   Program(const Program&) = delete;
 
+  /// Asserts that the program has not been moved.
+  void AssertNotMoved() const;
+
   type::Manager types_;
   ASTNodes nodes_;
   ast::Module* ast_;
   SymbolTable symbols_;
+  bool moved_ = false;
 };
 
 }  // namespace tint
diff --git a/src/program_builder.cc b/src/program_builder.cc
new file mode 100644
index 0000000..ccc053d
--- /dev/null
+++ b/src/program_builder.cc
@@ -0,0 +1,124 @@
+// Copyright 2021 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/program_builder.h"
+
+#include <assert.h>
+
+#include <sstream>
+
+#include "src/clone_context.h"
+#include "src/type/struct_type.h"
+
+namespace tint {
+
+ProgramBuilder::ProgramBuilder()
+    : ty(this), ast_(nodes_.Create<ast::Module>(Source{})) {}
+
+ProgramBuilder::ProgramBuilder(ProgramBuilder&& rhs)
+    : ty(std::move(rhs.ty)),
+      types_(std::move(rhs.types_)),
+      nodes_(std::move(rhs.nodes_)),
+      ast_(rhs.ast_),
+      symbols_(std::move(rhs.symbols_)) {
+  rhs.MarkAsMoved();
+}
+
+ProgramBuilder::~ProgramBuilder() = default;
+
+ProgramBuilder& ProgramBuilder::operator=(ProgramBuilder&& rhs) {
+  rhs.MarkAsMoved();
+  AssertNotMoved();
+  ty = std::move(rhs.ty);
+  types_ = std::move(rhs.types_);
+  nodes_ = std::move(rhs.nodes_);
+  ast_ = rhs.ast_;
+  symbols_ = std::move(rhs.symbols_);
+  return *this;
+}
+
+bool ProgramBuilder::IsValid() const {
+  return ast_->IsValid();
+}
+
+void ProgramBuilder::MarkAsMoved() {
+  AssertNotMoved();
+  moved_ = true;
+}
+
+void ProgramBuilder::AssertNotMoved() const {
+  assert(!moved_);
+}
+
+ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {}
+
+ast::Variable* ProgramBuilder::Var(const std::string& name,
+                                   ast::StorageClass storage,
+                                   type::Type* type) {
+  return Var(name, storage, type, nullptr, {});
+}
+
+ast::Variable* ProgramBuilder::Var(const std::string& name,
+                                   ast::StorageClass storage,
+                                   type::Type* type,
+                                   ast::Expression* constructor,
+                                   ast::VariableDecorationList decorations) {
+  auto* var = create<ast::Variable>(Symbols().Register(name), storage, type,
+                                    false, constructor, decorations);
+  OnVariableBuilt(var);
+  return var;
+}
+
+ast::Variable* ProgramBuilder::Var(const Source& source,
+                                   const std::string& name,
+                                   ast::StorageClass storage,
+                                   type::Type* type,
+                                   ast::Expression* constructor,
+                                   ast::VariableDecorationList decorations) {
+  auto* var = create<ast::Variable>(source, Symbols().Register(name), storage,
+                                    type, false, constructor, decorations);
+  OnVariableBuilt(var);
+  return var;
+}
+
+ast::Variable* ProgramBuilder::Const(const std::string& name,
+                                     ast::StorageClass storage,
+                                     type::Type* type) {
+  return Const(name, storage, type, nullptr, {});
+}
+
+ast::Variable* ProgramBuilder::Const(const std::string& name,
+                                     ast::StorageClass storage,
+                                     type::Type* type,
+                                     ast::Expression* constructor,
+                                     ast::VariableDecorationList decorations) {
+  auto* var = create<ast::Variable>(Symbols().Register(name), storage, type,
+                                    true, constructor, decorations);
+  OnVariableBuilt(var);
+  return var;
+}
+
+ast::Variable* ProgramBuilder::Const(const Source& source,
+                                     const std::string& name,
+                                     ast::StorageClass storage,
+                                     type::Type* type,
+                                     ast::Expression* constructor,
+                                     ast::VariableDecorationList decorations) {
+  auto* var = create<ast::Variable>(source, Symbols().Register(name), storage,
+                                    type, true, constructor, decorations);
+  OnVariableBuilt(var);
+  return var;
+}
+
+}  // namespace tint
diff --git a/src/program_builder.h b/src/program_builder.h
new file mode 100644
index 0000000..7e3d602
--- /dev/null
+++ b/src/program_builder.h
@@ -0,0 +1,885 @@
+// Copyright 2021 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_PROGRAM_BUILDER_H_
+#define SRC_PROGRAM_BUILDER_H_
+
+#include <string>
+#include <utility>
+
+#include "src/ast/array_accessor_expression.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/bool_literal.h"
+#include "src/ast/call_expression.h"
+#include "src/ast/expression.h"
+#include "src/ast/float_literal.h"
+#include "src/ast/identifier_expression.h"
+#include "src/ast/member_accessor_expression.h"
+#include "src/ast/module.h"
+#include "src/ast/scalar_constructor_expression.h"
+#include "src/ast/sint_literal.h"
+#include "src/ast/struct.h"
+#include "src/ast/struct_member.h"
+#include "src/ast/struct_member_offset_decoration.h"
+#include "src/ast/type_constructor_expression.h"
+#include "src/ast/uint_literal.h"
+#include "src/ast/variable.h"
+#include "src/program.h"
+#include "src/symbol_table.h"
+#include "src/type/alias_type.h"
+#include "src/type/array_type.h"
+#include "src/type/bool_type.h"
+#include "src/type/f32_type.h"
+#include "src/type/i32_type.h"
+#include "src/type/matrix_type.h"
+#include "src/type/pointer_type.h"
+#include "src/type/struct_type.h"
+#include "src/type/type_manager.h"
+#include "src/type/u32_type.h"
+#include "src/type/vector_type.h"
+#include "src/type/void_type.h"
+
+namespace tint {
+
+class CloneContext;
+
+/// ProgramBuilder is a mutable builder for a Program.
+/// To construct a Program, populate the builder and then `std::move` it to a
+/// Program.
+class ProgramBuilder {
+ public:
+  /// ASTNodes is an alias to BlockAllocator<ast::Node>
+  using ASTNodes = BlockAllocator<ast::Node>;
+
+  /// `i32` is a type alias to `int`.
+  /// Useful for passing to template methods such as `vec2<i32>()` to imitate
+  /// WGSL syntax.
+  /// Note: this is intentionally not aliased to uint32_t as we want integer
+  /// literals passed to the builder to match WGSL's integer literal types.
+  using i32 = decltype(1);
+  /// `u32` is a type alias to `unsigned int`.
+  /// Useful for passing to template methods such as `vec2<u32>()` to imitate
+  /// WGSL syntax.
+  /// Note: this is intentionally not aliased to uint32_t as we want integer
+  /// literals passed to the builder to match WGSL's integer literal types.
+  using u32 = decltype(1u);
+  /// `f32` is a type alias to `float`
+  /// Useful for passing to template methods such as `vec2<f32>()` to imitate
+  /// WGSL syntax.
+  using f32 = float;
+
+  /// Constructor
+  ProgramBuilder();
+
+  /// Move constructor
+  /// @param rhs the builder to move
+  ProgramBuilder(ProgramBuilder&& rhs);
+
+  /// Destructor
+  virtual ~ProgramBuilder();
+
+  /// Move assignment operator
+  /// @param rhs the builder to move
+  /// @return this builder
+  ProgramBuilder& operator=(ProgramBuilder&& rhs);
+
+  /// @returns a reference to the program's types
+  type::Manager& Types() {
+    AssertNotMoved();
+    return types_;
+  }
+
+  /// @returns a reference to the program's AST nodes storage
+  ASTNodes& Nodes() {
+    AssertNotMoved();
+    return nodes_;
+  }
+
+  /// @returns a reference to the program's AST root Module
+  ast::Module& AST() {
+    AssertNotMoved();
+    return *ast_;
+  }
+
+  /// @returns a reference to the program's SymbolTable
+  SymbolTable& Symbols() {
+    AssertNotMoved();
+    return symbols_;
+  }
+
+  /// @returns true if the program not missing information
+  bool IsValid() const;
+
+  /// creates a new ast::Node owned by the Module. When the Module is
+  /// destructed, the ast::Node will also be destructed.
+  /// @param source the Source of the node
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename... ARGS>
+  traits::EnableIfIsType<T, ast::Node>* create(const Source& source,
+                                               ARGS&&... args) {
+    AssertNotMoved();
+    return nodes_.Create<T>(source, std::forward<ARGS>(args)...);
+  }
+
+  /// creates a new ast::Node owned by the Module, injecting the current Source
+  /// as set by the last call to SetSource() as the only argument to the
+  /// constructor.
+  /// When the Module is destructed, the ast::Node will also be destructed.
+  /// @returns the node pointer
+  template <typename T>
+  traits::EnableIfIsType<T, ast::Node>* create() {
+    AssertNotMoved();
+    return nodes_.Create<T>(source_);
+  }
+
+  /// creates a new ast::Node owned by the Module, injecting the current Source
+  /// as set by the last call to SetSource() as the first argument to the
+  /// constructor.
+  /// When the Module is destructed, the ast::Node will also be destructed.
+  /// @param arg0 the first arguments to pass to the type constructor
+  /// @param args the remaining arguments to pass to the type constructor
+  /// @returns the node pointer
+  template <typename T, typename ARG0, typename... ARGS>
+  traits::EnableIf</* T is ast::Node and ARG0 is not Source */
+                   traits::IsTypeOrDerived<T, ast::Node>::value &&
+                       !traits::IsTypeOrDerived<ARG0, Source>::value,
+                   T>*
+  create(ARG0&& arg0, ARGS&&... args) {
+    AssertNotMoved();
+    return nodes_.Create<T>(source_, std::forward<ARG0>(arg0),
+                            std::forward<ARGS>(args)...);
+  }
+
+  /// creates a new type::Type owned by the Module.
+  /// When the Module is destructed, owned Module and the returned
+  /// `Type` will also be destructed.
+  /// Types are unique (de-aliased), and so calling create() for the same `T`
+  /// and arguments will return the same pointer.
+  /// @warning Use this method to acquire a type only if all of its type
+  /// information is provided in the constructor arguments `args`.<br>
+  /// If the type requires additional configuration after construction that
+  /// affect its fundamental type, build the type with `std::make_unique`, make
+  /// any necessary alterations and then call unique_type() instead.
+  /// @param args the arguments to pass to the type constructor
+  /// @returns the de-aliased type pointer
+  template <typename T, typename... ARGS>
+  traits::EnableIfIsType<T, type::Type>* create(ARGS&&... args) {
+    static_assert(std::is_base_of<type::Type, T>::value,
+                  "T does not derive from type::Type");
+    AssertNotMoved();
+    return types_.Get<T>(std::forward<ARGS>(args)...);
+  }
+
+  /// Marks this builder as moved, preventing any further use of the builder.
+  void MarkAsMoved();
+
+  //////////////////////////////////////////////////////////////////////////////
+  // TypesBuilder
+  //////////////////////////////////////////////////////////////////////////////
+
+  /// TypesBuilder holds basic `tint` types and methods for constructing
+  /// complex types.
+  class TypesBuilder {
+   public:
+    /// Constructor
+    /// @param builder the program builder
+    explicit TypesBuilder(ProgramBuilder* builder);
+
+    /// @return the tint AST type for the C type `T`.
+    template <typename T>
+    type::Type* Of() const {
+      return CToAST<T>::get(this);
+    }
+
+    /// @returns a boolean type
+    type::Bool* bool_() const { return builder->create<type::Bool>(); }
+
+    /// @returns a f32 type
+    type::F32* f32() const { return builder->create<type::F32>(); }
+
+    /// @returns a i32 type
+    type::I32* i32() const { return builder->create<type::I32>(); }
+
+    /// @returns a u32 type
+    type::U32* u32() const { return builder->create<type::U32>(); }
+
+    /// @returns a void type
+    type::Void* void_() const { return builder->create<type::Void>(); }
+
+    /// @return the tint AST type for a 2-element vector of the C type `T`.
+    template <typename T>
+    type::Vector* vec2() const {
+      return builder->create<type::Vector>(Of<T>(), 2);
+    }
+
+    /// @return the tint AST type for a 3-element vector of the C type `T`.
+    template <typename T>
+    type::Vector* vec3() const {
+      return builder->create<type::Vector>(Of<T>(), 3);
+    }
+
+    /// @return the tint AST type for a 4-element vector of the C type `T`.
+    template <typename T>
+    type::Type* vec4() const {
+      return builder->create<type::Vector>(Of<T>(), 4);
+    }
+
+    /// @return the tint AST type for a 2x3 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat2x2() const {
+      return builder->create<type::Matrix>(Of<T>(), 2, 2);
+    }
+
+    /// @return the tint AST type for a 2x3 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat2x3() const {
+      return builder->create<type::Matrix>(Of<T>(), 3, 2);
+    }
+
+    /// @return the tint AST type for a 2x4 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat2x4() const {
+      return builder->create<type::Matrix>(Of<T>(), 4, 2);
+    }
+
+    /// @return the tint AST type for a 3x2 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat3x2() const {
+      return builder->create<type::Matrix>(Of<T>(), 2, 3);
+    }
+
+    /// @return the tint AST type for a 3x3 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat3x3() const {
+      return builder->create<type::Matrix>(Of<T>(), 3, 3);
+    }
+
+    /// @return the tint AST type for a 3x4 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat3x4() const {
+      return builder->create<type::Matrix>(Of<T>(), 4, 3);
+    }
+
+    /// @return the tint AST type for a 4x2 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat4x2() const {
+      return builder->create<type::Matrix>(Of<T>(), 2, 4);
+    }
+
+    /// @return the tint AST type for a 4x3 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat4x3() const {
+      return builder->create<type::Matrix>(Of<T>(), 3, 4);
+    }
+
+    /// @return the tint AST type for a 4x4 matrix of the C type `T`.
+    template <typename T>
+    type::Matrix* mat4x4() const {
+      return builder->create<type::Matrix>(Of<T>(), 4, 4);
+    }
+
+    /// @param subtype the array element type
+    /// @param n the array size. 0 represents a runtime-array.
+    /// @return the tint AST type for a array of size `n` of type `T`
+    type::Array* array(type::Type* subtype, uint32_t n) const {
+      return builder->create<type::Array>(subtype, n,
+                                          ast::ArrayDecorationList{});
+    }
+
+    /// @return the tint AST type for an array of size `N` of type `T`
+    template <typename T, int N = 0>
+    type::Array* array() const {
+      return array(Of<T>(), N);
+    }
+
+    /// creates an alias type
+    /// @param name the alias name
+    /// @param type the alias type
+    /// @returns the alias pointer
+    type::Alias* alias(const std::string& name, type::Type* type) const {
+      return builder->create<type::Alias>(builder->Symbols().Register(name),
+                                          type);
+    }
+
+    /// @return the tint AST pointer to type `T` with the given
+    /// ast::StorageClass.
+    /// @param storage_class the storage class of the pointer
+    template <typename T>
+    type::Pointer* pointer(ast::StorageClass storage_class) const {
+      return builder->create<type::Pointer>(Of<T>(), storage_class);
+    }
+
+    /// @param name the struct name
+    /// @param impl the struct implementation
+    /// @returns a struct pointer
+    type::Struct* struct_(const std::string& name, ast::Struct* impl) const {
+      return builder->create<type::Struct>(builder->Symbols().Register(name),
+                                           impl);
+    }
+
+   private:
+    /// CToAST<T> is specialized for various `T` types and each specialization
+    /// contains a single static `get()` method for obtaining the corresponding
+    /// AST type for the C type `T`.
+    /// `get()` has the signature:
+    ///    `static type::Type* get(Types* t)`
+    template <typename T>
+    struct CToAST {};
+
+    ProgramBuilder* builder;
+  };
+
+  //////////////////////////////////////////////////////////////////////////////
+  // AST helper methods
+  //////////////////////////////////////////////////////////////////////////////
+
+  /// @param expr the expression
+  /// @return expr
+  ast::Expression* Expr(ast::Expression* expr) { return expr; }
+
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  ast::IdentifierExpression* Expr(const std::string& name) {
+    return create<ast::IdentifierExpression>(Symbols().Register(name));
+  }
+
+  /// @param source the source information
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  ast::IdentifierExpression* Expr(const Source& source,
+                                  const std::string& name) {
+    return create<ast::IdentifierExpression>(source, Symbols().Register(name));
+  }
+
+  /// @param name the identifier name
+  /// @return an ast::IdentifierExpression with the given name
+  ast::IdentifierExpression* Expr(const char* name) {
+    return create<ast::IdentifierExpression>(Symbols().Register(name));
+  }
+
+  /// @param value the boolean value
+  /// @return a Scalar constructor for the given value
+  ast::ScalarConstructorExpression* Expr(bool value) {
+    return create<ast::ScalarConstructorExpression>(Literal(value));
+  }
+
+  /// @param value the float value
+  /// @return a Scalar constructor for the given value
+  ast::ScalarConstructorExpression* Expr(f32 value) {
+    return create<ast::ScalarConstructorExpression>(Literal(value));
+  }
+
+  /// @param value the integer value
+  /// @return a Scalar constructor for the given value
+  ast::ScalarConstructorExpression* Expr(i32 value) {
+    return create<ast::ScalarConstructorExpression>(Literal(value));
+  }
+
+  /// @param value the unsigned int value
+  /// @return a Scalar constructor for the given value
+  ast::ScalarConstructorExpression* Expr(u32 value) {
+    return create<ast::ScalarConstructorExpression>(Literal(value));
+  }
+
+  /// Converts `arg` to an `ast::Expression` using `Expr()`, then appends it to
+  /// `list`.
+  /// @param list the list to append too
+  /// @param arg the arg to create
+  template <typename ARG>
+  void Append(ast::ExpressionList& list, ARG&& arg) {
+    list.emplace_back(Expr(std::forward<ARG>(arg)));
+  }
+
+  /// Converts `arg0` and `args` to `ast::Expression`s using `Expr()`,
+  /// then appends them to `list`.
+  /// @param list the list to append too
+  /// @param arg0 the first argument
+  /// @param args the rest of the arguments
+  template <typename ARG0, typename... ARGS>
+  void Append(ast::ExpressionList& list, ARG0&& arg0, ARGS&&... args) {
+    Append(list, std::forward<ARG0>(arg0));
+    Append(list, std::forward<ARGS>(args)...);
+  }
+
+  /// @return an empty list of expressions
+  ast::ExpressionList ExprList() { return {}; }
+
+  /// @param args the list of expressions
+  /// @return the list of expressions converted to `ast::Expression`s using
+  /// `Expr()`,
+  template <typename... ARGS>
+  ast::ExpressionList ExprList(ARGS&&... args) {
+    ast::ExpressionList list;
+    list.reserve(sizeof...(args));
+    Append(list, std::forward<ARGS>(args)...);
+    return list;
+  }
+
+  /// @param list the list of expressions
+  /// @return `list`
+  ast::ExpressionList ExprList(ast::ExpressionList list) { return list; }
+
+  /// @param val the boolan value
+  /// @return a boolean literal with the given value
+  ast::BoolLiteral* Literal(bool val) {
+    return create<ast::BoolLiteral>(ty.bool_(), val);
+  }
+
+  /// @param val the float value
+  /// @return a float literal with the given value
+  ast::FloatLiteral* Literal(f32 val) {
+    return create<ast::FloatLiteral>(ty.f32(), val);
+  }
+
+  /// @param val the unsigned int value
+  /// @return a ast::UintLiteral with the given value
+  ast::UintLiteral* Literal(u32 val) {
+    return create<ast::UintLiteral>(ty.u32(), val);
+  }
+
+  /// @param val the integer value
+  /// @return the ast::SintLiteral with the given value
+  ast::SintLiteral* Literal(i32 val) {
+    return create<ast::SintLiteral>(ty.i32(), val);
+  }
+
+  /// @param args the arguments for the type constructor
+  /// @return an `ast::TypeConstructorExpression` of type `ty`, with the values
+  /// of `args` converted to `ast::Expression`s using `Expr()`
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* Construct(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.Of<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param type the type to construct
+  /// @param args the arguments for the constructor
+  /// @return an `ast::TypeConstructorExpression` of `type` constructed with the
+  /// values `args`.
+  template <typename... ARGS>
+  ast::TypeConstructorExpression* Construct(type::Type* type, ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        type, ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @return an `ast::TypeConstructorExpression` of a 2-element vector of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* vec2(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.vec2<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @return an `ast::TypeConstructorExpression` of a 3-element vector of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* vec3(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.vec3<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the vector constructor
+  /// @return an `ast::TypeConstructorExpression` of a 4-element vector of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* vec4(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.vec4<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 2x2 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat2x2(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat2x2<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 2x3 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat2x3(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat2x3<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 2x4 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat2x4(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat2x4<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 3x2 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat3x2(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat3x2<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 3x3 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat3x3(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat3x3<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 3x4 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat3x4(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat3x4<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 4x2 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat4x2(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat4x2<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 4x3 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat4x3(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat4x3<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the matrix constructor
+  /// @return an `ast::TypeConstructorExpression` of a 4x4 matrix of type
+  /// `T`, constructed with the values `args`.
+  template <typename T, typename... ARGS>
+  ast::TypeConstructorExpression* mat4x4(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.mat4x4<T>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param args the arguments for the array constructor
+  /// @return an `ast::TypeConstructorExpression` of an array with element type
+  /// `T`, constructed with the values `args`.
+  template <typename T, int N = 0, typename... ARGS>
+  ast::TypeConstructorExpression* array(ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.array<T, N>(), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param subtype the array element type
+  /// @param n the array size. 0 represents a runtime-array.
+  /// @param args the arguments for the array constructor
+  /// @return an `ast::TypeConstructorExpression` of an array with element type
+  /// `subtype`, constructed with the values `args`.
+  template <typename... ARGS>
+  ast::TypeConstructorExpression* array(type::Type* subtype,
+                                        uint32_t n,
+                                        ARGS&&... args) {
+    return create<ast::TypeConstructorExpression>(
+        ty.array(subtype, n), ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param name the variable name
+  /// @param storage the variable storage class
+  /// @param type the variable type
+  /// @returns a `ast::Variable` with the given name, storage and type. The
+  /// variable will be built with a nullptr constructor and no decorations.
+  ast::Variable* Var(const std::string& name,
+                     ast::StorageClass storage,
+                     type::Type* type);
+
+  /// @param name the variable name
+  /// @param storage the variable storage class
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param decorations variable decorations
+  /// @returns a `ast::Variable` with the given name, storage and type
+  ast::Variable* Var(const std::string& name,
+                     ast::StorageClass storage,
+                     type::Type* type,
+                     ast::Expression* constructor,
+                     ast::VariableDecorationList decorations);
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param storage the variable storage class
+  /// @param type the variable type
+  /// @param constructor constructor expression
+  /// @param decorations variable decorations
+  /// @returns a `ast::Variable` with the given name, storage and type
+  ast::Variable* Var(const Source& source,
+                     const std::string& name,
+                     ast::StorageClass storage,
+                     type::Type* type,
+                     ast::Expression* constructor,
+                     ast::VariableDecorationList decorations);
+
+  /// @param name the variable name
+  /// @param storage the variable storage class
+  /// @param type the variable type
+  /// @returns a constant `ast::Variable` with the given name, storage and type.
+  /// The variable will be built with a nullptr constructor and no decorations.
+  ast::Variable* Const(const std::string& name,
+                       ast::StorageClass storage,
+                       type::Type* type);
+
+  /// @param name the variable name
+  /// @param storage the variable storage class
+  /// @param type the variable type
+  /// @param constructor optional constructor expression
+  /// @param decorations optional variable decorations
+  /// @returns a constant `ast::Variable` with the given name, storage and type
+  ast::Variable* Const(const std::string& name,
+                       ast::StorageClass storage,
+                       type::Type* type,
+                       ast::Expression* constructor,
+                       ast::VariableDecorationList decorations);
+
+  /// @param source the variable source
+  /// @param name the variable name
+  /// @param storage the variable storage class
+  /// @param type the variable type
+  /// @param constructor optional constructor expression
+  /// @param decorations optional variable decorations
+  /// @returns a constant `ast::Variable` with the given name, storage and type
+  ast::Variable* Const(const Source& source,
+                       const std::string& name,
+                       ast::StorageClass storage,
+                       type::Type* type,
+                       ast::Expression* constructor,
+                       ast::VariableDecorationList decorations);
+
+  /// @param func the function name
+  /// @param args the function call arguments
+  /// @returns a `ast::CallExpression` to the function `func`, with the
+  /// arguments of `args` converted to `ast::Expression`s using `Expr()`.
+  template <typename NAME, typename... ARGS>
+  ast::CallExpression* Call(NAME&& func, ARGS&&... args) {
+    return create<ast::CallExpression>(Expr(func),
+                                       ExprList(std::forward<ARGS>(args)...));
+  }
+
+  /// @param lhs the left hand argument to the addition operation
+  /// @param rhs the right hand argument to the addition operation
+  /// @returns a `ast::BinaryExpression` summing the arguments `lhs` and `rhs`
+  template <typename LHS, typename RHS>
+  ast::Expression* Add(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kAdd,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the subtraction operation
+  /// @param rhs the right hand argument to the subtraction operation
+  /// @returns a `ast::BinaryExpression` subtracting `rhs` from `lhs`
+  template <typename LHS, typename RHS>
+  ast::Expression* Sub(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kSubtract,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param lhs the left hand argument to the multiplication operation
+  /// @param rhs the right hand argument to the multiplication operation
+  /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs`
+  template <typename LHS, typename RHS>
+  ast::Expression* Mul(LHS&& lhs, RHS&& rhs) {
+    return create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
+                                         Expr(std::forward<LHS>(lhs)),
+                                         Expr(std::forward<RHS>(rhs)));
+  }
+
+  /// @param arr the array argument for the array accessor expression
+  /// @param idx the index argument for the array accessor expression
+  /// @returns a `ast::ArrayAccessorExpression` that indexes `arr` with `idx`
+  template <typename ARR, typename IDX>
+  ast::Expression* IndexAccessor(ARR&& arr, IDX&& idx) {
+    return create<ast::ArrayAccessorExpression>(Expr(std::forward<ARR>(arr)),
+                                                Expr(std::forward<IDX>(idx)));
+  }
+
+  /// @param obj the object for the member accessor expression
+  /// @param idx the index argument for the array accessor expression
+  /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx`
+  template <typename OBJ, typename IDX>
+  ast::Expression* MemberAccessor(OBJ&& obj, IDX&& idx) {
+    return create<ast::MemberAccessorExpression>(Expr(std::forward<OBJ>(obj)),
+                                                 Expr(std::forward<IDX>(idx)));
+  }
+
+  /// creates a ast::StructMemberOffsetDecoration
+  /// @param val the offset value
+  /// @returns the offset decoration pointer
+  ast::StructMemberOffsetDecoration* MemberOffset(uint32_t val) {
+    return create<ast::StructMemberOffsetDecoration>(source_, val);
+  }
+
+  /// creates a ast::Function
+  /// @param source the source information
+  /// @param name the function name
+  /// @param params the function parameters
+  /// @param type the function return type
+  /// @param body the function body
+  /// @param decorations the function decorations
+  /// @returns the function pointer
+  ast::Function* Func(Source source,
+                      std::string name,
+                      ast::VariableList params,
+                      type::Type* type,
+                      ast::StatementList body,
+                      ast::FunctionDecorationList decorations) {
+    return create<ast::Function>(source, Symbols().Register(name), params, type,
+                                 create<ast::BlockStatement>(body),
+                                 decorations);
+  }
+
+  /// creates a ast::Function
+  /// @param name the function name
+  /// @param params the function parameters
+  /// @param type the function return type
+  /// @param body the function body
+  /// @param decorations the function decorations
+  /// @returns the function pointer
+  ast::Function* Func(std::string name,
+                      ast::VariableList params,
+                      type::Type* type,
+                      ast::StatementList body,
+                      ast::FunctionDecorationList decorations) {
+    return create<ast::Function>(Symbols().Register(name), params, type,
+                                 create<ast::BlockStatement>(body),
+                                 decorations);
+  }
+
+  /// creates a ast::StructMember
+  /// @param source the source information
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @returns the struct member pointer
+  ast::StructMember* Member(const Source& source,
+                            const std::string& name,
+                            type::Type* type) {
+    return create<ast::StructMember>(source, Symbols().Register(name), type,
+                                     ast::StructMemberDecorationList{});
+  }
+
+  /// creates a ast::StructMember
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @returns the struct member pointer
+  ast::StructMember* Member(const std::string& name, type::Type* type) {
+    return create<ast::StructMember>(source_, Symbols().Register(name), type,
+                                     ast::StructMemberDecorationList{});
+  }
+
+  /// creates a ast::StructMember
+  /// @param name the struct member name
+  /// @param type the struct member type
+  /// @param decorations the struct member decorations
+  /// @returns the struct member pointer
+  ast::StructMember* Member(const std::string& name,
+                            type::Type* type,
+                            ast::StructMemberDecorationList decorations) {
+    return create<ast::StructMember>(source_, Symbols().Register(name), type,
+                                     decorations);
+  }
+
+  /// Sets the current builder source to `src`
+  /// @param src the Source used for future create() calls
+  void SetSource(const Source& src) {
+    AssertNotMoved();
+    source_ = src;
+  }
+
+  /// Sets the current builder source to `loc`
+  /// @param loc the Source used for future create() calls
+  void SetSource(const Source::Location& loc) {
+    AssertNotMoved();
+    source_ = Source(loc);
+  }
+
+  /// The builder types
+  TypesBuilder ty;
+
+ protected:
+  /// Asserts that the builder has not been moved.
+  void AssertNotMoved() const;
+
+  /// Called whenever a new variable is built with `Var()`.
+  virtual void OnVariableBuilt(ast::Variable*) {}
+
+ private:
+  type::Manager types_;
+  ASTNodes nodes_;
+  ast::Module* ast_;
+  SymbolTable symbols_;
+
+  /// The source to use when creating AST nodes without providing a Source as
+  /// the first argument.
+  Source source_;
+
+  /// Set by MarkAsMoved(). Once set, no methods may be called on this builder.
+  bool moved_ = false;
+};
+
+//! @cond Doxygen_Suppress
+// Various template specializations for ProgramBuilder::TypesBuilder::CToAST.
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::i32> {
+  static type::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->i32();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::u32> {
+  static type::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->u32();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::f32> {
+  static type::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->f32();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<bool> {
+  static type::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->bool_();
+  }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<void> {
+  static type::Type* get(const ProgramBuilder::TypesBuilder* t) {
+    return t->void_();
+  }
+};
+//! @endcond
+
+}  // namespace tint
+
+#endif  // SRC_PROGRAM_BUILDER_H_
diff --git a/src/program_test.cc b/src/program_test.cc
index 6659ae3..a352b0b 100644
--- a/src/program_test.cc
+++ b/src/program_test.cc
@@ -31,80 +31,101 @@
 using ProgramTest = ast::TestHelper;
 
 TEST_F(ProgramTest, Creation) {
-  EXPECT_EQ(AST().Functions().size(), 0u);
+  Program program(std::move(*this));
+  EXPECT_EQ(program.AST().Functions().size(), 0u);
 }
 
 TEST_F(ProgramTest, ToStrEmitsPreambleAndPostamble) {
-  const auto str = mod->to_str();
+  Program program(std::move(*this));
+  const auto str = program.to_str();
   auto* const expected = "Module{\n}\n";
   EXPECT_EQ(str, expected);
 }
 
 TEST_F(ProgramTest, IsValid_Empty) {
-  EXPECT_TRUE(IsValid());
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_GlobalVariable) {
   auto* var = Var("var", ast::StorageClass::kInput, ty.f32());
   AST().AddGlobalVariable(var);
-  EXPECT_TRUE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Null_GlobalVariable) {
   AST().AddGlobalVariable(nullptr);
-  EXPECT_FALSE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Invalid_GlobalVariable) {
   auto* var = Var("var", ast::StorageClass::kInput, nullptr);
   AST().AddGlobalVariable(var);
-  EXPECT_FALSE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Alias) {
   auto* alias = ty.alias("alias", ty.f32());
   AST().AddConstructedType(alias);
-  EXPECT_TRUE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Null_Alias) {
   AST().AddConstructedType(nullptr);
-  EXPECT_FALSE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Struct) {
   auto* st = ty.struct_("name", {});
   auto* alias = ty.alias("name", st);
   AST().AddConstructedType(alias);
-  EXPECT_TRUE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Struct_EmptyName) {
   auto* st = ty.struct_("", {});
   auto* alias = ty.alias("name", st);
   AST().AddConstructedType(alias);
-  EXPECT_FALSE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Function) {
   auto* func = Func("main", ast::VariableList(), ty.f32(), ast::StatementList{},
                     ast::FunctionDecorationList{});
-
   AST().Functions().Add(func);
-  EXPECT_TRUE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_TRUE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Null_Function) {
   AST().Functions().Add(nullptr);
-  EXPECT_FALSE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.IsValid());
 }
 
 TEST_F(ProgramTest, IsValid_Invalid_Function) {
   auto* func = Func("main", ast::VariableList{}, nullptr, ast::StatementList{},
                     ast::FunctionDecorationList{});
-
   AST().Functions().Add(func);
-  EXPECT_FALSE(IsValid());
+
+  Program program(std::move(*this));
+  EXPECT_FALSE(program.IsValid());
 }
 
 }  // namespace
diff --git a/src/reader/reader.h b/src/reader/reader.h
index 008d780..402a2c5 100644
--- a/src/reader/reader.h
+++ b/src/reader/reader.h
@@ -46,7 +46,8 @@
   /// @returns the full list of diagnostic messages.
   const diag::List& diagnostics() const { return diags_; }
 
-  /// @returns the program. The program in the parser will be reset after this.
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
   virtual Program program() = 0;
 
  protected:
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 419a87e..6ca91fa 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -632,15 +632,15 @@
   /// @param cond the switch statement condition
   explicit SwitchStatementBuilder(ast::Expression* cond) : condition(cond) {}
 
-  /// @param program the program to build into
+  /// @param builder the program builder
   /// @returns the built ast::SwitchStatement
-  ast::SwitchStatement* Build(Program* program) const override {
+  ast::SwitchStatement* Build(ProgramBuilder* builder) const override {
     // We've listed cases in reverse order in the switch statement.
     // Reorder them to match the presentation order in WGSL.
     auto reversed_cases = cases;
     std::reverse(reversed_cases.begin(), reversed_cases.end());
 
-    return program->create<ast::SwitchStatement>(Source{}, condition,
+    return builder->create<ast::SwitchStatement>(Source{}, condition,
                                                  reversed_cases);
   }
 
@@ -658,10 +658,10 @@
   /// @param c the if-statement condition
   explicit IfStatementBuilder(ast::Expression* c) : cond(c) {}
 
-  /// @param program the program to build into
+  /// @param builder the program builder
   /// @returns the built ast::IfStatement
-  ast::IfStatement* Build(Program* program) const override {
-    return program->create<ast::IfStatement>(Source{}, cond, body, else_stmts);
+  ast::IfStatement* Build(ProgramBuilder* builder) const override {
+    return builder->create<ast::IfStatement>(Source{}, cond, body, else_stmts);
   }
 
   /// If-statement condition
@@ -676,10 +676,10 @@
 /// @see StatementBuilder
 struct LoopStatementBuilder
     : public Castable<LoopStatementBuilder, StatementBuilder> {
-  /// @param program the program to build into
+  /// @param builder the program builder
   /// @returns the built ast::LoopStatement
-  ast::LoopStatement* Build(Program* program) const override {
-    return program->create<ast::LoopStatement>(Source{}, body, continuing);
+  ast::LoopStatement* Build(ProgramBuilder* builder) const override {
+    return builder->create<ast::LoopStatement>(Source{}, body, continuing);
   }
 
   /// Loop-statement block body
@@ -717,7 +717,7 @@
                                  const spvtools::opt::Function& function,
                                  const EntryPointInfo* ep_info)
     : parser_impl_(*pi),
-      program_(pi->get_program()),
+      builder_(pi->builder()),
       ir_context_(*(pi->ir_context())),
       def_use_mgr_(ir_context_.get_def_use_mgr()),
       constant_mgr_(ir_context_.get_constant_mgr()),
@@ -725,7 +725,7 @@
       fail_stream_(pi->fail_stream()),
       namer_(pi->namer()),
       function_(function),
-      i32_(program_.create<type::I32>()),
+      i32_(builder_.create<type::I32>()),
       ep_info_(ep_info) {
   PushNewStatementBlock(nullptr, 0, nullptr);
 }
@@ -749,12 +749,12 @@
 
 FunctionEmitter::StatementBlock::~StatementBlock() = default;
 
-void FunctionEmitter::StatementBlock::Finalize(Program* program) {
+void FunctionEmitter::StatementBlock::Finalize(ProgramBuilder* pb) {
   assert(!finalized_ /* Finalize() must only be called once */);
 
   for (size_t i = 0; i < statements_.size(); i++) {
-    if (auto* builder = statements_[i]->As<StatementBuilder>()) {
-      statements_[i] = builder->Build(program);
+    if (auto* sb = statements_[i]->As<StatementBuilder>()) {
+      statements_[i] = sb->Build(pb);
     }
   }
 
@@ -786,7 +786,7 @@
   const auto& top = statements_stack_.back();
 
   auto* cond = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(guard_name));
+      Source{}, builder_.Symbols().Register(guard_name));
   auto* builder = AddStatementBuilder<IfStatementBuilder>(cond);
 
   PushNewStatementBlock(
@@ -811,7 +811,7 @@
 const ast::StatementList FunctionEmitter::ast_body() {
   assert(!statements_stack_.empty());
   auto& entry = statements_stack_[0];
-  entry.Finalize(&program_);
+  entry.Finalize(&builder_);
   return entry.GetStatements();
 }
 
@@ -855,12 +855,12 @@
                   << statements_stack_.size();
   }
 
-  statements_stack_[0].Finalize(&program_);
+  statements_stack_[0].Finalize(&builder_);
 
   auto& statements = statements_stack_[0].GetStatements();
   auto* body = create<ast::BlockStatement>(Source{}, statements);
-  program_.AST().Functions().Add(
-      create<ast::Function>(decl.source, program_.Symbols().Register(decl.name),
+  builder_.AST().Functions().Add(
+      create<ast::Function>(decl.source, builder_.Symbols().Register(decl.name),
                             std::move(decl.params), decl.return_type, body,
                             std::move(decl.decorations)));
 
@@ -2013,7 +2013,7 @@
     return TypedExpression{
         parser_impl_.ConvertType(def_use_mgr_->GetDef(id)->type_id()),
         create<ast::IdentifierExpression>(Source{},
-                                          program_.Symbols().Register(name))};
+                                          builder_.Symbols().Register(name))};
   }
   if (singly_used_values_.count(id)) {
     auto expr = std::move(singly_used_values_[id]);
@@ -2035,7 +2035,7 @@
       auto name = namer_.Name(inst->result_id());
       return TypedExpression{parser_impl_.ConvertType(inst->type_id()),
                              create<ast::IdentifierExpression>(
-                                 Source{}, program_.Symbols().Register(name))};
+                                 Source{}, builder_.Symbols().Register(name))};
     }
     default:
       break;
@@ -2079,7 +2079,7 @@
   // Close off previous constructs.
   while (!statements_stack_.empty() &&
          (statements_stack_.back().GetEndId() == block_info.id)) {
-    statements_stack_.back().Finalize(&program_);
+    statements_stack_.back().Finalize(&builder_);
     statements_stack_.pop_back();
   }
   if (statements_stack_.empty()) {
@@ -2266,7 +2266,7 @@
     // Declare the guard variable just before the "if", initialized to true.
     auto* guard_var = create<ast::Variable>(
         Source{},                                 // source
-        program_.Symbols().Register(guard_name),  // symbol
+        builder_.Symbols().Register(guard_name),  // symbol
         ast::StorageClass::kFunction,             // storage_class
         parser_impl_.Bool(),                      // type
         false,                                    // is_const
@@ -2673,7 +2673,7 @@
         return create<ast::AssignmentStatement>(
             Source{},
             create<ast::IdentifierExpression>(
-                Source{}, program_.Symbols().Register(flow_guard)),
+                Source{}, builder_.Symbols().Register(flow_guard)),
             MakeFalse(Source{}));
       }
 
@@ -2794,7 +2794,7 @@
     assert(!phi_var_name.empty());
     auto* var = create<ast::Variable>(
         Source{},                                       // source
-        program_.Symbols().Register(phi_var_name),      // symbol
+        builder_.Symbols().Register(phi_var_name),      // symbol
         ast::StorageClass::kFunction,                   // storage_class
         parser_impl_.ConvertType(def_inst->type_id()),  // type
         false,                                          // is_const
@@ -2833,7 +2833,7 @@
       AddStatement(create<ast::AssignmentStatement>(
           Source{},
           create<ast::IdentifierExpression>(
-              Source{}, program_.Symbols().Register(var_name)),
+              Source{}, builder_.Symbols().Register(var_name)),
           expr.expr));
     }
   }
@@ -2871,7 +2871,7 @@
     AddStatement(create<ast::AssignmentStatement>(
         Source{},
         create<ast::IdentifierExpression>(Source{},
-                                          program_.Symbols().Register(name)),
+                                          builder_.Symbols().Register(name)),
         ast_expr.expr));
     return true;
   }
@@ -3010,7 +3010,7 @@
       TypedExpression expr{
           parser_impl_.ConvertType(inst.type_id()),
           create<ast::IdentifierExpression>(
-              Source{}, program_.Symbols().Register(def_info->phi_var))};
+              Source{}, builder_.Symbols().Register(def_info->phi_var))};
       return EmitConstDefOrWriteToHoistedVar(inst, expr);
     }
 
@@ -3073,7 +3073,7 @@
             create<ast::CallExpression>(
                 Source{},
                 create<ast::IdentifierExpression>(
-                    Source{}, program_.Symbols().Register(unary_builtin_name)),
+                    Source{}, builder_.Symbols().Register(unary_builtin_name)),
                 std::move(params))};
   }
 
@@ -3179,7 +3179,7 @@
   }
 
   auto* func = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(name));
+      Source{}, builder_.Symbols().Register(name));
   ast::ExpressionList operands;
   type::Type* first_operand_type = nullptr;
   // All parameters to GLSL.std.450 extended instructions are IDs.
@@ -3205,20 +3205,20 @@
   }
   const char* names[] = {"x", "y", "z", "w"};
   return create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(names[i & 3]));
+      Source{}, builder_.Symbols().Register(names[i & 3]));
 }
 
 ast::IdentifierExpression* FunctionEmitter::PrefixSwizzle(uint32_t n) {
   switch (n) {
     case 1:
       return create<ast::IdentifierExpression>(
-          Source{}, program_.Symbols().Register("x"));
+          Source{}, builder_.Symbols().Register("x"));
     case 2:
       return create<ast::IdentifierExpression>(
-          Source{}, program_.Symbols().Register("xy"));
+          Source{}, builder_.Symbols().Register("xy"));
     case 3:
       return create<ast::IdentifierExpression>(
-          Source{}, program_.Symbols().Register("xyz"));
+          Source{}, builder_.Symbols().Register("xyz"));
     default:
       break;
   }
@@ -3313,7 +3313,7 @@
 
       auto name = namer_.Name(base_id);
       current_expr.expr = create<ast::IdentifierExpression>(
-          Source{}, program_.Symbols().Register(name));
+          Source{}, builder_.Symbols().Register(name));
       current_expr.type = parser_impl_.ConvertType(ptr_ty_id);
     }
   }
@@ -3406,7 +3406,7 @@
         auto name =
             namer_.GetMemberName(pointee_type_id, uint32_t(index_const_val));
         auto* member_access = create<ast::IdentifierExpression>(
-            Source{}, program_.Symbols().Register(name));
+            Source{}, builder_.Symbols().Register(name));
 
         next_expr = create<ast::MemberAccessorExpression>(
             Source{}, current_expr.expr, member_access);
@@ -3527,7 +3527,7 @@
         }
         auto name = namer_.GetMemberName(current_type_id, uint32_t(index_val));
         auto* member_access = create<ast::IdentifierExpression>(
-            Source{}, program_.Symbols().Register(name));
+            Source{}, builder_.Symbols().Register(name));
 
         next_expr = create<ast::MemberAccessorExpression>(
             Source{}, current_expr.expr, member_access);
@@ -3700,8 +3700,7 @@
     // buffer pointer.
     const auto sc = GetStorageClassForPointerValue(result_id);
     if (ast_ptr_type->storage_class() != sc) {
-      return parser_impl_.get_program().create<type::Pointer>(
-          ast_ptr_type->type(), sc);
+      return builder_.create<type::Pointer>(ast_ptr_type->type(), sc);
     }
   }
   return type;
@@ -3932,7 +3931,7 @@
   // We ignore function attributes such as Inline, DontInline, Pure, Const.
   auto name = namer_.Name(inst.GetSingleWordInOperand(0));
   auto* function = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(name));
+      Source{}, builder_.Symbols().Register(name));
 
   ast::ExpressionList params;
   for (uint32_t iarg = 1; iarg < inst.NumInOperands(); ++iarg) {
@@ -3961,7 +3960,7 @@
   ss << intrinsic;
   auto name = ss.str();
   auto* ident = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(name));
+      Source{}, builder_.Symbols().Register(name));
   ident->set_intrinsic(intrinsic);
 
   ast::ExpressionList params;
@@ -4008,7 +4007,7 @@
             create<ast::CallExpression>(
                 Source{},
                 create<ast::IdentifierExpression>(
-                    Source{}, program_.Symbols().Register("select")),
+                    Source{}, builder_.Symbols().Register("select")),
                 std::move(params))};
   }
   return {};
@@ -4058,7 +4057,7 @@
   }
   auto name = namer_.Name(image->result_id());
   return create<ast::IdentifierExpression>(GetSourceForInst(inst),
-                                           program_.Symbols().Register(name));
+                                           builder_.Symbols().Register(name));
 }
 
 ast::Expression* FunctionEmitter::GetSamplerExpression(
@@ -4074,7 +4073,7 @@
   }
   auto name = namer_.Name(image->result_id());
   return create<ast::IdentifierExpression>(GetSourceForInst(inst),
-                                           program_.Symbols().Register(name));
+                                           builder_.Symbols().Register(name));
 }
 
 bool FunctionEmitter::EmitImageAccess(const spvtools::opt::Instruction& inst) {
@@ -4261,7 +4260,7 @@
   }
 
   auto* ident = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(builtin_name));
+      Source{}, builder_.Symbols().Register(builtin_name));
   auto* call_expr =
       create<ast::CallExpression>(Source{}, ident, std::move(params));
 
@@ -4569,14 +4568,14 @@
   }
 
   auto* member_ident = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(field_name));
+      Source{}, builder_.Symbols().Register(field_name));
   auto* member_access = create<ast::MemberAccessorExpression>(
       Source{}, MakeExpression(struct_ptr_id).expr, member_ident);
 
   // Generate the intrinsic function call.
   std::string call_ident_str = "arrayLength";
   auto* call_ident = create<ast::IdentifierExpression>(
-      Source{}, program_.Symbols().Register(call_ident_str));
+      Source{}, builder_.Symbols().Register(call_ident_str));
   call_ident->set_intrinsic(ast::Intrinsic::kArrayLength);
 
   ast::ExpressionList params{member_access};
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index a7a0ddd..acc01d9 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -33,9 +33,10 @@
 #include "src/ast/case_statement.h"
 #include "src/ast/expression.h"
 #include "src/ast/identifier_expression.h"
+#include "src/ast/module.h"
 #include "src/ast/statement.h"
 #include "src/ast/storage_class.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/reader/spirv/construct.h"
 #include "src/reader/spirv/entry_point_info.h"
 #include "src/reader/spirv/fail_stream.h"
@@ -345,9 +346,9 @@
   /// Constructor
   StatementBuilder() : Base(Source{}) {}
 
-  /// @param program the program to build into
-  /// @returns the built AST node
-  virtual ast::Statement* Build(Program* program) const = 0;
+  /// @param builder the program builder
+  /// @returns the build AST node
+  virtual ast::Statement* Build(ProgramBuilder* builder) const = 0;
 
  private:
   bool IsValid() const override;
@@ -355,15 +356,15 @@
   void to_str(std::ostream& out, size_t indent) const override;
 };
 
-/// A FunctionEmitter emits a SPIR-V function onto a Tint program.
+/// A FunctionEmitter emits a SPIR-V function onto a Tint AST module.
 class FunctionEmitter {
  public:
-  /// Creates a FunctionEmitter, and prepares to write to the program
+  /// Creates a FunctionEmitter, and prepares to write to the AST module
   /// in `pi`
   /// @param pi a ParserImpl which has already executed BuildInternalModule
   /// @param function the function to emit
   FunctionEmitter(ParserImpl* pi, const spvtools::opt::Function& function);
-  /// Creates a FunctionEmitter, and prepares to write to the program
+  /// Creates a FunctionEmitter, and prepares to write to the AST module
   /// in `pi`
   /// @param pi a ParserImpl which has already executed BuildInternalModule
   /// @param function the function to emit
@@ -374,7 +375,7 @@
   /// Destructor
   ~FunctionEmitter();
 
-  /// Emits the function to program.
+  /// Emits the function to AST module.
   /// @return whether emission succeeded
   bool Emit();
 
@@ -964,8 +965,8 @@
     /// Replaces any StatementBuilders with the built result, and calls the
     /// completion callback (if set). Must only be called once, after all
     /// statements have been added with Add().
-    /// @param program the program
-    void Finalize(Program* program);
+    /// @param builder the program builder
+    void Finalize(ProgramBuilder* builder);
 
     /// Add() adds `statement` to the block.
     /// Add() must not be called after calling Finalize().
@@ -1043,19 +1044,18 @@
   /// @returns a boolean false expression.
   ast::Expression* MakeFalse(const Source&) const;
 
-  /// Creates a new `ast::Node` owned by the Module. When the Module is
-  /// destructed, the `ast::Node` will also be destructed.
+  /// Creates a new `ast::Node` owned by the ProgramBuilder.
   /// @param args the arguments to pass to the type constructor
   /// @returns the node pointer
   template <typename T, typename... ARGS>
   T* create(ARGS&&... args) const {
-    return program_.create<T>(std::forward<ARGS>(args)...);
+    return builder_.create<T>(std::forward<ARGS>(args)...);
   }
 
   using StatementsStack = std::vector<StatementBlock>;
 
   ParserImpl& parser_impl_;
-  Program& program_;
+  ProgramBuilder& builder_;
   spvtools::opt::IRContext& ir_context_;
   spvtools::opt::analysis::DefUseManager* def_use_mgr_;
   spvtools::opt::analysis::ConstantManager* constant_mgr_;
diff --git a/src/reader/spirv/function_arithmetic_test.cc b/src/reader/spirv/function_arithmetic_test.cc
index e034ed0..dd17a2d 100644
--- a/src/reader/spirv/function_arithmetic_test.cc
+++ b/src/reader/spirv/function_arithmetic_test.cc
@@ -139,7 +139,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -151,7 +151,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_Int_Uint) {
@@ -166,7 +166,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -180,7 +180,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_Uint_Int) {
@@ -195,7 +195,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -209,7 +209,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_Uint_Uint) {
@@ -224,7 +224,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -240,7 +240,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_SignedVec_SignedVec) {
@@ -255,7 +255,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -271,7 +271,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_SignedVec_UnsignedVec) {
@@ -286,7 +286,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -304,7 +304,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_SignedVec) {
@@ -319,7 +319,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -337,7 +337,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_UnsignedVec) {
@@ -352,7 +352,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -372,7 +372,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, FNegate_Scalar) {
@@ -387,7 +387,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -399,7 +399,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryArithTest, FNegate_Vector) {
@@ -414,7 +414,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -430,7 +430,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 struct BinaryData {
@@ -478,7 +478,8 @@
      << GetParam().ast_type << "\n    {\n      Binary[not set]{"
      << "\n        " << GetParam().ast_lhs << "\n        " << GetParam().ast_op
      << "\n        " << GetParam().ast_rhs;
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(ss.str()))
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+              HasSubstr(ss.str()))
       << assembly;
 }
 
@@ -701,7 +702,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -736,7 +737,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -759,7 +760,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -847,7 +848,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -882,7 +883,7 @@
       << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -905,7 +906,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -935,7 +936,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -948,7 +949,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesScalar) {
@@ -965,7 +966,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -978,7 +979,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, VectorTimesMatrix) {
@@ -995,7 +996,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -1008,7 +1009,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesVector) {
@@ -1025,7 +1026,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -1038,7 +1039,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesMatrix) {
@@ -1055,7 +1056,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -1068,7 +1069,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, Dot) {
@@ -1085,7 +1086,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_3
     none
@@ -1100,7 +1101,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvBinaryArithTestBasic, OuterProduct) {
@@ -1119,7 +1120,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(got, HasSubstr(R"(VariableConst{
     x_3
     none
diff --git a/src/reader/spirv/function_bit_test.cc b/src/reader/spirv/function_bit_test.cc
index b54067d..57d400a 100644
--- a/src/reader/spirv/function_bit_test.cc
+++ b/src/reader/spirv/function_bit_test.cc
@@ -163,7 +163,8 @@
      << GetParam().ast_type << "\n    {\n      Binary[not set]{"
      << "\n        " << GetParam().ast_lhs << "\n        " << GetParam().ast_op
      << "\n        " << GetParam().ast_rhs;
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(ss.str()))
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+              HasSubstr(ss.str()))
       << assembly;
 }
 
@@ -401,7 +402,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -413,7 +414,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_Int_Uint) {
@@ -428,7 +429,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -442,7 +443,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_Uint_Int) {
@@ -457,7 +458,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -471,7 +472,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_Uint_Uint) {
@@ -486,7 +487,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -498,7 +499,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_SignedVec_SignedVec) {
@@ -513,7 +514,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -529,7 +530,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_SignedVec_UnsignedVec) {
@@ -544,7 +545,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -562,7 +563,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryBitTest, Not_UnsignedVec_SignedVec) {
@@ -577,7 +578,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -595,7 +596,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 TEST_F(SpvUnaryBitTest, Not_UnsignedVec_UnsignedVec) {
   const auto assembly = CommonTypes() + R"(
@@ -609,7 +610,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -625,7 +626,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 std::string BitTestPreamble() {
@@ -664,7 +665,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -692,7 +693,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -722,7 +723,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -752,7 +753,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -780,7 +781,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -808,7 +809,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -838,7 +839,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -868,7 +869,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -896,7 +897,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -924,7 +925,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -954,7 +955,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -984,7 +985,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1012,7 +1013,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1040,7 +1041,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1070,7 +1071,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1100,7 +1101,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body = ToString(p->get_program(), fe.ast_body());
+  const auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
diff --git a/src/reader/spirv/function_call_test.cc b/src/reader/spirv/function_call_test.cc
index c88e7cc..5b8ce72 100644
--- a/src/reader/spirv/function_call_test.cc
+++ b/src/reader/spirv/function_call_test.cc
@@ -46,8 +46,8 @@
      OpFunctionEnd
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  const auto program_ast_str = p->get_program().to_str();
-  EXPECT_THAT(program_ast_str, Eq(R"(Module{
+  const auto got = p->program().AST().to_str();
+  const char* expect = R"(Module{
   Function tint_symbol_1 -> __void
   ()
   {
@@ -64,7 +64,8 @@
     Return{}
   }
 }
-)")) << program_ast_str;
+)";
+  EXPECT_EQ(expect, got);
 }
 
 TEST_F(SpvParserTest, EmitStatement_ScalarCallNoParams) {
@@ -90,7 +91,7 @@
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
     EXPECT_TRUE(fe.EmitBody());
-    EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
                 HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -106,17 +107,18 @@
   }
 }
 Return{})"))
-        << ToString(p->get_program(), fe.ast_body());
+        << ToString(p->builder().Symbols(), fe.ast_body());
   }
 
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 50));
     EXPECT_TRUE(fe.EmitBody());
-    EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(Return{
+    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+                HasSubstr(R"(Return{
   {
     ScalarConstructor[not set]{42}
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
   }
 }
 
@@ -147,7 +149,7 @@
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
     EXPECT_TRUE(fe.EmitBody()) << p->error();
-    EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
                 HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_10
@@ -178,16 +180,17 @@
   Identifier[not set]{x_1}
 }
 Return{})"))
-        << ToString(p->get_program(), fe.ast_body());
+        << ToString(p->builder().Symbols(), fe.ast_body());
   }
   {
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 50));
     EXPECT_TRUE(fe.EmitBody()) << p->error();
-    EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(Return{
+    EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+                HasSubstr(R"(Return{
   {
     ScalarConstructor[not set]{42}
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
   }
 }
 
@@ -216,7 +219,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast_str = Demangler().Demangle(p->get_program());
+  const auto program_ast_str = Demangler().Demangle(p->program());
   EXPECT_THAT(program_ast_str, HasSubstr(R"(Module{
   Function x_50 -> __u32
   (
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index 693dbe6..57af7e9 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -7361,7 +7361,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -7473,7 +7473,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -7600,7 +7600,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error() << assembly;
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -7791,7 +7791,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -7827,7 +7827,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -7875,7 +7875,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -7931,7 +7931,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -7998,7 +7998,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8070,7 +8070,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8134,7 +8134,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8219,7 +8219,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8309,7 +8309,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8364,7 +8364,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8415,7 +8415,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8458,7 +8458,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8509,7 +8509,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8574,7 +8574,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8648,7 +8648,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8720,7 +8720,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -8778,7 +8778,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -8838,7 +8838,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -8905,7 +8905,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -8979,7 +8979,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   If{
     (
@@ -9037,7 +9037,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9086,7 +9086,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9142,7 +9142,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9204,7 +9204,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9266,7 +9266,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -9319,7 +9319,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9364,7 +9364,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9418,7 +9418,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9478,7 +9478,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9544,7 +9544,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9615,7 +9615,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9687,7 +9687,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9757,7 +9757,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -9809,7 +9809,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Return{}
 )";
   ASSERT_EQ(expect, got);
@@ -9835,7 +9835,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -9875,7 +9875,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Return{}
 }
@@ -9905,7 +9905,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Return{
   {
     ScalarConstructor[not set]{2}
@@ -9944,7 +9944,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10001,7 +10001,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Return{
     {
@@ -10031,7 +10031,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Discard{}
 )";
   ASSERT_EQ(expect, got);
@@ -10057,7 +10057,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10097,7 +10097,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Discard{}
 }
@@ -10119,7 +10119,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Return{}
 )";
   ASSERT_EQ(expect, got);
@@ -10145,7 +10145,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10185,7 +10185,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Return{}
 }
@@ -10215,7 +10215,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 200));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Return{
   {
     ScalarConstructor[not set]{0}
@@ -10249,7 +10249,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   continuing {
     Assignment{
@@ -10284,7 +10284,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -10321,7 +10321,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10381,7 +10381,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10452,7 +10452,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -10534,7 +10534,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   continuing {
     Assignment{
@@ -10576,7 +10576,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   Assignment{
     Identifier[not set]{var_1}
@@ -10630,7 +10630,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   If{
     (
@@ -10699,7 +10699,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10769,7 +10769,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10812,7 +10812,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{false}
@@ -10864,7 +10864,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -10915,7 +10915,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11019,7 +11019,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11062,7 +11062,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11113,7 +11113,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11171,7 +11171,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11227,7 +11227,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11287,7 +11287,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11348,7 +11348,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11431,7 +11431,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11526,7 +11526,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11611,7 +11611,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11680,7 +11680,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11747,7 +11747,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11821,7 +11821,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -11890,7 +11890,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -11948,7 +11948,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12021,7 +12021,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12117,7 +12117,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12256,7 +12256,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12337,7 +12337,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12405,7 +12405,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12462,7 +12462,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12534,7 +12534,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12622,7 +12622,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12701,7 +12701,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -12789,7 +12789,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12886,7 +12886,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -12982,7 +12982,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13091,7 +13091,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13188,7 +13188,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13269,7 +13269,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13333,7 +13333,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{0}
@@ -13418,7 +13418,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -13509,7 +13509,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{var_1}
   ScalarConstructor[not set]{1}
@@ -13578,7 +13578,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = "unhandled case";
   ASSERT_EQ(expect, got);
 }
@@ -13613,7 +13613,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = "unhandled case";
   ASSERT_EQ(expect, got);
 }
diff --git a/src/reader/spirv/function_composite_test.cc b/src/reader/spirv/function_composite_test.cc
index d292742..5907fa4 100644
--- a/src/reader/spirv/function_composite_test.cc
+++ b/src/reader/spirv/function_composite_test.cc
@@ -86,7 +86,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -128,7 +128,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Matrix) {
@@ -143,7 +143,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -169,7 +169,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Array) {
@@ -184,7 +184,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -200,7 +200,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Struct) {
@@ -215,7 +215,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -233,7 +233,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 using SpvParserTest_CompositeExtract = SpvParserTest;
@@ -250,7 +250,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -266,7 +266,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Vector_IndexTooBigError) {
@@ -301,7 +301,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -313,7 +313,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Matrix_IndexTooBigError) {
@@ -352,7 +352,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -367,7 +367,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Array) {
@@ -386,7 +386,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -398,7 +398,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, RuntimeArray_IsError) {
@@ -437,7 +437,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -449,7 +449,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct_DifferOnlyInMemberName) {
@@ -480,7 +480,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
   auto got = fe.ast_body();
-  EXPECT_THAT(ToString(p->get_program(), got), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), got), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -492,8 +492,8 @@
       }
     }
   })"))
-      << ToString(p->get_program(), got);
-  EXPECT_THAT(ToString(p->get_program(), got), HasSubstr(R"(
+      << ToString(p->builder().Symbols(), got);
+  EXPECT_THAT(ToString(p->builder().Symbols(), got), HasSubstr(R"(
   VariableConst{
     x_4
     none
@@ -505,7 +505,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), got);
+      << ToString(p->builder().Symbols(), got);
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct_IndexTooBigError) {
@@ -546,7 +546,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -567,7 +567,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 using SpvParserTest_CopyObject = SpvParserTest;
@@ -585,7 +585,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -605,7 +605,7 @@
       Identifier[not set]{x_1}
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_CopyObject, Pointer) {
@@ -624,7 +624,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -644,7 +644,7 @@
       Identifier[not set]{x_1}
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 using SpvParserTest_VectorShuffle = SpvParserTest;
@@ -665,7 +665,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -692,7 +692,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_UseBoth) {
@@ -708,7 +708,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -751,7 +751,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_AllOnesMapToNull) {
@@ -768,7 +768,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_10
     none
@@ -784,7 +784,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest_VectorShuffle, IndexTooBig_IsError) {
diff --git a/src/reader/spirv/function_conversion_test.cc b/src/reader/spirv/function_conversion_test.cc
index 1e352a2..352ee99 100644
--- a/src/reader/spirv/function_conversion_test.cc
+++ b/src/reader/spirv/function_conversion_test.cc
@@ -82,7 +82,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -93,7 +93,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, Bitcast_Vector) {
@@ -108,7 +108,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -123,7 +123,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_BadArg) {
@@ -238,7 +238,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -250,7 +250,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Scalar_FromUnsigned) {
@@ -266,7 +266,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -280,7 +280,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromSigned) {
@@ -296,7 +296,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -308,7 +308,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromUnsigned) {
@@ -324,7 +324,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -338,7 +338,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_BadArgType) {
@@ -387,7 +387,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -401,7 +401,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_FromUnsigned) {
@@ -417,7 +417,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -429,7 +429,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromSigned) {
@@ -445,7 +445,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -459,7 +459,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromUnsigned) {
@@ -475,7 +475,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -487,7 +487,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_BadArgType) {
@@ -537,7 +537,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -549,7 +549,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_ToUnsigned) {
@@ -565,7 +565,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -579,7 +579,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToSigned) {
@@ -595,7 +595,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -607,7 +607,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToUnsigned) {
@@ -623,7 +623,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -637,7 +637,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_BadArgType) {
@@ -687,7 +687,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -701,7 +701,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_ToUnsigned) {
@@ -717,7 +717,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -729,7 +729,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToSigned) {
@@ -745,7 +745,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -759,7 +759,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Vector_ToUnsigned) {
@@ -775,7 +775,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableConst{
     x_1
     none
@@ -787,7 +787,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 // TODO(dneto): OpSConvert // only if multiple widths
diff --git a/src/reader/spirv/function_decl_test.cc b/src/reader/spirv/function_decl_test.cc
index 614d7ae..0af7a00 100644
--- a/src/reader/spirv/function_decl_test.cc
+++ b/src/reader/spirv/function_decl_test.cc
@@ -59,7 +59,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.Emit());
-  auto got = Demangler().Demangle(p->get_program());
+  auto got = Demangler().Demangle(p->program());
   std::string expect = R"(Module{
   Function x_100 -> __void
   ()
@@ -83,7 +83,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.Emit());
 
-  auto got = Demangler().Demangle(p->get_program());
+  auto got = Demangler().Demangle(p->program());
   std::string expect = R"(Module{
   Function x_100 -> __f32
   ()
@@ -115,7 +115,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.Emit());
 
-  auto got = Demangler().Demangle(p->get_program());
+  auto got = Demangler().Demangle(p->program());
   std::string expect = R"(Module{
   Function x_100 -> __void
   (
@@ -159,7 +159,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.Emit());
 
-  auto got = Demangler().Demangle(p->get_program());
+  auto got = Demangler().Demangle(p->program());
   std::string expect = R"(Module{
   Function x_100 -> __void
   (
diff --git a/src/reader/spirv/function_glsl_std_450_test.cc b/src/reader/spirv/function_glsl_std_450_test.cc
index 0706ec6..9158de9 100644
--- a/src/reader/spirv/function_glsl_std_450_test.cc
+++ b/src/reader/spirv/function_glsl_std_450_test.cc
@@ -183,7 +183,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -191,14 +191,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
         )
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float_Floating, Vector) {
@@ -212,7 +212,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -220,14 +220,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
         )
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Scalar) {
@@ -241,7 +241,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -249,7 +249,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{f2}
@@ -257,7 +257,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Vector) {
@@ -271,7 +271,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -279,7 +279,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2f2}
@@ -287,7 +287,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Scalar) {
@@ -301,7 +301,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -309,14 +309,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
         )
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Vector) {
@@ -330,7 +330,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -338,14 +338,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
         )
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Scalar) {
@@ -359,7 +359,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -367,7 +367,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{f2}
@@ -375,7 +375,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Vector) {
@@ -389,7 +389,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -397,7 +397,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2f2}
@@ -405,7 +405,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Scalar) {
@@ -419,7 +419,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -427,7 +427,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{f2}
@@ -436,7 +436,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Vector) {
@@ -451,7 +451,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -459,7 +459,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2f2}
@@ -468,7 +468,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingUinting, Scalar) {
@@ -482,7 +482,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -490,7 +490,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{u1}
@@ -498,7 +498,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingUinting, Vector) {
@@ -513,7 +513,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -521,7 +521,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2u1}
@@ -529,7 +529,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Scalar) {
@@ -543,7 +543,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -551,7 +551,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{f1}
           Identifier[not set]{i1}
@@ -559,7 +559,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingInting, Vector) {
@@ -574,7 +574,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -582,7 +582,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2f1}
           Identifier[not set]{v2i1}
@@ -590,7 +590,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Float3_Float3Float3, Samples) {
@@ -605,7 +605,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -613,7 +613,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v3f1}
           Identifier[not set]{v3f2}
@@ -621,7 +621,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(Samples,
@@ -709,7 +709,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -717,14 +717,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{i1}
         )
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_Inting, Vector) {
@@ -739,7 +739,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -747,14 +747,14 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2i1}
         )
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Scalar) {
@@ -769,7 +769,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -777,7 +777,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{i1}
           Identifier[not set]{i2}
@@ -785,7 +785,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingInting, Vector) {
@@ -800,7 +800,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -808,7 +808,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2i1}
           Identifier[not set]{v2i2}
@@ -816,7 +816,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Scalar) {
@@ -831,7 +831,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -839,7 +839,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{i1}
           Identifier[not set]{i2}
@@ -848,7 +848,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_IntingIntingInting, Vector) {
@@ -863,7 +863,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -871,7 +871,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2i1}
           Identifier[not set]{v2i2}
@@ -880,7 +880,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(Samples,
@@ -907,7 +907,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -915,7 +915,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{u1}
           Identifier[not set]{u2}
@@ -923,7 +923,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Vector) {
@@ -938,7 +938,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -946,7 +946,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2u1}
           Identifier[not set]{v2u2}
@@ -954,7 +954,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Scalar) {
@@ -968,7 +968,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -976,7 +976,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{u1}
           Identifier[not set]{u2}
@@ -985,7 +985,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUintingUinting, Vector) {
@@ -1000,7 +1000,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1008,7 +1008,7 @@
     {
       Call[not set]{
         Identifier[not set]{)" + GetParam().wgsl_func +
-                                                                   R"(}
+                                                                         R"(}
         (
           Identifier[not set]{v2u1}
           Identifier[not set]{v2u2}
@@ -1017,7 +1017,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 INSTANTIATE_TEST_SUITE_P(Samples,
@@ -1043,7 +1043,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1095,7 +1095,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1153,7 +1153,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1211,7 +1211,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1271,7 +1271,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1329,7 +1329,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
@@ -1387,7 +1387,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  auto body = ToString(p->get_program(), fe.ast_body());
+  auto body = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body, HasSubstr(R"(
   VariableConst{
     x_1
diff --git a/src/reader/spirv/function_logical_test.cc b/src/reader/spirv/function_logical_test.cc
index 6cb1f14..dc40f0c 100644
--- a/src/reader/spirv/function_logical_test.cc
+++ b/src/reader/spirv/function_logical_test.cc
@@ -206,7 +206,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -218,7 +218,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvUnaryLogicalTest, LogicalNot_Vector) {
@@ -233,7 +233,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -249,7 +249,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 struct BinaryData {
@@ -296,7 +296,8 @@
      << GetParam().ast_type << "\n    {\n      Binary[not set]{"
      << "\n        " << GetParam().ast_lhs << "\n        " << GetParam().ast_op
      << "\n        " << GetParam().ast_rhs;
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(ss.str()))
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
+              HasSubstr(ss.str()))
       << assembly;
 }
 
@@ -702,7 +703,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -718,7 +719,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordEqual_Vector) {
@@ -733,7 +734,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -757,7 +758,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
@@ -772,7 +773,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -788,7 +789,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Vector) {
@@ -803,7 +804,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -827,7 +828,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
@@ -842,7 +843,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -858,7 +859,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Vector) {
@@ -873,7 +874,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -897,7 +898,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
@@ -912,7 +913,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -928,7 +929,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Vector) {
@@ -943,7 +944,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -967,7 +968,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
@@ -982,7 +983,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -998,7 +999,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Vector) {
@@ -1013,7 +1014,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1037,7 +1038,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
@@ -1052,7 +1053,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1068,7 +1069,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Vector) {
@@ -1083,7 +1084,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -1107,7 +1108,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_BoolParams) {
@@ -1122,7 +1123,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1139,7 +1140,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_IntScalarParams) {
@@ -1154,7 +1155,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1171,7 +1172,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_FloatScalarParams) {
@@ -1186,7 +1187,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1203,7 +1204,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_BoolCond_VectorParams) {
@@ -1218,7 +1219,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1243,7 +1244,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvFUnordTest, Select_VecBoolCond_VectorParams) {
@@ -1258,7 +1259,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1287,7 +1288,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 using SpvLogicalTest = SpvParserTestBase<::testing::Test>;
@@ -1304,7 +1305,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1323,7 +1324,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, All) {
@@ -1338,7 +1339,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1357,7 +1358,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsNan_Scalar) {
@@ -1372,7 +1373,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1387,7 +1388,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsNan_Vector) {
@@ -1402,7 +1403,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1421,7 +1422,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsInf_Scalar) {
@@ -1436,7 +1437,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1451,7 +1452,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvLogicalTest, IsInf_Vector) {
@@ -1466,7 +1467,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1485,7 +1486,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 // TODO(dneto): Kernel-guarded instructions.
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 94d460a..d28cdba 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -51,7 +51,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{true}
@@ -84,7 +84,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42}
@@ -113,7 +113,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42}
@@ -142,7 +142,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42.000000}
@@ -172,7 +172,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_2
     none
@@ -201,7 +201,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_2
@@ -243,7 +243,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_2
@@ -281,7 +281,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{42}
@@ -349,14 +349,14 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
     Identifier[not set]{z}
   }
   ScalarConstructor[not set]{42}
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitStatement_AccessChain_VectorConstOutOfBounds) {
@@ -413,7 +413,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     Identifier[not set]{myvar}
@@ -451,7 +451,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     Identifier[not set]{myvar}
@@ -495,7 +495,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     Identifier[not set]{myvar}
@@ -538,7 +538,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
@@ -587,7 +587,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
@@ -601,7 +601,7 @@
     Identifier[not set]{ancientness}
   }
   ScalarConstructor[not set]{420.000000}
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitStatement_AccessChain_StructNonConstIndex) {
@@ -698,7 +698,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     MemberAccessor[not set]{
@@ -739,7 +739,7 @@
       << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     ArrayAccessor[not set]{
@@ -813,7 +813,7 @@
   auto p = parser(test::Assemble(assembly));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
       << assembly << p->error();
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   RTArr -> __array__u32_stride_4
   S Struct{
@@ -848,7 +848,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   MemberAccessor[not set]{
     Identifier[not set]{myvar}
@@ -865,7 +865,7 @@
     ScalarConstructor[not set]{1}
   }
   ScalarConstructor[not set]{0}
-})")) << ToString(p->get_program(), fe.ast_body())
+})")) << ToString(p->builder().Symbols(), fe.ast_body())
       << p->error();
 }
 
@@ -888,7 +888,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(Assignment{
   ArrayAccessor[not set]{
     MemberAccessor[not set]{
@@ -898,7 +898,7 @@
     ScalarConstructor[not set]{1}
   }
   ScalarConstructor[not set]{0}
-})")) << ToString(p->get_program(), fe.ast_body())
+})")) << ToString(p->builder().Symbols(), fe.ast_body())
       << p->error();
 }
 
@@ -922,7 +922,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_2
@@ -942,7 +942,7 @@
 Assignment{
   Identifier[not set]{x_2}
   ScalarConstructor[not set]{0}
-})")) << ToString(p->get_program(), fe.ast_body())
+})")) << ToString(p->builder().Symbols(), fe.ast_body())
       << p->error();
 }
 
@@ -978,7 +978,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               Eq(R"(VariableDeclStatement{
   Variable{
     x_2
@@ -1013,7 +1013,7 @@
   ScalarConstructor[not set]{0}
 }
 Return{}
-)")) << ToString(p->get_program(), fe.ast_body())
+)")) << ToString(p->builder().Symbols(), fe.ast_body())
      << p->error();
 }
 
@@ -1066,7 +1066,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  const auto body_str = ToString(p->get_program(), fe.ast_body());
+  const auto body_str = ToString(p->builder().Symbols(), fe.ast_body());
   EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_1
diff --git a/src/reader/spirv/function_misc_test.cc b/src/reader/spirv/function_misc_test.cc
index 6a22ebd..6df487a 100644
--- a/src/reader/spirv/function_misc_test.cc
+++ b/src/reader/spirv/function_misc_test.cc
@@ -68,7 +68,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -108,7 +108,7 @@
       ScalarConstructor[not set]{0.000000}
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Vector) {
@@ -129,7 +129,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -171,7 +171,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Matrix) {
@@ -190,7 +190,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -212,7 +212,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Array) {
@@ -232,7 +232,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -246,7 +246,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Struct) {
@@ -265,7 +265,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   VariableConst{
     x_11
@@ -281,7 +281,7 @@
       }
     }
   }
-})")) << ToString(p->get_program(), fe.ast_body());
+})")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpNop) {
@@ -297,8 +297,8 @@
       << p->error() << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), Eq(R"(Return{}
-)")) << ToString(p->get_program(), fe.ast_body());
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), Eq(R"(Return{}
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 // Test swizzle generation.
@@ -329,7 +329,7 @@
     ASSERT_NE(result, nullptr);
     std::ostringstream ss;
     result->to_str(ss, 0);
-    auto str = Demangler().Demangle(p->get_program().Symbols(), ss.str());
+    auto str = Demangler().Demangle(p->program().Symbols(), ss.str());
     EXPECT_THAT(str, Eq(GetParam().expected_expr));
   } else {
     EXPECT_EQ(result, nullptr);
diff --git a/src/reader/spirv/function_var_test.cc b/src/reader/spirv/function_var_test.cc
index 9707955..288dbe2 100644
--- a/src/reader/spirv/function_var_test.cc
+++ b/src/reader/spirv/function_var_test.cc
@@ -91,7 +91,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_1
@@ -130,7 +130,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -169,7 +169,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -211,7 +211,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -285,7 +285,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     a
@@ -345,7 +345,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -360,7 +360,7 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_MatrixInitializer) {
@@ -384,7 +384,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -431,7 +431,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -446,7 +446,7 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_ArrayInitializer_Alias) {
@@ -466,8 +466,8 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
-              HasSubstr(R"(VariableDeclStatement{
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
+  const char* expect = R"(VariableDeclStatement{
   Variable{
     x_200
     function
@@ -481,7 +481,8 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)";
+  EXPECT_EQ(expect, got);
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_ArrayInitializer_Null) {
@@ -500,7 +501,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -515,7 +516,7 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_ArrayInitializer_Alias_Null) {
@@ -535,7 +536,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -550,7 +551,7 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_StructInitializer) {
@@ -570,7 +571,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -590,7 +591,7 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest, EmitFunctionVariables_StructInitializer_Null) {
@@ -610,7 +611,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitFunctionVariables());
 
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()),
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()),
               HasSubstr(R"(VariableDeclStatement{
   Variable{
     x_200
@@ -630,7 +631,7 @@
     }
   }
 }
-)")) << ToString(p->get_program(), fe.ast_body());
+)")) << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 TEST_F(SpvParserTest,
@@ -655,7 +656,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect =
       R"(VariableDeclStatement{
   Variable{
@@ -703,7 +704,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   Variable{
     x_25
@@ -775,7 +776,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   Variable{
     x_25
@@ -874,7 +875,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Assignment{
   Identifier[not set]{x_1}
   ScalarConstructor[not set]{0}
@@ -992,7 +993,7 @@
 
   // We don't hoist x_1 into its own mutable variable. It is emitted as
   // a const definition.
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1077,7 +1078,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{true}
@@ -1164,7 +1165,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(If{
   (
     ScalarConstructor[not set]{true}
@@ -1247,7 +1248,7 @@
 
   // We don't hoist x_1 into its own mutable variable. It is emitted as
   // a const definition.
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_1
@@ -1321,7 +1322,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   VariableDeclStatement{
     Variable{
@@ -1465,7 +1466,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(Loop{
   VariableDeclStatement{
     Variable{
@@ -1623,7 +1624,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_101
@@ -1792,7 +1793,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_101
@@ -1914,7 +1915,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   VariableConst{
     x_101
@@ -2022,7 +2023,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-  auto got = ToString(p->get_program(), fe.ast_body());
+  auto got = ToString(p->builder().Symbols(), fe.ast_body());
   auto* expect = R"(VariableDeclStatement{
   Variable{
     x_101_phi
diff --git a/src/reader/spirv/parser.h b/src/reader/spirv/parser.h
index 842ccbc..e189656 100644
--- a/src/reader/spirv/parser.h
+++ b/src/reader/spirv/parser.h
@@ -40,7 +40,8 @@
   /// @returns true if the parse was successful, false otherwise.
   bool Parse() override;
 
-  /// @returns the program. The program in the parser will be reset after this.
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
   Program program() override;
 
  private:
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index a13b77b..8c0b629 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -255,7 +255,7 @@
     : Reader(),
       spv_binary_(spv_binary),
       fail_stream_(&success_, &errors_),
-      bool_type_(program_.create<type::Bool>()),
+      bool_type_(builder_.create<type::Bool>()),
       namer_(fail_stream_),
       enum_converter_(fail_stream_),
       tools_context_(kInputEnv) {
@@ -310,7 +310,7 @@
 Program ParserImpl::program() {
   // TODO(dneto): Should we clear out spv_binary_ here, to reduce
   // memory usage?
-  return std::move(program_);
+  return tint::Program(std::move(builder_));
 }
 
 type::Type* ParserImpl::ConvertType(uint32_t type_id) {
@@ -344,7 +344,7 @@
 
   switch (spirv_type->kind()) {
     case spvtools::opt::analysis::Type::kVoid:
-      return save(program_.create<type::Void>());
+      return save(builder_.create<type::Void>());
     case spvtools::opt::analysis::Type::kBool:
       return save(bool_type_);
     case spvtools::opt::analysis::Type::kInteger:
@@ -374,7 +374,7 @@
     case spvtools::opt::analysis::Type::kImage:
       // Fake it for sampler and texture types.  These are handled in an
       // entirely different way.
-      return save(program_.create<type::Void>());
+      return save(builder_.create<type::Void>());
     default:
       break;
   }
@@ -713,8 +713,8 @@
 type::Type* ParserImpl::ConvertType(
     const spvtools::opt::analysis::Integer* int_ty) {
   if (int_ty->width() == 32) {
-    type::Type* signed_ty = program_.create<type::I32>();
-    type::Type* unsigned_ty = program_.create<type::U32>();
+    type::Type* signed_ty = builder_.create<type::I32>();
+    type::Type* unsigned_ty = builder_.create<type::U32>();
     signed_type_for_[unsigned_ty] = signed_ty;
     unsigned_type_for_[signed_ty] = unsigned_ty;
     return int_ty->IsSigned() ? signed_ty : unsigned_ty;
@@ -726,7 +726,7 @@
 type::Type* ParserImpl::ConvertType(
     const spvtools::opt::analysis::Float* float_ty) {
   if (float_ty->width() == 32) {
-    return program_.create<type::F32>();
+    return builder_.create<type::F32>();
   }
   Fail() << "unhandled float width: " << float_ty->width();
   return nullptr;
@@ -739,16 +739,16 @@
   if (ast_elem_ty == nullptr) {
     return nullptr;
   }
-  auto* this_ty = program_.create<type::Vector>(ast_elem_ty, num_elem);
+  auto* this_ty = builder_.create<type::Vector>(ast_elem_ty, num_elem);
   // Generate the opposite-signedness vector type, if this type is integral.
   if (unsigned_type_for_.count(ast_elem_ty)) {
-    auto* other_ty = program_.create<type::Vector>(
+    auto* other_ty = builder_.create<type::Vector>(
         unsigned_type_for_[ast_elem_ty], num_elem);
     signed_type_for_[other_ty] = this_ty;
     unsigned_type_for_[this_ty] = other_ty;
   } else if (signed_type_for_.count(ast_elem_ty)) {
     auto* other_ty =
-        program_.create<type::Vector>(signed_type_for_[ast_elem_ty], num_elem);
+        builder_.create<type::Vector>(signed_type_for_[ast_elem_ty], num_elem);
     unsigned_type_for_[other_ty] = this_ty;
     signed_type_for_[this_ty] = other_ty;
   }
@@ -765,7 +765,7 @@
   if (ast_scalar_ty == nullptr) {
     return nullptr;
   }
-  return program_.create<type::Matrix>(ast_scalar_ty, num_rows, num_columns);
+  return builder_.create<type::Matrix>(ast_scalar_ty, num_rows, num_columns);
 }
 
 type::Type* ParserImpl::ConvertType(
@@ -947,7 +947,7 @@
     }
     const auto member_name = namer_.GetMemberName(type_id, member_index);
     auto* ast_struct_member = create<ast::StructMember>(
-        Source{}, program_.Symbols().Register(member_name), ast_member_ty,
+        Source{}, builder_.Symbols().Register(member_name), ast_member_ty,
         std::move(ast_member_decorations));
     ast_members.push_back(ast_struct_member);
   }
@@ -963,13 +963,13 @@
   namer_.SuggestSanitizedName(type_id, "S");
 
   auto name = namer_.GetName(type_id);
-  auto* result = program_.create<type::Struct>(
-      program_.Symbols().Register(name), ast_struct);
+  auto* result = builder_.create<type::Struct>(
+      builder_.Symbols().Register(name), ast_struct);
   id_to_type_[type_id] = result;
   if (num_non_writable_members == members.size()) {
     read_only_struct_types_.insert(result);
   }
-  program_.AST().AddConstructedType(result);
+  builder_.AST().AddConstructedType(result);
   return result;
 }
 
@@ -1003,7 +1003,7 @@
     ast_storage_class = ast::StorageClass::kStorage;
     remap_buffer_block_type_.insert(type_id);
   }
-  return program_.create<type::Pointer>(ast_elem_ty, ast_storage_class);
+  return builder_.create<type::Pointer>(ast_elem_ty, ast_storage_class);
 }
 
 bool ParserImpl::RegisterTypes() {
@@ -1091,7 +1091,7 @@
           MakeVariable(inst.result_id(), ast::StorageClass::kNone, ast_type,
                        true, ast_expr, std::move(spec_id_decos));
       if (ast_var) {
-        program_.AST().AddGlobalVariable(ast_var);
+        builder_.AST().AddGlobalVariable(ast_var);
         scalar_spec_constants_.insert(inst.result_id());
       }
     }
@@ -1129,11 +1129,11 @@
     return;
   }
   const auto name = namer_.GetName(type_id);
-  auto* ast_alias_type = program_.create<type::Alias>(
-      program_.Symbols().Register(name), ast_underlying_type);
+  auto* ast_alias_type = builder_.create<type::Alias>(
+      builder_.Symbols().Register(name), ast_underlying_type);
   // Record this new alias as the AST type for this SPIR-V ID.
   id_to_type_[type_id] = ast_alias_type;
-  program_.AST().AddConstructedType(ast_alias_type);
+  builder_.AST().AddConstructedType(ast_alias_type);
 }
 
 bool ParserImpl::EmitModuleScopeVariables() {
@@ -1209,7 +1209,7 @@
                      ast_constructor, ast::VariableDecorationList{});
     // TODO(dneto): initializers (a.k.a. constructor expression)
     if (ast_var) {
-      program_.AST().AddGlobalVariable(ast_var);
+      builder_.AST().AddGlobalVariable(ast_var);
     }
   }
 
@@ -1226,7 +1226,7 @@
             create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kPosition),
         });
 
-    program_.AST().AddGlobalVariable(var);
+    builder_.AST().AddGlobalVariable(var);
   }
   return success_;
 }
@@ -1248,7 +1248,7 @@
     auto access = read_only_struct_types_.count(type)
                       ? ast::AccessControl::kReadOnly
                       : ast::AccessControl::kReadWrite;
-    type = program_.create<type::AccessControl>(access, type);
+    type = builder_.create<type::AccessControl>(access, type);
   }
 
   for (auto& deco : GetDecorationsFor(id)) {
@@ -1307,7 +1307,7 @@
 
   std::string name = namer_.Name(id);
   return create<ast::Variable>(Source{},                           // source
-                               program_.Symbols().Register(name),  // symbol
+                               builder_.Symbols().Register(name),  // symbol
                                sc,            // storage_class
                                type,          // type
                                is_const,      // is_const
@@ -1451,7 +1451,7 @@
   if (const auto* mat_ty = type->As<type::Matrix>()) {
     // Matrix components are columns
     auto* column_ty =
-        program_.create<type::Vector>(mat_ty->type(), mat_ty->rows());
+        builder_.create<type::Vector>(mat_ty->type(), mat_ty->rows());
     ast::ExpressionList ast_components;
     for (size_t i = 0; i < mat_ty->columns(); ++i) {
       ast_components.emplace_back(MakeNullValue(column_ty));
@@ -1546,14 +1546,14 @@
   if (other == nullptr) {
     Fail() << "no type provided";
   }
-  auto* i32 = program_.create<type::I32>();
+  auto* i32 = builder_.create<type::I32>();
   if (other->Is<type::F32>() || other->Is<type::U32>() ||
       other->Is<type::I32>()) {
     return i32;
   }
   auto* vec_ty = other->As<type::Vector>();
   if (vec_ty) {
-    return program_.create<type::Vector>(i32, vec_ty->size());
+    return builder_.create<type::Vector>(i32, vec_ty->size());
   }
   Fail() << "required numeric scalar or vector, but got " << other->type_name();
   return nullptr;
@@ -1564,14 +1564,14 @@
     Fail() << "no type provided";
     return nullptr;
   }
-  auto* u32 = program_.create<type::U32>();
+  auto* u32 = builder_.create<type::U32>();
   if (other->Is<type::F32>() || other->Is<type::U32>() ||
       other->Is<type::I32>()) {
     return u32;
   }
   auto* vec_ty = other->As<type::Vector>();
   if (vec_ty) {
-    return program_.create<type::Vector>(u32, vec_ty->size());
+    return builder_.create<type::Vector>(u32, vec_ty->size());
   }
   Fail() << "required numeric scalar or vector, but got " << other->type_name();
   return nullptr;
@@ -1845,7 +1845,7 @@
   // Construct the Tint handle type.
   type::Type* ast_store_type = nullptr;
   if (usage.IsSampler()) {
-    ast_store_type = program_.create<type::Sampler>(
+    ast_store_type = builder_.create<type::Sampler>(
         usage.IsComparisonSampler() ? type::SamplerKind::kComparisonSampler
                                     : type::SamplerKind::kSampler);
   } else if (usage.IsTexture()) {
@@ -1876,13 +1876,13 @@
       // OpImage variable with an OpImage*Dref* instruction.  In WGSL we must
       // treat that as a depth texture.
       if (image_type->depth() || usage.IsDepthTexture()) {
-        ast_store_type = program_.create<type::DepthTexture>(dim);
+        ast_store_type = builder_.create<type::DepthTexture>(dim);
       } else if (image_type->is_multisampled()) {
         // Multisampled textures are never depth textures.
-        ast_store_type = program_.create<type::MultisampledTexture>(
+        ast_store_type = builder_.create<type::MultisampledTexture>(
             dim, ast_sampled_component_type);
       } else {
-        ast_store_type = program_.create<type::SampledTexture>(
+        ast_store_type = builder_.create<type::SampledTexture>(
             dim, ast_sampled_component_type);
       }
     } else {
@@ -1893,8 +1893,8 @@
       if (format == type::ImageFormat::kNone) {
         return nullptr;
       }
-      ast_store_type = program_.create<type::AccessControl>(
-          access, program_.create<type::StorageTexture>(dim, format));
+      ast_store_type = builder_.create<type::AccessControl>(
+          access, builder_.create<type::StorageTexture>(dim, format));
     }
   } else {
     Fail() << "unsupported: UniformConstant variable is not a recognized "
@@ -1904,7 +1904,7 @@
   }
 
   // Form the pointer type.
-  auto* result = program_.create<type::Pointer>(
+  auto* result = builder_.create<type::Pointer>(
       ast_store_type, ast::StorageClass::kUniformConstant);
   // Remember it for later.
   handle_type_[&var] = result;
@@ -1922,7 +1922,7 @@
     case type::ImageFormat::kRg32Uint:
     case type::ImageFormat::kRgba16Uint:
     case type::ImageFormat::kRgba32Uint:
-      return program_.create<type::U32>();
+      return builder_.create<type::U32>();
 
     case type::ImageFormat::kR8Sint:
     case type::ImageFormat::kR16Sint:
@@ -1933,7 +1933,7 @@
     case type::ImageFormat::kRg32Sint:
     case type::ImageFormat::kRgba16Sint:
     case type::ImageFormat::kRgba32Sint:
-      return program_.create<type::I32>();
+      return builder_.create<type::I32>();
 
     case type::ImageFormat::kR8Unorm:
     case type::ImageFormat::kRg8Unorm:
@@ -1952,7 +1952,7 @@
     case type::ImageFormat::kRg32Float:
     case type::ImageFormat::kRgba16Float:
     case type::ImageFormat::kRgba32Float:
-      return program_.create<type::F32>();
+      return builder_.create<type::F32>();
     default:
       break;
   }
@@ -1992,7 +1992,7 @@
     case type::ImageFormat::kRg8Uint:
     case type::ImageFormat::kRg8Unorm:
       // Two channels
-      return program_.create<type::Vector>(component_type, 2);
+      return builder_.create<type::Vector>(component_type, 2);
 
     case type::ImageFormat::kBgra8Unorm:
     case type::ImageFormat::kBgra8UnormSrgb:
@@ -2009,7 +2009,7 @@
     case type::ImageFormat::kRgba8Unorm:
     case type::ImageFormat::kRgba8UnormSrgb:
       // Four channels
-      return program_.create<type::Vector>(component_type, 4);
+      return builder_.create<type::Vector>(component_type, 4);
 
     default:
       break;
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index c3cee04..9827e0e 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -33,7 +33,7 @@
 #include "spirv-tools/libspirv.hpp"
 #include "src/ast/expression.h"
 #include "src/ast/struct_member_decoration.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/reader/reader.h"
 #include "src/reader/spirv/entry_point_info.h"
 #include "src/reader/spirv/enum_converter.h"
@@ -95,12 +95,13 @@
   /// @returns true if the parse was successful, false otherwise.
   bool Parse() override;
 
-  /// @returns the program. The program in the parser will be reset after this.
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
   Program program() override;
 
-  /// Returns a reference to the program, without resetting it.
-  /// @returns the program
-  Program& get_program() { return program_; }
+  /// @returns a reference to the internal builder, without building the
+  /// program. To be used only for testing.
+  ProgramBuilder& builder() { return builder_; }
 
   /// Logs failure, ands return a failure stream to accumulate diagnostic
   /// messages. By convention, a failure should only be logged along with
@@ -118,14 +119,14 @@
   const std::string error() { return errors_.str(); }
 
   /// Builds an internal representation of the SPIR-V binary,
-  /// and parses it into a Tint program.  Diagnostics are emitted
+  /// and parses it into a Tint AST module.  Diagnostics are emitted
   /// to the error stream.
   /// @returns true if it was successful.
   bool BuildAndParseInternalModule() {
     return BuildInternalModule() && ParseInternalModule();
   }
   /// Builds an internal representation of the SPIR-V binary,
-  /// and parses the module, except functions, into a Tint program.
+  /// and parses the module, except functions, into a Tint AST module.
   /// Diagnostics are emitted to the error stream.
   /// @returns true if it was successful.
   bool BuildAndParseInternalModuleExceptFunctions() {
@@ -524,20 +525,19 @@
   bool ParseArrayDecorations(const spvtools::opt::analysis::Type* spv_type,
                              ast::ArrayDecorationList* decorations);
 
-  /// Creates a new `ast::Node` owned by the Program. When the Program is
-  /// destructed, the `ast::Node` will also be destructed.
+  /// Creates a new `ast::Node` owned by the ProgramBuilder.
   /// @param args the arguments to pass to the type constructor
   /// @returns the node pointer
   template <typename T, typename... ARGS>
   T* create(ARGS&&... args) {
-    return program_.create<T>(std::forward<ARGS>(args)...);
+    return builder_.create<T>(std::forward<ARGS>(args)...);
   }
 
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;
 
-  // The resulting module in Tint AST form.
-  Program program_;
+  // The program builder.
+  ProgramBuilder builder_;
 
   // Is the parse successful?
   bool success_ = true;
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index 766021f..12d67b9 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -566,7 +566,7 @@
   EXPECT_TRUE(type->Is<type::Struct>());
   std::stringstream ss;
   type->As<type::Struct>()->impl()->to_str(ss, 0);
-  EXPECT_THAT(Demangler().Demangle(p->get_program().Symbols(), ss.str()),
+  EXPECT_THAT(Demangler().Demangle(p->program().Symbols(), ss.str()),
               Eq(R"(Struct{
   StructMember{field0: __u32}
   StructMember{field1: __f32}
@@ -588,7 +588,7 @@
   EXPECT_TRUE(type->Is<type::Struct>());
   std::stringstream ss;
   type->As<type::Struct>()->impl()->to_str(ss, 0);
-  EXPECT_THAT(Demangler().Demangle(p->get_program().Symbols(), ss.str()),
+  EXPECT_THAT(Demangler().Demangle(p->program().Symbols(), ss.str()),
               Eq(R"(Struct{
   [[block]]
   StructMember{field0: __u32}
@@ -614,7 +614,7 @@
   EXPECT_TRUE(type->Is<type::Struct>());
   std::stringstream ss;
   type->As<type::Struct>()->impl()->to_str(ss, 0);
-  EXPECT_THAT(Demangler().Demangle(p->get_program().Symbols(), ss.str()),
+  EXPECT_THAT(Demangler().Demangle(p->program().Symbols(), ss.str()),
               Eq(R"(Struct{
   StructMember{[[ offset 0 ]] field0: __f32}
   StructMember{[[ offset 8 ]] field1: __vec_2__f32}
diff --git a/src/reader/spirv/parser_impl_function_decl_test.cc b/src/reader/spirv/parser_impl_function_decl_test.cc
index fce07a9..d9f5458 100644
--- a/src/reader/spirv/parser_impl_function_decl_test.cc
+++ b/src/reader/spirv/parser_impl_function_decl_test.cc
@@ -17,6 +17,7 @@
 #include <vector>
 
 #include "gmock/gmock.h"
+#include "src/ast/module.h"
 #include "src/reader/spirv/parser_impl.h"
 #include "src/reader/spirv/parser_impl_test_helper.h"
 #include "src/reader/spirv/spirv_tools_helpers_test.h"
@@ -53,7 +54,8 @@
   auto p = parser(test::Assemble(CommonTypes()));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
 }
 
@@ -64,7 +66,8 @@
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, Not(HasSubstr("Function{")));
 }
 
@@ -79,9 +82,10 @@
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + p->get_program().Symbols().Get("main").to_str() +
+  Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
   StageDecoration{vertex}
   ()
@@ -99,9 +103,10 @@
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + p->get_program().Symbols().Get("main").to_str() +
+  Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
   StageDecoration{fragment}
   ()
@@ -119,9 +124,10 @@
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + p->get_program().Symbols().Get("main").to_str() +
+  Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
   StageDecoration{compute}
   ()
@@ -141,15 +147,16 @@
   auto p = parser(test::Assemble(input));
   ASSERT_TRUE(p->BuildAndParseInternalModule());
   ASSERT_TRUE(p->error().empty()) << p->error();
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + p->get_program().Symbols().Get("frag_main").to_str() +
+  Function )" + program.Symbols().Get("frag_main").to_str() +
                                      R"( -> __void
   StageDecoration{fragment}
   ()
   {)"));
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + p->get_program().Symbols().Get("comp_main").to_str() +
+  Function )" + program.Symbols().Get("comp_main").to_str() +
                                      R"( -> __void
   StageDecoration{compute}
   ()
@@ -165,9 +172,10 @@
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = p->get_program().to_str();
+  Program program = p->program();
+  const auto program_ast = program.AST().to_str();
   EXPECT_THAT(program_ast, HasSubstr(R"(
-  Function )" + p->get_program().Symbols().Get("main").to_str() +
+  Function )" + program.Symbols().Get("main").to_str() +
                                      R"( -> __void
   ()
   {)"));
@@ -199,7 +207,8 @@
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = Demangler().Demangle(p->get_program());
+  Program program = p->program();
+  const auto program_ast = Demangler().Demangle(program);
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function leaf -> __u32
   ()
@@ -266,7 +275,8 @@
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = Demangler().Demangle(p->get_program());
+  Program program = p->program();
+  const auto program_ast = Demangler().Demangle(program);
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function ret_float -> __f32
   ()
@@ -295,7 +305,8 @@
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = Demangler().Demangle(p->get_program());
+  Program program = p->program();
+  const auto program_ast = Demangler().Demangle(program);
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function mixed_params -> __void
   (
@@ -334,7 +345,8 @@
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = Demangler().Demangle(p->get_program());
+  Program program = p->program();
+  const auto program_ast = Demangler().Demangle(program);
   EXPECT_THAT(program_ast, HasSubstr(R"(
   Function mixed_params -> __void
   (
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index d1d5f6c..1c95533 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -1129,7 +1129,7 @@
   auto p = parser(test::Assemble(assembly));
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
   EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto program = Demangler().Demangle(p->get_program());
+  const auto program = Demangler().Demangle(p->program());
   EXPECT_THAT(program, HasSubstr(GetParam().var_decl)) << program;
 }
 
@@ -1305,7 +1305,7 @@
   auto p = parser(test::Assemble(assembly));
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
   EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto program = Demangler().Demangle(p->get_program());
+  const auto program = Demangler().Demangle(p->program());
   EXPECT_THAT(program, HasSubstr(GetParam().var_decl))
       << "DECLARATIONS ARE BAD " << program;
   EXPECT_THAT(program, HasSubstr(GetParam().texture_builtin))
@@ -2554,7 +2554,7 @@
   auto p = parser(test::Assemble(assembly));
   ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
   EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto program = Demangler().Demangle(p->get_program());
+  const auto program = Demangler().Demangle(p->program());
   EXPECT_THAT(program, HasSubstr(GetParam().var_decl))
       << "DECLARATIONS ARE BAD " << program;
   EXPECT_THAT(program, HasSubstr(GetParam().texture_builtin))
@@ -3706,10 +3706,11 @@
       EXPECT_TRUE(fe.success()) << p->error();
       EXPECT_TRUE(p->error().empty());
       std::vector<std::string> result_strings;
+      Program program = p->program();
       for (auto* expr : result) {
         ASSERT_NE(expr, nullptr);
         result_strings.push_back(
-            Demangler().Demangle(p->get_program().Symbols(), expr->str()));
+            Demangler().Demangle(program.Symbols(), expr->str()));
       }
       EXPECT_THAT(result_strings,
                   ::testing::ContainerEq(GetParam().expected_expressions));
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 085b3db..13e6c0f 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -15,6 +15,7 @@
 #include <string>
 
 #include "gmock/gmock.h"
+#include "src/ast/module.h"
 #include "src/demangler.h"
 #include "src/reader/spirv/function.h"
 #include "src/reader/spirv/parser_impl.h"
@@ -70,8 +71,8 @@
   auto p = parser(test::Assemble(""));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto program_ast = p->program().to_str();
-  EXPECT_THAT(program_ast, Not(HasSubstr("Variable")));
+  const auto module_ast = p->program().AST().to_str();
+  EXPECT_THAT(module_ast, Not(HasSubstr("Variable")));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, BadStorageClass_NotAWebGPUStorageClass) {
@@ -143,7 +144,7 @@
 
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     x_52
@@ -162,7 +163,7 @@
 
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     the_counter
@@ -181,7 +182,7 @@
 
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     my_own_private_idaho
@@ -200,7 +201,7 @@
 
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     Decorations{
@@ -250,7 +251,7 @@
   EXPECT_EQ(position_info.pointer_type_id, 11u);
   EXPECT_EQ(position_info.storage_class, SpvStorageClassOutput);
   EXPECT_EQ(position_info.per_vertex_var_id, 1u);
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     Decorations{
@@ -330,7 +331,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
     Assignment{
       Identifier[not set]{gl_Position}
@@ -383,7 +384,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
     Assignment{
       Identifier[not set]{gl_Position}
@@ -414,7 +415,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
     Assignment{
       MemberAccessor[not set]{
@@ -445,7 +446,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   {
     Assignment{
@@ -475,7 +476,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Variable{
     Decorations{
@@ -531,7 +532,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Variable{
     x_900
@@ -598,7 +599,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Variable{
     Decorations{
@@ -649,7 +650,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Function x_500 -> __void
   ()
@@ -693,7 +694,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Variable{
     x_900
@@ -729,7 +730,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Function x_500 -> __void
   ()
@@ -756,7 +757,7 @@
   auto p = parser(test::Assemble(assembly));
   EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
   EXPECT_TRUE(p->error().empty()) << p->error();
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_EQ(module_str, R"(Module{
   Function x_500 -> __void
   ()
@@ -860,7 +861,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_1
     private
@@ -917,7 +918,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_1
     private
@@ -966,7 +967,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_1
     private
@@ -1010,7 +1011,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1033,7 +1034,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1056,7 +1057,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1079,7 +1080,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1102,7 +1103,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1125,7 +1126,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1148,7 +1149,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1171,7 +1172,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1194,7 +1195,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1223,7 +1224,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1259,7 +1260,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1295,7 +1296,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1332,7 +1333,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1355,7 +1356,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1378,7 +1379,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1403,7 +1404,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1432,7 +1433,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1461,7 +1462,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(Variable{
     x_200
     private
@@ -1492,7 +1493,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     Decorations{
@@ -1544,7 +1545,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     Decorations{
@@ -1598,7 +1599,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   Variable{
     Decorations{
@@ -1652,7 +1653,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   S Struct{
     [[block]]
@@ -1683,7 +1684,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   S Struct{
     [[block]]
@@ -1712,7 +1713,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   S Struct{
     [[block]]
@@ -1761,7 +1762,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   S Struct{
     [[block]]
@@ -1790,7 +1791,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   S Struct{
     [[block]]
@@ -1822,7 +1823,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   S Struct{
     [[block]]
@@ -1846,7 +1847,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   VariableConst{
     Decorations{
@@ -1871,7 +1872,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   VariableConst{
     Decorations{
@@ -1896,7 +1897,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   VariableConst{
     Decorations{
@@ -1921,7 +1922,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   VariableConst{
     Decorations{
@@ -1946,7 +1947,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   VariableConst{
     Decorations{
@@ -1972,7 +1973,7 @@
   )"));
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  const auto module_str = Demangler().Demangle(p->get_program());
+  const auto module_str = Demangler().Demangle(p->program());
   EXPECT_THAT(module_str, HasSubstr(R"(
   VariableConst{
     myconst
@@ -2001,7 +2002,7 @@
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_TRUE(fe.EmitBody()) << p->error();
   EXPECT_TRUE(p->error().empty());
-  EXPECT_THAT(ToString(p->get_program(), fe.ast_body()), HasSubstr(R"(
+  EXPECT_THAT(ToString(p->builder().Symbols(), fe.ast_body()), HasSubstr(R"(
   VariableConst{
     x_1
     none
@@ -2014,7 +2015,7 @@
       }
     }
   })"))
-      << ToString(p->get_program(), fe.ast_body());
+      << ToString(p->builder().Symbols(), fe.ast_body());
 }
 
 }  // namespace
diff --git a/src/reader/spirv/parser_impl_named_types_test.cc b/src/reader/spirv/parser_impl_named_types_test.cc
index 04dd8ac..2494080 100644
--- a/src/reader/spirv/parser_impl_named_types_test.cc
+++ b/src/reader/spirv/parser_impl_named_types_test.cc
@@ -17,6 +17,7 @@
 #include <vector>
 
 #include "gmock/gmock.h"
+#include "src/ast/module.h"
 #include "src/ast/struct.h"
 #include "src/demangler.h"
 #include "src/reader/spirv/parser_impl.h"
@@ -40,7 +41,7 @@
     %s = OpTypeStruct %uint %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()), HasSubstr("S Struct"));
+  EXPECT_THAT(Demangler().Demangle(p->program()), HasSubstr("S Struct"));
 }
 
 TEST_F(SpvParserTest, NamedTypes_NamedStruct) {
@@ -50,8 +51,7 @@
     %s = OpTypeStruct %uint %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()),
-              HasSubstr("mystruct Struct"));
+  EXPECT_THAT(Demangler().Demangle(p->program()), HasSubstr("mystruct Struct"));
 }
 
 TEST_F(SpvParserTest, NamedTypes_Dup_EmitBoth) {
@@ -61,7 +61,7 @@
     %s2 = OpTypeStruct %uint %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
-  EXPECT_THAT(Demangler().Demangle(p->get_program()), HasSubstr(R"(S Struct{
+  EXPECT_THAT(Demangler().Demangle(p->program()), HasSubstr(R"(S Struct{
     StructMember{field0: __u32}
     StructMember{field1: __u32}
   }
@@ -82,7 +82,7 @@
     %arr = OpTypeRuntimeArray %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()),
+  EXPECT_THAT(Demangler().Demangle(p->program()),
               HasSubstr("RTArr -> __array__u32_stride_8\n"));
 }
 
@@ -95,7 +95,7 @@
     %arr2 = OpTypeRuntimeArray %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()),
+  EXPECT_THAT(Demangler().Demangle(p->program()),
               HasSubstr("RTArr -> __array__u32_stride_8\n  RTArr_1 -> "
                         "__array__u32_stride_8\n"));
 }
@@ -108,7 +108,7 @@
     %arr = OpTypeRuntimeArray %uint
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()),
+  EXPECT_THAT(Demangler().Demangle(p->program()),
               HasSubstr("myrtarr -> __array__u32_stride_8\n"));
 }
 
@@ -122,7 +122,7 @@
     %arr2 = OpTypeArray %uint %uint_5
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()),
+  EXPECT_THAT(Demangler().Demangle(p->program()),
               HasSubstr("myarr -> __array__u32_5_stride_8"));
 }
 
@@ -136,7 +136,7 @@
     %arr2 = OpTypeArray %uint %uint_5
   )"));
   EXPECT_TRUE(p->BuildAndParseInternalModule());
-  EXPECT_THAT(Demangler().Demangle(p->get_program()),
+  EXPECT_THAT(Demangler().Demangle(p->program()),
               HasSubstr("Arr -> __array__u32_5_stride_8\n  Arr_1 -> "
                         "__array__u32_5_stride_8"));
 }
diff --git a/src/reader/spirv/parser_impl_test_helper.h b/src/reader/spirv/parser_impl_test_helper.h
index 86ca5a9..0b51e9e 100644
--- a/src/reader/spirv/parser_impl_test_helper.h
+++ b/src/reader/spirv/parser_impl_test_helper.h
@@ -58,16 +58,16 @@
 using SpvParserTest = SpvParserTestBase<::testing::Test>;
 
 /// Returns the string dump of a statement list.
-/// @param program the program
+/// @param symbols the SymbolTable
 /// @param stmts the statement list
 /// @returns the string dump of a statement list.
-inline std::string ToString(const Program& program,
+inline std::string ToString(const SymbolTable& symbols,
                             const ast::StatementList& stmts) {
   std::ostringstream outs;
   for (const auto* stmt : stmts) {
     stmt->to_str(outs, 0);
   }
-  return Demangler().Demangle(program.Symbols(), outs.str());
+  return Demangler().Demangle(symbols, outs.str());
 }
 
 }  // namespace spirv
diff --git a/src/reader/wgsl/parser.h b/src/reader/wgsl/parser.h
index 42da6bb..ddc8217 100644
--- a/src/reader/wgsl/parser.h
+++ b/src/reader/wgsl/parser.h
@@ -39,7 +39,8 @@
   /// @returns true if the parse was successful, false otherwise.
   bool Parse() override;
 
-  /// @returns the program. The program in the parser will be reset after this.
+  /// @returns the program. The program builder in the parser will be reset
+  /// after this.
   Program program() override;
 
  private:
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 1270924..341c252 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -293,7 +293,7 @@
     }
   }
 
-  assert(program_.IsValid());
+  assert(builder_.IsValid());
 }
 
 // global_decl
@@ -323,7 +323,7 @@
       if (!expect("variable declaration", Token::Type::kSemicolon))
         return Failure::kErrored;
 
-      program_.AST().AddGlobalVariable(gv.value);
+      builder_.AST().AddGlobalVariable(gv.value);
       return true;
     }
 
@@ -335,7 +335,7 @@
       if (!expect("constant declaration", Token::Type::kSemicolon))
         return Failure::kErrored;
 
-      program_.AST().AddGlobalVariable(gc.value);
+      builder_.AST().AddGlobalVariable(gc.value);
       return true;
     }
 
@@ -347,7 +347,7 @@
       if (!expect("type alias", Token::Type::kSemicolon))
         return Failure::kErrored;
 
-      program_.AST().AddConstructedType(ta.value);
+      builder_.AST().AddConstructedType(ta.value);
       return true;
     }
 
@@ -359,10 +359,9 @@
       if (!expect("struct declaration", Token::Type::kSemicolon))
         return Failure::kErrored;
 
-      auto* type = str.value;
-      register_constructed(
-          program_.Symbols().NameFor(type->As<type::Struct>()->symbol()), type);
-      program_.AST().AddConstructedType(type);
+      register_constructed(builder_.Symbols().NameFor(str.value->symbol()),
+                           str.value);
+      builder_.AST().AddConstructedType(str.value);
       return true;
     }
 
@@ -378,7 +377,7 @@
   if (func.errored)
     errored = true;
   if (func.matched) {
-    program_.AST().Functions().Add(func.value);
+    builder_.AST().Functions().Add(func.value);
     return true;
   }
 
@@ -437,7 +436,7 @@
 
   return create<ast::Variable>(
       decl->source,                             // source
-      program_.Symbols().Register(decl->name),  // symbol
+      builder_.Symbols().Register(decl->name),  // symbol
       decl->storage_class,                      // storage_class
       decl->type,                               // type
       false,                                    // is_const
@@ -466,7 +465,7 @@
 
   return create<ast::Variable>(
       decl->source,                             // source
-      program_.Symbols().Register(decl->name),  // symbol
+      builder_.Symbols().Register(decl->name),  // symbol
       ast::StorageClass::kNone,                 // storage_class
       decl->type,                               // type
       true,                                     // is_const
@@ -516,7 +515,7 @@
     if (subtype.errored)
       return Failure::kErrored;
 
-    return program_.create<type::SampledTexture>(dim.value, subtype.value);
+    return builder_.create<type::SampledTexture>(dim.value, subtype.value);
   }
 
   auto ms_dim = multisampled_texture_type();
@@ -527,7 +526,7 @@
     if (subtype.errored)
       return Failure::kErrored;
 
-    return program_.create<type::MultisampledTexture>(ms_dim.value,
+    return builder_.create<type::MultisampledTexture>(ms_dim.value,
                                                       subtype.value);
   }
 
@@ -541,7 +540,7 @@
     if (format.errored)
       return Failure::kErrored;
 
-    return program_.create<type::StorageTexture>(storage.value, format.value);
+    return builder_.create<type::StorageTexture>(storage.value, format.value);
   }
 
   // DEPRECATED
@@ -555,9 +554,9 @@
     if (format.errored)
       return Failure::kErrored;
 
-    return program_.create<type::AccessControl>(
+    return builder_.create<type::AccessControl>(
         ac_storage->second,
-        program_.create<type::StorageTexture>(ac_storage->first, format.value));
+        builder_.create<type::StorageTexture>(ac_storage->first, format.value));
   }
 
   return Failure::kNoMatch;
@@ -568,10 +567,10 @@
 //  | SAMPLER_COMPARISON
 Maybe<type::Type*> ParserImpl::sampler_type() {
   if (match(Token::Type::kSampler))
-    return program_.create<type::Sampler>(type::SamplerKind::kSampler);
+    return builder_.create<type::Sampler>(type::SamplerKind::kSampler);
 
   if (match(Token::Type::kComparisonSampler))
-    return program_.create<type::Sampler>(
+    return builder_.create<type::Sampler>(
         type::SamplerKind::kComparisonSampler);
 
   return Failure::kNoMatch;
@@ -717,17 +716,17 @@
 //  | TEXTURE_DEPTH_CUBE_ARRAY
 Maybe<type::Type*> ParserImpl::depth_texture_type() {
   if (match(Token::Type::kTextureDepth2d))
-    return program_.create<type::DepthTexture>(type::TextureDimension::k2d);
+    return builder_.create<type::DepthTexture>(type::TextureDimension::k2d);
 
   if (match(Token::Type::kTextureDepth2dArray))
-    return program_.create<type::DepthTexture>(
+    return builder_.create<type::DepthTexture>(
         type::TextureDimension::k2dArray);
 
   if (match(Token::Type::kTextureDepthCube))
-    return program_.create<type::DepthTexture>(type::TextureDimension::kCube);
+    return builder_.create<type::DepthTexture>(type::TextureDimension::kCube);
 
   if (match(Token::Type::kTextureDepthCubeArray))
-    return program_.create<type::DepthTexture>(
+    return builder_.create<type::DepthTexture>(
         type::TextureDimension::kCubeArray);
 
   return Failure::kNoMatch;
@@ -913,7 +912,7 @@
   for (auto* deco : access_decos) {
     // If we have an access control decoration then we take it and wrap our
     // type up with that decoration
-    ty = program_.create<type::AccessControl>(
+    ty = builder_.create<type::AccessControl>(
         deco->As<ast::AccessDecoration>()->value(), ty);
   }
 
@@ -975,8 +974,8 @@
   if (!type.matched)
     return add_error(peek(), "invalid type alias");
 
-  auto* alias = program_.create<type::Alias>(
-      program_.Symbols().Register(name.value), type.value);
+  auto* alias = builder_.create<type::Alias>(
+      builder_.Symbols().Register(name.value), type.value);
   register_constructed(name.value, alias);
 
   return alias;
@@ -1034,16 +1033,16 @@
   }
 
   if (match(Token::Type::kBool))
-    return program_.create<type::Bool>();
+    return builder_.create<type::Bool>();
 
   if (match(Token::Type::kF32))
-    return program_.create<type::F32>();
+    return builder_.create<type::F32>();
 
   if (match(Token::Type::kI32))
-    return program_.create<type::I32>();
+    return builder_.create<type::I32>();
 
   if (match(Token::Type::kU32))
-    return program_.create<type::U32>();
+    return builder_.create<type::U32>();
 
   if (t.IsVec2() || t.IsVec3() || t.IsVec4()) {
     next();  // Consume the peek
@@ -1101,7 +1100,7 @@
     if (subtype.errored)
       return Failure::kErrored;
 
-    return program_.create<type::Pointer>(subtype.value, sc.value);
+    return builder_.create<type::Pointer>(subtype.value, sc.value);
   });
 }
 
@@ -1118,7 +1117,7 @@
   if (subtype.errored)
     return Failure::kErrored;
 
-  return program_.create<type::Vector>(subtype.value, count);
+  return builder_.create<type::Vector>(subtype.value, count);
 }
 
 Expect<type::Type*> ParserImpl::expect_type_decl_array(
@@ -1162,7 +1161,7 @@
   if (subtype.errored)
     return Failure::kErrored;
 
-  return program_.create<type::Matrix>(subtype.value, rows, columns);
+  return builder_.create<type::Matrix>(subtype.value, rows, columns);
 }
 
 // storage_class
@@ -1229,7 +1228,7 @@
     return Failure::kErrored;
 
   return create<type::Struct>(
-      program_.Symbols().Register(name.value),
+      builder_.Symbols().Register(name.value),
       create<ast::Struct>(source, std::move(body.value),
                           std::move(struct_decos.value)));
 }
@@ -1284,7 +1283,7 @@
     return Failure::kErrored;
 
   return create<ast::StructMember>(decl->source,
-                                   program_.Symbols().Register(decl->name),
+                                   builder_.Symbols().Register(decl->name),
                                    decl->type, std::move(member_decos.value));
 }
 
@@ -1321,7 +1320,7 @@
     return Failure::kErrored;
 
   return create<ast::Function>(
-      header->source, program_.Symbols().Register(header->name), header->params,
+      header->source, builder_.Symbols().Register(header->name), header->params,
       header->return_type, body.value, func_decos.value);
 }
 
@@ -1330,7 +1329,7 @@
 //   | VOID
 Maybe<type::Type*> ParserImpl::function_type_decl() {
   if (match(Token::Type::kVoid))
-    return program_.create<type::Void>();
+    return builder_.create<type::Void>();
 
   return type_decl();
 }
@@ -1391,7 +1390,7 @@
   for (;;) {
     auto* var = create<ast::Variable>(
         decl->source,                             // source
-        program_.Symbols().Register(decl->name),  // symbol
+        builder_.Symbols().Register(decl->name),  // symbol
         ast::StorageClass::kNone,                 // storage_class
         decl->type,                               // type
         true,                                     // is_const
@@ -1662,7 +1661,7 @@
 
     auto* var = create<ast::Variable>(
         decl->source,                             // source
-        program_.Symbols().Register(decl->name),  // symbol
+        builder_.Symbols().Register(decl->name),  // symbol
         ast::StorageClass::kNone,                 // storage_class
         decl->type,                               // type
         true,                                     // is_const
@@ -1691,7 +1690,7 @@
 
   auto* var =
       create<ast::Variable>(decl->source,                             // source
-                            program_.Symbols().Register(decl->name),  // symbol
+                            builder_.Symbols().Register(decl->name),  // symbol
                             decl->storage_class,             // storage_class
                             decl->type,                      // type
                             false,                           // is_const
@@ -2093,7 +2092,7 @@
       Source{}, create<ast::CallExpression>(
                     source,
                     create<ast::IdentifierExpression>(
-                        source, program_.Symbols().Register(name)),
+                        source, builder_.Symbols().Register(name)),
                     std::move(params)));
 }
 
@@ -2166,7 +2165,7 @@
 
   if (match(Token::Type::kIdentifier))
     return create<ast::IdentifierExpression>(
-        t.source(), program_.Symbols().Register(t.to_str()));
+        t.source(), builder_.Symbols().Register(t.to_str()));
 
   auto type = type_decl();
   if (type.errored)
@@ -2242,7 +2241,7 @@
     return postfix_expr(create<ast::MemberAccessorExpression>(
         ident.source, prefix,
         create<ast::IdentifierExpression>(
-            ident.source, program_.Symbols().Register(ident.value))));
+            ident.source, builder_.Symbols().Register(ident.value))));
   }
 
   return prefix;
@@ -2742,19 +2741,19 @@
 Maybe<ast::Literal*> ParserImpl::const_literal() {
   auto t = peek();
   if (match(Token::Type::kTrue)) {
-    auto* type = program_.create<type::Bool>();
+    auto* type = builder_.create<type::Bool>();
     return create<ast::BoolLiteral>(Source{}, type, true);
   }
   if (match(Token::Type::kFalse)) {
-    auto* type = program_.create<type::Bool>();
+    auto* type = builder_.create<type::Bool>();
     return create<ast::BoolLiteral>(Source{}, type, false);
   }
   if (match(Token::Type::kSintLiteral)) {
-    auto* type = program_.create<type::I32>();
+    auto* type = builder_.create<type::I32>();
     return create<ast::SintLiteral>(Source{}, type, t.to_i32());
   }
   if (match(Token::Type::kUintLiteral)) {
-    auto* type = program_.create<type::U32>();
+    auto* type = builder_.create<type::U32>();
     return create<ast::UintLiteral>(Source{}, type, t.to_u32());
   }
   if (match(Token::Type::kFloatLiteral)) {
@@ -2763,7 +2762,7 @@
       next();  // Consume 'f'
       add_error(p.source(), "float literals must not be suffixed with 'f'");
     }
-    auto* type = program_.create<type::F32>();
+    auto* type = builder_.create<type::F32>();
     return create<ast::FloatLiteral>(Source{}, type, t.to_f32());
   }
   return Failure::kNoMatch;
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index cada777..943b5e09 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -52,7 +52,7 @@
 #include "src/ast/variable_decoration.h"
 #include "src/diagnostic/diagnostic.h"
 #include "src/diagnostic/formatter.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/reader/wgsl/parser_impl_detail.h"
 #include "src/reader/wgsl/token.h"
 #include "src/type/storage_texture_type.h"
@@ -307,11 +307,12 @@
   /// @returns the diagnostic messages
   diag::List& diagnostics() { return diags_; }
 
-  /// @returns the program. The program in the parser will be reset after this.
-  Program program() { return std::move(program_); }
+  /// @returns the Program. The program builder in the parser will be reset
+  /// after this.
+  Program program() { return Program(std::move(builder_)); }
 
-  /// @returns a pointer to the module, without resetting it.
-  Program& get_program() { return program_; }
+  /// @returns the program builder.
+  ProgramBuilder& builder() { return builder_; }
 
   /// @returns the next token
   Token next();
@@ -829,7 +830,7 @@
   /// @returns the node pointer
   template <typename T, typename... ARGS>
   T* create(ARGS&&... args) {
-    return program_.create<T>(std::forward<ARGS>(args)...);
+    return builder_.create<T>(std::forward<ARGS>(args)...);
   }
 
   diag::List diags_;
@@ -839,7 +840,7 @@
   std::vector<Token::Type> sync_tokens_;
   int silence_errors_ = 0;
   std::unordered_map<std::string, type::Type*> registered_constructs_;
-  Program program_;
+  ProgramBuilder builder_;
   size_t max_errors_ = 25;
 };
 
diff --git a/src/reader/wgsl/parser_impl_additive_expression_test.cc b/src/reader/wgsl/parser_impl_additive_expression_test.cc
index e3b3e6a..9360c7e 100644
--- a/src/reader/wgsl/parser_impl_additive_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_additive_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -62,7 +62,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_and_expression_test.cc b/src/reader/wgsl/parser_impl_and_expression_test.cc
index 5f5b20a..5c0d2d0 100644
--- a/src/reader/wgsl/parser_impl_and_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_and_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Register("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
index 26ca7d0..c762153 100644
--- a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
@@ -42,7 +42,7 @@
 
   ASSERT_TRUE(e->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = e->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(e->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(e->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -77,7 +77,7 @@
 
   ASSERT_TRUE(mem->member()->Is<ast::IdentifierExpression>());
   auto* ident = mem->member()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("d"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("d"));
 
   ASSERT_TRUE(mem->structure()->Is<ast::ArrayAccessorExpression>());
   auto* ary = mem->structure()->As<ast::ArrayAccessorExpression>();
@@ -93,18 +93,18 @@
   mem = ary->array()->As<ast::MemberAccessorExpression>();
   ASSERT_TRUE(mem->member()->Is<ast::IdentifierExpression>());
   ident = mem->member()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("c"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("c"));
 
   ASSERT_TRUE(mem->structure()->Is<ast::MemberAccessorExpression>());
   mem = mem->structure()->As<ast::MemberAccessorExpression>();
 
   ASSERT_TRUE(mem->structure()->Is<ast::IdentifierExpression>());
   ident = mem->structure()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(mem->member()->Is<ast::IdentifierExpression>());
   ident = mem->member()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("b"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("b"));
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_MissingEqual) {
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc
index 9b2a445..07f5b1d 100644
--- a/src/reader/wgsl/parser_impl_call_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -38,7 +38,7 @@
 
   ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
   auto* ident = c->func()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   EXPECT_EQ(c->params().size(), 0u);
 }
@@ -56,7 +56,7 @@
 
   ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
   auto* ident = c->func()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   EXPECT_EQ(c->params().size(), 3u);
   EXPECT_TRUE(c->params()[0]->Is<ast::ConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_equality_expression_test.cc b/src/reader/wgsl/parser_impl_equality_expression_test.cc
index 9edab2a..693bef4 100644
--- a/src/reader/wgsl/parser_impl_equality_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_equality_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -62,7 +62,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
index 79d19bb..c28d214 100644
--- a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_function_decl_test.cc b/src/reader/wgsl/parser_impl_function_decl_test.cc
index ab76197..637a3ff 100644
--- a/src/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decl_test.cc
@@ -37,13 +37,13 @@
   EXPECT_TRUE(f.matched);
   ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->symbol(), p->get_program().Symbols().Register("main"));
+  EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
   EXPECT_TRUE(f->return_type()->Is<type::Void>());
 
   ASSERT_EQ(f->params().size(), 2u);
-  EXPECT_EQ(f->params()[0]->symbol(), p->get_program().Symbols().Register("a"));
-  EXPECT_EQ(f->params()[1]->symbol(), p->get_program().Symbols().Register("b"));
+  EXPECT_EQ(f->params()[0]->symbol(), p->builder().Symbols().Get("a"));
+  EXPECT_EQ(f->params()[1]->symbol(), p->builder().Symbols().Get("b"));
 
   ASSERT_NE(f->return_type(), nullptr);
   EXPECT_TRUE(f->return_type()->Is<type::Void>());
@@ -65,7 +65,7 @@
   EXPECT_TRUE(f.matched);
   ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->symbol(), p->get_program().Symbols().Register("main"));
+  EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
   EXPECT_TRUE(f->return_type()->Is<type::Void>());
   ASSERT_EQ(f->params().size(), 0u);
@@ -103,7 +103,7 @@
   EXPECT_TRUE(f.matched);
   ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->symbol(), p->get_program().Symbols().Register("main"));
+  EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
   EXPECT_TRUE(f->return_type()->Is<type::Void>());
   ASSERT_EQ(f->params().size(), 0u);
@@ -148,7 +148,7 @@
   EXPECT_TRUE(f.matched);
   ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->symbol(), p->get_program().Symbols().Register("main"));
+  EXPECT_EQ(f->symbol(), p->builder().Symbols().Get("main"));
   ASSERT_NE(f->return_type(), nullptr);
   EXPECT_TRUE(f->return_type()->Is<type::Void>());
   ASSERT_EQ(f->params().size(), 0u);
diff --git a/src/reader/wgsl/parser_impl_function_header_test.cc b/src/reader/wgsl/parser_impl_function_header_test.cc
index 9b8a813..01e9b92 100644
--- a/src/reader/wgsl/parser_impl_function_header_test.cc
+++ b/src/reader/wgsl/parser_impl_function_header_test.cc
@@ -33,8 +33,8 @@
 
   EXPECT_EQ(f->name, "main");
   ASSERT_EQ(f->params.size(), 2u);
-  EXPECT_EQ(f->params[0]->symbol(), p->get_program().Symbols().Register("a"));
-  EXPECT_EQ(f->params[1]->symbol(), p->get_program().Symbols().Register("b"));
+  EXPECT_EQ(f->params[0]->symbol(), p->builder().Symbols().Get("a"));
+  EXPECT_EQ(f->params[1]->symbol(), p->builder().Symbols().Get("b"));
   EXPECT_TRUE(f->return_type->Is<type::Void>());
 }
 
diff --git a/src/reader/wgsl/parser_impl_function_type_decl_test.cc b/src/reader/wgsl/parser_impl_function_type_decl_test.cc
index 57cc4b0..c080945 100644
--- a/src/reader/wgsl/parser_impl_function_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_type_decl_test.cc
@@ -29,8 +29,7 @@
 TEST_F(ParserImplTest, FunctionTypeDecl_Void) {
   auto p = parser("void");
 
-  auto& mod = p->get_program();
-  auto* v = mod.create<type::Void>();
+  auto* v = p->builder().create<type::Void>();
 
   auto e = p->function_type_decl();
   EXPECT_TRUE(e.matched);
@@ -42,9 +41,8 @@
 TEST_F(ParserImplTest, FunctionTypeDecl_Type) {
   auto p = parser("vec2<f32>");
 
-  auto& mod = p->get_program();
-  auto* f32 = mod.create<type::F32>();
-  auto* vec2 = mod.create<type::Vector>(f32, 2);
+  auto* f32 = p->builder().create<type::F32>();
+  auto* vec2 = p->builder().create<type::Vector>(f32, 2);
 
   auto e = p->function_type_decl();
   EXPECT_TRUE(e.matched);
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
index 991f3c7..ca2b7f4 100644
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -33,7 +33,7 @@
   ASSERT_NE(e.value, nullptr);
 
   EXPECT_TRUE(e->is_const());
-  EXPECT_EQ(e->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
   ASSERT_NE(e->type(), nullptr);
   EXPECT_TRUE(e->type()->Is<type::F32>());
 
diff --git a/src/reader/wgsl/parser_impl_global_decl_test.cc b/src/reader/wgsl/parser_impl_global_decl_test.cc
index 3e10ea3..22beb2b 100644
--- a/src/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_decl_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/module.h"
+#include "src/program.h"
 #include "src/reader/wgsl/parser_impl.h"
 #include "src/reader/wgsl/parser_impl_test_helper.h"
 #include "src/type/array_type.h"
@@ -34,11 +36,11 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().GlobalVariables().size(), 1u);
+  auto program = p->program();
+  ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
 
-  auto* v = m.AST().GlobalVariables()[0];
-  EXPECT_EQ(v->symbol(), p->get_program().Symbols().Register("a"));
+  auto* v = program.AST().GlobalVariables()[0];
+  EXPECT_EQ(v->symbol(), program.Symbols().Get("a"));
 }
 
 TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Invalid) {
@@ -60,11 +62,11 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().GlobalVariables().size(), 1u);
+  auto program = p->program();
+  ASSERT_EQ(program.AST().GlobalVariables().size(), 1u);
 
-  auto* v = m.AST().GlobalVariables()[0];
-  EXPECT_EQ(v->symbol(), p->get_program().Symbols().Register("a"));
+  auto* v = program.AST().GlobalVariables()[0];
+  EXPECT_EQ(v->symbol(), program.Symbols().Get("a"));
 }
 
 TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_Invalid) {
@@ -86,12 +88,13 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().ConstructedTypes().size(), 1u);
-  ASSERT_TRUE(m.AST().ConstructedTypes()[0]->Is<type::Alias>());
-  EXPECT_EQ(m.Symbols().NameFor(
-                m.AST().ConstructedTypes()[0]->As<type::Alias>()->symbol()),
-            "A");
+  auto program = p->program();
+  ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
+  ASSERT_TRUE(program.AST().ConstructedTypes()[0]->Is<type::Alias>());
+  EXPECT_EQ(
+      program.Symbols().NameFor(
+          program.AST().ConstructedTypes()[0]->As<type::Alias>()->symbol()),
+      "A");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_TypeAlias_StructIdent) {
@@ -103,15 +106,15 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().ConstructedTypes().size(), 2u);
-  ASSERT_TRUE(m.AST().ConstructedTypes()[0]->Is<type::Struct>());
-  auto* str = m.AST().ConstructedTypes()[0]->As<type::Struct>();
-  EXPECT_EQ(str->symbol(), p->get_program().Symbols().Register("A"));
+  auto program = p->program();
+  ASSERT_EQ(program.AST().ConstructedTypes().size(), 2u);
+  ASSERT_TRUE(program.AST().ConstructedTypes()[0]->Is<type::Struct>());
+  auto* str = program.AST().ConstructedTypes()[0]->As<type::Struct>();
+  EXPECT_EQ(str->symbol(), program.Symbols().Get("A"));
 
-  ASSERT_TRUE(m.AST().ConstructedTypes()[1]->Is<type::Alias>());
-  auto* alias = m.AST().ConstructedTypes()[1]->As<type::Alias>();
-  EXPECT_EQ(alias->symbol(), p->get_program().Symbols().Register("B"));
+  ASSERT_TRUE(program.AST().ConstructedTypes()[1]->Is<type::Alias>());
+  auto* alias = program.AST().ConstructedTypes()[1]->As<type::Alias>();
+  EXPECT_EQ(alias->symbol(), program.Symbols().Get("B"));
   EXPECT_EQ(alias->type(), str);
 }
 
@@ -134,9 +137,10 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().Functions().size(), 1u);
-  EXPECT_EQ(m.Symbols().NameFor(m.AST().Functions()[0]->symbol()), "main");
+  auto program = p->program();
+  ASSERT_EQ(program.AST().Functions().size(), 1u);
+  EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol()),
+            "main");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Function_WithDecoration) {
@@ -144,9 +148,10 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().Functions().size(), 1u);
-  EXPECT_EQ(m.Symbols().NameFor(m.AST().Functions()[0]->symbol()), "main");
+  auto program = p->program();
+  ASSERT_EQ(program.AST().Functions().size(), 1u);
+  EXPECT_EQ(program.Symbols().NameFor(program.AST().Functions()[0]->symbol()),
+            "main");
 }
 
 TEST_F(ParserImplTest, GlobalDecl_Function_Invalid) {
@@ -161,15 +166,15 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().ConstructedTypes().size(), 1u);
+  auto program = p->program();
+  ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
 
-  auto* t = m.AST().ConstructedTypes()[0];
+  auto* t = program.AST().ConstructedTypes()[0];
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->Is<type::Struct>());
 
   auto* str = t->As<type::Struct>();
-  EXPECT_EQ(str->symbol(), p->get_program().Symbols().Register("A"));
+  EXPECT_EQ(str->symbol(), program.Symbols().Get("A"));
   EXPECT_EQ(str->impl()->members().size(), 2u);
 }
 
@@ -180,15 +185,15 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().ConstructedTypes().size(), 1u);
+  auto program = p->program();
+  ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
 
-  auto* t = m.AST().ConstructedTypes()[0];
+  auto* t = program.AST().ConstructedTypes()[0];
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->Is<type::Struct>());
 
   auto* str = t->As<type::Struct>();
-  EXPECT_EQ(str->symbol(), p->get_program().Symbols().Register("A"));
+  EXPECT_EQ(str->symbol(), program.Symbols().Get("A"));
   EXPECT_EQ(str->impl()->members().size(), 1u);
   EXPECT_FALSE(str->IsBlockDecorated());
 
@@ -204,15 +209,15 @@
   p->expect_global_decl();
   ASSERT_FALSE(p->has_error()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(m.AST().ConstructedTypes().size(), 1u);
+  auto program = p->program();
+  ASSERT_EQ(program.AST().ConstructedTypes().size(), 1u);
 
-  auto* t = m.AST().ConstructedTypes()[0];
+  auto* t = program.AST().ConstructedTypes()[0];
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->Is<type::Struct>());
 
   auto* str = t->As<type::Struct>();
-  EXPECT_EQ(str->symbol(), p->get_program().Symbols().Register("A"));
+  EXPECT_EQ(str->symbol(), program.Symbols().Get("A"));
   EXPECT_EQ(str->impl()->members().size(), 1u);
   EXPECT_TRUE(str->IsBlockDecorated());
 }
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
index f6929e3..9c83e42 100644
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -36,7 +36,7 @@
   EXPECT_FALSE(e.errored);
   ASSERT_NE(e.value, nullptr);
 
-  EXPECT_EQ(e->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
   EXPECT_TRUE(e->type()->Is<type::F32>());
   EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
 
@@ -59,7 +59,7 @@
   EXPECT_FALSE(e.errored);
   ASSERT_NE(e.value, nullptr);
 
-  EXPECT_EQ(e->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
   EXPECT_TRUE(e->type()->Is<type::F32>());
   EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
 
@@ -84,7 +84,7 @@
   EXPECT_FALSE(e.errored);
   ASSERT_NE(e.value, nullptr);
 
-  EXPECT_EQ(e->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
   ASSERT_NE(e->type(), nullptr);
   EXPECT_TRUE(e->type()->Is<type::F32>());
   EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
@@ -114,7 +114,7 @@
   EXPECT_FALSE(e.errored);
   ASSERT_NE(e.value, nullptr);
 
-  EXPECT_EQ(e->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
   ASSERT_NE(e->type(), nullptr);
   EXPECT_TRUE(e->type()->Is<type::F32>());
   EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
diff --git a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
index 9a23a38..6150645 100644
--- a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
index 2a91303..bec0731 100644
--- a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
index 63e8a77..5c8e534 100644
--- a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
index d989caa..a6887e6 100644
--- a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -62,7 +62,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -85,7 +85,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_param_list_test.cc b/src/reader/wgsl/parser_impl_param_list_test.cc
index 5b65e69..cbe3753 100644
--- a/src/reader/wgsl/parser_impl_param_list_test.cc
+++ b/src/reader/wgsl/parser_impl_param_list_test.cc
@@ -30,15 +30,14 @@
 TEST_F(ParserImplTest, ParamList_Single) {
   auto p = parser("a : i32");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
+  auto* i32 = p->builder().create<type::I32>();
 
   auto e = p->expect_param_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   EXPECT_EQ(e.value.size(), 1u);
 
-  EXPECT_EQ(e.value[0]->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e.value[0]->symbol(), p->builder().Symbols().Get("a"));
   EXPECT_EQ(e.value[0]->type(), i32);
   EXPECT_TRUE(e.value[0]->is_const());
 
@@ -51,17 +50,16 @@
 TEST_F(ParserImplTest, ParamList_Multiple) {
   auto p = parser("a : i32, b: f32, c: vec2<f32>");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
-  auto* f32 = mod.create<type::F32>();
-  auto* vec2 = mod.create<type::Vector>(f32, 2);
+  auto* i32 = p->builder().create<type::I32>();
+  auto* f32 = p->builder().create<type::F32>();
+  auto* vec2 = p->builder().create<type::Vector>(f32, 2);
 
   auto e = p->expect_param_list();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   EXPECT_EQ(e.value.size(), 3u);
 
-  EXPECT_EQ(e.value[0]->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e.value[0]->symbol(), p->builder().Symbols().Get("a"));
   EXPECT_EQ(e.value[0]->type(), i32);
   EXPECT_TRUE(e.value[0]->is_const());
 
@@ -70,7 +68,7 @@
   ASSERT_EQ(e.value[0]->source().range.end.line, 1u);
   ASSERT_EQ(e.value[0]->source().range.end.column, 2u);
 
-  EXPECT_EQ(e.value[1]->symbol(), p->get_program().Symbols().Register("b"));
+  EXPECT_EQ(e.value[1]->symbol(), p->builder().Symbols().Get("b"));
   EXPECT_EQ(e.value[1]->type(), f32);
   EXPECT_TRUE(e.value[1]->is_const());
 
@@ -79,7 +77,7 @@
   ASSERT_EQ(e.value[1]->source().range.end.line, 1u);
   ASSERT_EQ(e.value[1]->source().range.end.column, 11u);
 
-  EXPECT_EQ(e.value[2]->symbol(), p->get_program().Symbols().Register("c"));
+  EXPECT_EQ(e.value[2]->symbol(), p->builder().Symbols().Get("c"));
   EXPECT_EQ(e.value[2]->type(), vec2);
   EXPECT_TRUE(e.value[2]->is_const());
 
diff --git a/src/reader/wgsl/parser_impl_postfix_expression_test.cc b/src/reader/wgsl/parser_impl_postfix_expression_test.cc
index fc88cbe..43ed375 100644
--- a/src/reader/wgsl/parser_impl_postfix_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_postfix_expression_test.cc
@@ -42,7 +42,7 @@
 
   ASSERT_TRUE(ary->array()->Is<ast::IdentifierExpression>());
   auto* ident = ary->array()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
@@ -64,7 +64,7 @@
 
   ASSERT_TRUE(ary->array()->Is<ast::IdentifierExpression>());
   auto* ident = ary->array()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(ary->idx_expr()->Is<ast::BinaryExpression>());
 }
@@ -112,7 +112,7 @@
 
   ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
   auto* func = c->func()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(func->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(func->symbol(), p->builder().Symbols().Get("a"));
 
   EXPECT_EQ(c->params().size(), 0u);
 }
@@ -130,7 +130,7 @@
 
   ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
   auto* func = c->func()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(func->symbol(), p->get_program().Symbols().Register("test"));
+  EXPECT_EQ(func->symbol(), p->builder().Symbols().Get("test"));
 
   EXPECT_EQ(c->params().size(), 3u);
   EXPECT_TRUE(c->params()[0]->Is<ast::ConstructorExpression>());
@@ -180,11 +180,11 @@
   auto* m = e->As<ast::MemberAccessorExpression>();
   ASSERT_TRUE(m->structure()->Is<ast::IdentifierExpression>());
   EXPECT_EQ(m->structure()->As<ast::IdentifierExpression>()->symbol(),
-            p->get_program().Symbols().Register("a"));
+            p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(m->member()->Is<ast::IdentifierExpression>());
   EXPECT_EQ(m->member()->As<ast::IdentifierExpression>()->symbol(),
-            p->get_program().Symbols().Register("b"));
+            p->builder().Symbols().Get("b"));
 }
 
 TEST_F(ParserImplTest, PostfixExpression_MemberAccesssor_InvalidIdent) {
diff --git a/src/reader/wgsl/parser_impl_primary_expression_test.cc b/src/reader/wgsl/parser_impl_primary_expression_test.cc
index 9dd8b6e..0e74e7f 100644
--- a/src/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -41,7 +41,7 @@
   ASSERT_NE(e.value, nullptr);
   ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
   auto* ident = e->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl) {
@@ -193,8 +193,7 @@
 TEST_F(ParserImplTest, PrimaryExpression_Cast) {
   auto p = parser("f32(1)");
 
-  auto& mod = p->get_program();
-  auto* f32 = mod.create<type::F32>();
+  auto* f32 = p->builder().create<type::F32>();
 
   auto e = p->primary_expression();
   EXPECT_TRUE(e.matched);
@@ -215,8 +214,7 @@
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast) {
   auto p = parser("bitcast<f32>(1)");
 
-  auto& mod = p->get_program();
-  auto* f32 = mod.create<type::F32>();
+  auto* f32 = p->builder().create<type::F32>();
 
   auto e = p->primary_expression();
   EXPECT_TRUE(e.matched);
diff --git a/src/reader/wgsl/parser_impl_relational_expression_test.cc b/src/reader/wgsl/parser_impl_relational_expression_test.cc
index 1f9f0db..77ed912 100644
--- a/src/reader/wgsl/parser_impl_relational_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_relational_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Register("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -62,7 +62,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Register("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -85,7 +85,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Register("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -108,7 +108,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Register("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_shift_expression_test.cc b/src/reader/wgsl/parser_impl_shift_expression_test.cc
index e7fe8ea..0be6486 100644
--- a/src/reader/wgsl/parser_impl_shift_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_shift_expression_test.cc
@@ -39,7 +39,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
@@ -62,7 +62,7 @@
 
   ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
   auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
index c89efcc..7693fd9 100644
--- a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -25,8 +25,8 @@
 TEST_F(ParserImplTest, StructBodyDecl_Parses) {
   auto p = parser("{a : i32;}");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
+  auto& builder = p->builder();
+  auto* i32 = builder.create<type::I32>();
 
   auto m = p->expect_struct_body_decl();
   ASSERT_FALSE(p->has_error());
@@ -34,7 +34,7 @@
   ASSERT_EQ(m.value.size(), 1u);
 
   const auto* mem = m.value[0];
-  EXPECT_EQ(mem->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(mem->symbol(), builder.Symbols().Get("a"));
   EXPECT_EQ(mem->type(), i32);
   EXPECT_EQ(mem->decorations().size(), 0u);
 }
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index 879d0b6..219bb3b 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -39,12 +39,12 @@
   EXPECT_FALSE(s.errored);
   EXPECT_TRUE(s.matched);
   ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->symbol(), p->get_program().Symbols().Register("S"));
+  ASSERT_EQ(s->symbol(), p->builder().Symbols().Register("S"));
   ASSERT_EQ(s->impl()->members().size(), 2u);
   EXPECT_EQ(s->impl()->members()[0]->symbol(),
-            p->get_program().Symbols().Register("a"));
+            p->builder().Symbols().Register("a"));
   EXPECT_EQ(s->impl()->members()[1]->symbol(),
-            p->get_program().Symbols().Register("b"));
+            p->builder().Symbols().Register("b"));
 }
 
 TEST_F(ParserImplTest, StructDecl_ParsesWithDecoration) {
@@ -63,12 +63,12 @@
   EXPECT_FALSE(s.errored);
   EXPECT_TRUE(s.matched);
   ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->symbol(), p->get_program().Symbols().Register("B"));
+  ASSERT_EQ(s->symbol(), p->builder().Symbols().Register("B"));
   ASSERT_EQ(s->impl()->members().size(), 2u);
   EXPECT_EQ(s->impl()->members()[0]->symbol(),
-            p->get_program().Symbols().Register("a"));
+            p->builder().Symbols().Register("a"));
   EXPECT_EQ(s->impl()->members()[1]->symbol(),
-            p->get_program().Symbols().Register("b"));
+            p->builder().Symbols().Register("b"));
   ASSERT_EQ(s->impl()->decorations().size(), 1u);
   EXPECT_TRUE(s->impl()->decorations()[0]->Is<ast::StructBlockDecoration>());
 }
@@ -90,12 +90,12 @@
   EXPECT_FALSE(s.errored);
   EXPECT_TRUE(s.matched);
   ASSERT_NE(s.value, nullptr);
-  ASSERT_EQ(s->symbol(), p->get_program().Symbols().Register("S"));
+  ASSERT_EQ(s->symbol(), p->builder().Symbols().Register("S"));
   ASSERT_EQ(s->impl()->members().size(), 2u);
   EXPECT_EQ(s->impl()->members()[0]->symbol(),
-            p->get_program().Symbols().Register("a"));
+            p->builder().Symbols().Register("a"));
   EXPECT_EQ(s->impl()->members()[1]->symbol(),
-            p->get_program().Symbols().Register("b"));
+            p->builder().Symbols().Register("b"));
   ASSERT_EQ(s->impl()->decorations().size(), 2u);
   EXPECT_TRUE(s->impl()->decorations()[0]->Is<ast::StructBlockDecoration>());
   EXPECT_TRUE(s->impl()->decorations()[1]->Is<ast::StructBlockDecoration>());
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index 4cbab37..e8fd7ea 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -26,8 +26,8 @@
 TEST_F(ParserImplTest, StructMember_Parses) {
   auto p = parser("a : i32;");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
+  auto& builder = p->builder();
+  auto* i32 = builder.create<type::I32>();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -39,7 +39,7 @@
   ASSERT_FALSE(m.errored);
   ASSERT_NE(m.value, nullptr);
 
-  EXPECT_EQ(m->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
   EXPECT_EQ(m->type(), i32);
   EXPECT_EQ(m->decorations().size(), 0u);
 
@@ -52,8 +52,8 @@
 TEST_F(ParserImplTest, StructMember_ParsesWithDecoration) {
   auto p = parser("[[offset(2)]] a : i32;");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
+  auto& builder = p->builder();
+  auto* i32 = builder.create<type::I32>();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -65,7 +65,7 @@
   ASSERT_FALSE(m.errored);
   ASSERT_NE(m.value, nullptr);
 
-  EXPECT_EQ(m->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
   EXPECT_EQ(m->type(), i32);
   EXPECT_EQ(m->decorations().size(), 1u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberOffsetDecoration>());
@@ -83,8 +83,8 @@
   auto p = parser(R"([[offset(2)]]
 [[offset(4)]] a : i32;)");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
+  auto& builder = p->builder();
+  auto* i32 = builder.create<type::I32>();
 
   auto decos = p->decoration_list();
   EXPECT_FALSE(decos.errored);
@@ -96,7 +96,7 @@
   ASSERT_FALSE(m.errored);
   ASSERT_NE(m.value, nullptr);
 
-  EXPECT_EQ(m->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(m->symbol(), builder.Symbols().Get("a"));
   EXPECT_EQ(m->type(), i32);
   EXPECT_EQ(m->decorations().size(), 2u);
   EXPECT_TRUE(m->decorations()[0]->Is<ast::StructMemberOffsetDecoration>());
diff --git a/src/reader/wgsl/parser_impl_test.cc b/src/reader/wgsl/parser_impl_test.cc
index c4aede6..ff0b0f5 100644
--- a/src/reader/wgsl/parser_impl_test.cc
+++ b/src/reader/wgsl/parser_impl_test.cc
@@ -39,9 +39,9 @@
 )");
   ASSERT_TRUE(p->Parse()) << p->error();
 
-  auto& m = p->get_program();
-  ASSERT_EQ(1u, m.AST().Functions().size());
-  ASSERT_EQ(1u, m.AST().GlobalVariables().size());
+  Program program = p->program();
+  ASSERT_EQ(1u, program.AST().Functions().size());
+  ASSERT_EQ(1u, program.AST().GlobalVariables().size());
 }
 
 TEST_F(ParserImplTest, HandlesError) {
diff --git a/src/reader/wgsl/parser_impl_test_helper.h b/src/reader/wgsl/parser_impl_test_helper.h
index 7a7efa7..b61709e 100644
--- a/src/reader/wgsl/parser_impl_test_helper.h
+++ b/src/reader/wgsl/parser_impl_test_helper.h
@@ -21,7 +21,7 @@
 #include <vector>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
+#include "src/program_builder.h"
 #include "src/reader/wgsl/parser_impl.h"
 
 namespace tint {
@@ -29,7 +29,7 @@
 namespace wgsl {
 
 /// WGSL Parser test class
-class ParserImplTest : public testing::Test, public ast::BuilderWithProgram {
+class ParserImplTest : public testing::Test, public ProgramBuilder {
  public:
   /// Constructor
   ParserImplTest();
@@ -52,7 +52,7 @@
 /// WGSL Parser test class with param
 template <typename T>
 class ParserImplTestWithParam : public testing::TestWithParam<T>,
-                                public ast::BuilderWithProgram {
+                                public ProgramBuilder {
  public:
   /// Constructor
   ParserImplTestWithParam() = default;
diff --git a/src/reader/wgsl/parser_impl_type_alias_test.cc b/src/reader/wgsl/parser_impl_type_alias_test.cc
index a3e877b..2610eac 100644
--- a/src/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/reader/wgsl/parser_impl_type_alias_test.cc
@@ -28,8 +28,7 @@
 TEST_F(ParserImplTest, TypeDecl_ParsesType) {
   auto p = parser("type a = i32");
 
-  auto& mod = p->get_program();
-  auto* i32 = mod.create<type::I32>();
+  auto* i32 = p->builder().create<type::I32>();
 
   auto t = p->type_alias();
   EXPECT_FALSE(p->has_error());
@@ -45,7 +44,7 @@
 TEST_F(ParserImplTest, TypeDecl_ParsesStruct_Ident) {
   auto p = parser("type a = B");
 
-  type::Struct str(p->get_program().Symbols().Register("B"), {});
+  type::Struct str(p->builder().Symbols().Get("B"), {});
   p->register_constructed("B", &str);
 
   auto t = p->type_alias();
@@ -55,12 +54,12 @@
   ASSERT_NE(t.value, nullptr);
   ASSERT_TRUE(t->Is<type::Alias>());
   auto* alias = t->As<type::Alias>();
-  EXPECT_EQ(p->get_program().Symbols().NameFor(alias->symbol()), "a");
+  EXPECT_EQ(p->builder().Symbols().NameFor(alias->symbol()), "a");
   ASSERT_TRUE(alias->type()->Is<type::Struct>());
 
   auto* s = alias->type()->As<type::Struct>();
-  EXPECT_EQ(s->symbol(), p->get_program().Symbols().Register("B"));
-  EXPECT_EQ(s->symbol(), p->get_program().Symbols().Register("B"));
+  EXPECT_EQ(s->symbol(), p->builder().Symbols().Get("B"));
+  EXPECT_EQ(s->symbol(), p->builder().Symbols().Get("B"));
 }
 
 TEST_F(ParserImplTest, TypeDecl_MissingIdent) {
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index 4c2b3e0..78cf5c1 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -46,11 +46,11 @@
 TEST_F(ParserImplTest, TypeDecl_Identifier) {
   auto p = parser("A");
 
-  auto& mod = p->get_program();
+  auto& builder = p->builder();
 
-  auto* int_type = mod.create<type::I32>();
+  auto* int_type = builder.create<type::I32>();
   auto* alias_type =
-      mod.create<type::Alias>(mod.Symbols().Register("A"), int_type);
+      builder.create<type::Alias>(builder.Symbols().Register("A"), int_type);
 
   p->register_constructed("A", alias_type);
 
@@ -62,7 +62,7 @@
   ASSERT_TRUE(t->Is<type::Alias>());
 
   auto* alias = t->As<type::Alias>();
-  EXPECT_EQ(p->get_program().Symbols().NameFor(alias->symbol()), "A");
+  EXPECT_EQ(p->builder().Symbols().NameFor(alias->symbol()), "A");
   EXPECT_EQ(alias->type(), int_type);
 }
 
@@ -80,8 +80,8 @@
 TEST_F(ParserImplTest, TypeDecl_Bool) {
   auto p = parser("bool");
 
-  auto& mod = p->get_program();
-  auto* bool_type = mod.create<type::Bool>();
+  auto& builder = p->builder();
+  auto* bool_type = builder.create<type::Bool>();
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
@@ -94,8 +94,8 @@
 TEST_F(ParserImplTest, TypeDecl_F32) {
   auto p = parser("f32");
 
-  auto& mod = p->get_program();
-  auto* float_type = mod.create<type::F32>();
+  auto& builder = p->builder();
+  auto* float_type = builder.create<type::F32>();
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
@@ -108,8 +108,8 @@
 TEST_F(ParserImplTest, TypeDecl_I32) {
   auto p = parser("i32");
 
-  auto& mod = p->get_program();
-  auto* int_type = mod.create<type::I32>();
+  auto& builder = p->builder();
+  auto* int_type = builder.create<type::I32>();
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
@@ -122,8 +122,8 @@
 TEST_F(ParserImplTest, TypeDecl_U32) {
   auto p = parser("u32");
 
-  auto& mod = p->get_program();
-  auto* uint_type = mod.create<type::U32>();
+  auto& builder = p->builder();
+  auto* uint_type = builder.create<type::U32>();
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
@@ -739,8 +739,8 @@
 TEST_F(ParserImplTest, TypeDecl_Sampler) {
   auto p = parser("sampler");
 
-  auto& mod = p->get_program();
-  auto* type = mod.create<type::Sampler>(type::SamplerKind::kSampler);
+  auto& builder = p->builder();
+  auto* type = builder.create<type::Sampler>(type::SamplerKind::kSampler);
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
@@ -754,9 +754,9 @@
 TEST_F(ParserImplTest, TypeDecl_Texture_Old) {
   auto p = parser("texture_sampled_cube<f32>");
 
-  auto& mod = p->get_program();
-  auto* type =
-      mod.create<type::SampledTexture>(type::TextureDimension::kCube, ty.f32());
+  auto& builder = p->builder();
+  auto* type = builder.create<type::SampledTexture>(
+      type::TextureDimension::kCube, ty.f32());
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
@@ -771,9 +771,9 @@
 TEST_F(ParserImplTest, TypeDecl_Texture) {
   auto p = parser("texture_cube<f32>");
 
-  auto& mod = p->get_program();
-  auto* type =
-      mod.create<type::SampledTexture>(type::TextureDimension::kCube, ty.f32());
+  auto& builder = p->builder();
+  auto* type = builder.create<type::SampledTexture>(
+      type::TextureDimension::kCube, ty.f32());
 
   auto t = p->type_decl();
   EXPECT_TRUE(t.matched);
diff --git a/src/reader/wgsl/parser_impl_unary_expression_test.cc b/src/reader/wgsl/parser_impl_unary_expression_test.cc
index 64e0288..51561e5 100644
--- a/src/reader/wgsl/parser_impl_unary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_unary_expression_test.cc
@@ -38,7 +38,7 @@
   auto* ary = e->As<ast::ArrayAccessorExpression>();
   ASSERT_TRUE(ary->array()->Is<ast::IdentifierExpression>());
   auto* ident = ary->array()->As<ast::IdentifierExpression>();
-  EXPECT_EQ(ident->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(ident->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
   ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
diff --git a/src/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
index 33ad0ae..3560b5f 100644
--- a/src/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -32,7 +32,7 @@
   ASSERT_NE(e.value, nullptr);
   ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
   ASSERT_NE(e->variable(), nullptr);
-  EXPECT_EQ(e->variable()->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->variable()->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_EQ(e->source().range.begin.line, 1u);
   ASSERT_EQ(e->source().range.begin.column, 5u);
@@ -51,7 +51,7 @@
   ASSERT_NE(e.value, nullptr);
   ASSERT_TRUE(e->Is<ast::VariableDeclStatement>());
   ASSERT_NE(e->variable(), nullptr);
-  EXPECT_EQ(e->variable()->symbol(), p->get_program().Symbols().Register("a"));
+  EXPECT_EQ(e->variable()->symbol(), p->builder().Symbols().Get("a"));
 
   ASSERT_EQ(e->source().range.begin.line, 1u);
   ASSERT_EQ(e->source().range.begin.column, 5u);
diff --git a/src/reader/wgsl/parser_test.cc b/src/reader/wgsl/parser_test.cc
index c31ac86..4925cfd 100644
--- a/src/reader/wgsl/parser_test.cc
+++ b/src/reader/wgsl/parser_test.cc
@@ -16,6 +16,8 @@
 
 #include "gtest/gtest.h"
 
+#include "src/ast/module.h"
+
 namespace tint {
 namespace reader {
 namespace wgsl {
@@ -41,9 +43,9 @@
   Parser p(&file);
   ASSERT_TRUE(p.Parse()) << p.error();
 
-  auto m = p.program();
-  ASSERT_EQ(1u, m.AST().Functions().size());
-  ASSERT_EQ(1u, m.AST().GlobalVariables().size());
+  auto program = p.program();
+  ASSERT_EQ(1u, program.AST().Functions().size());
+  ASSERT_EQ(1u, program.AST().GlobalVariables().size());
 }
 
 TEST_F(ParserTest, HandlesError) {
diff --git a/src/scope_stack_test.cc b/src/scope_stack_test.cc
index 9103896..5e9febf 100644
--- a/src/scope_stack_test.cc
+++ b/src/scope_stack_test.cc
@@ -14,15 +14,15 @@
 #include "src/scope_stack.h"
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
 #include "src/ast/variable.h"
+#include "src/program_builder.h"
 #include "src/symbol.h"
 #include "src/type/f32_type.h"
 
 namespace tint {
 namespace {
 
-class ScopeStackTest : public ast::BuilderWithProgram, public testing::Test {};
+class ScopeStackTest : public ProgramBuilder, public testing::Test {};
 
 TEST_F(ScopeStackTest, Global) {
   ScopeStack<uint32_t> s;
diff --git a/src/traits.h b/src/traits.h
index 3601cbc..d28e975 100644
--- a/src/traits.h
+++ b/src/traits.h
@@ -64,11 +64,23 @@
 template <typename F, int N>
 using ParamTypeT = typename ParamType<F, N>::type;
 
+/// `IsTypeOrDerived<T, BASE>::value` is true iff `T` is of type `BASE`, or
+/// derives from `BASE`.
+template <typename T, typename BASE>
+using IsTypeOrDerived = std::integral_constant<
+    bool,
+    std::is_base_of<BASE, typename std::decay<T>::type>::value ||
+        std::is_same<BASE, typename std::decay<T>::type>::value>;
+
+/// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
+/// invalid type.
+template <bool CONDITION, typename T>
+using EnableIf = typename std::enable_if<CONDITION, T>::type;
+
 /// If T is a base of BASE then EnableIfIsType resolves to type T, otherwise an
 /// invalid type.
 template <typename T, typename BASE>
-using EnableIfIsType =
-    typename std::enable_if<std::is_base_of<BASE, T>::value, T>::type;
+using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>::value, T>;
 
 }  // namespace traits
 }  // namespace tint
diff --git a/src/transform/bound_array_accessors.cc b/src/transform/bound_array_accessors.cc
index 271c575..4253fa2 100644
--- a/src/transform/bound_array_accessors.cc
+++ b/src/transform/bound_array_accessors.cc
@@ -23,7 +23,6 @@
 #include "src/ast/bitcast_expression.h"
 #include "src/ast/block_statement.h"
 #include "src/ast/break_statement.h"
-#include "src/ast/builder.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/case_statement.h"
@@ -44,6 +43,7 @@
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/clone_context.h"
+#include "src/program_builder.h"
 #include "src/type/array_type.h"
 #include "src/type/matrix_type.h"
 #include "src/type/u32_type.h"
@@ -56,13 +56,14 @@
 BoundArrayAccessors::~BoundArrayAccessors() = default;
 
 Transform::Output BoundArrayAccessors::Run(const Program* in) {
-  Output out;
-  CloneContext(&out.program, in)
+  ProgramBuilder out;
+  diag::List diagnostics;
+  CloneContext(&out, in)
       .ReplaceAll([&](CloneContext* ctx, ast::ArrayAccessorExpression* expr) {
-        return Transform(expr, ctx, &out.diagnostics);
+        return Transform(expr, ctx, &diagnostics);
       })
       .Clone();
-  return out;
+  return Output(Program(std::move(out)), std::move(diagnostics));
 }
 
 ast::ArrayAccessorExpression* BoundArrayAccessors::Transform(
@@ -75,8 +76,8 @@
     return nullptr;
   }
 
-  ast::Builder b(ctx->dst);
-  using u32 = ast::Builder::u32;
+  ProgramBuilder& b = *ctx->dst;
+  using u32 = ProgramBuilder::u32;
 
   uint32_t size = 0;
   bool is_vec = ret_type->Is<type::Vector>();
diff --git a/src/transform/emit_vertex_point_size.cc b/src/transform/emit_vertex_point_size.cc
index bbc8f95..67267e0 100644
--- a/src/transform/emit_vertex_point_size.cc
+++ b/src/transform/emit_vertex_point_size.cc
@@ -24,6 +24,8 @@
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/variable.h"
 #include "src/clone_context.h"
+#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/f32_type.h"
 #include "src/type/type_manager.h"
 
@@ -39,41 +41,39 @@
 EmitVertexPointSize::~EmitVertexPointSize() = default;
 
 Transform::Output EmitVertexPointSize::Run(const Program* in) {
-  Output out;
-
   if (!in->AST().Functions().HasStage(ast::PipelineStage::kVertex)) {
     // If the module doesn't have any vertex stages, then there's nothing to do.
-    out.program = in->Clone();
-    return out;
+    return Output(Program(in->Clone()));
   }
 
-  auto* f32 = out.program.create<type::F32>();
+  ProgramBuilder out;
+  auto* f32 = out.create<type::F32>();
 
   // Declare the pointsize builtin output variable.
-  auto* pointsize_var = out.program.create<ast::Variable>(
-      Source{},                                       // source
-      out.program.Symbols().Register(kPointSizeVar),  // symbol
-      ast::StorageClass::kOutput,                     // storage_class
-      f32,                                            // type
-      false,                                          // is_const
-      nullptr,                                        // constructor
+  auto* pointsize_var = out.create<ast::Variable>(
+      Source{},                               // source
+      out.Symbols().Register(kPointSizeVar),  // symbol
+      ast::StorageClass::kOutput,             // storage_class
+      f32,                                    // type
+      false,                                  // is_const
+      nullptr,                                // constructor
       ast::VariableDecorationList{
           // decorations
-          out.program.create<ast::BuiltinDecoration>(Source{},
-                                                     ast::Builtin::kPointSize),
+          out.create<ast::BuiltinDecoration>(Source{},
+                                             ast::Builtin::kPointSize),
       });
-  out.program.AST().AddGlobalVariable(pointsize_var);
+  out.AST().AddGlobalVariable(pointsize_var);
 
   // Build the AST expression & statement for assigning pointsize one.
-  auto* one = out.program.create<ast::ScalarConstructorExpression>(
-      Source{}, out.program.create<ast::FloatLiteral>(Source{}, f32, 1.0f));
-  auto* pointsize_ident = out.program.create<ast::IdentifierExpression>(
-      Source{}, out.program.Symbols().Register(kPointSizeVar));
-  auto* pointsize_assign = out.program.create<ast::AssignmentStatement>(
-      Source{}, pointsize_ident, one);
+  auto* one = out.create<ast::ScalarConstructorExpression>(
+      Source{}, out.create<ast::FloatLiteral>(Source{}, f32, 1.0f));
+  auto* pointsize_ident = out.create<ast::IdentifierExpression>(
+      Source{}, out.Symbols().Register(kPointSizeVar));
+  auto* pointsize_assign =
+      out.create<ast::AssignmentStatement>(Source{}, pointsize_ident, one);
 
   // Add the pointsize assignment statement to the front of all vertex stages.
-  CloneContext(&out.program, in)
+  CloneContext(&out, in)
       .ReplaceAll(
           [&](CloneContext* ctx, ast::Function* func) -> ast::Function* {
             if (func->pipeline_stage() != ast::PipelineStage::kVertex) {
@@ -83,7 +83,7 @@
           })
       .Clone();
 
-  return out;
+  return Output(Program(std::move(out)));
 }
 
 }  // namespace transform
diff --git a/src/transform/first_index_offset.cc b/src/transform/first_index_offset.cc
index d3137f4..d9790b0 100644
--- a/src/transform/first_index_offset.cc
+++ b/src/transform/first_index_offset.cc
@@ -47,6 +47,8 @@
 #include "src/ast/variable_decl_statement.h"
 #include "src/ast/variable_decoration.h"
 #include "src/clone_context.h"
+#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/struct_type.h"
 #include "src/type/u32_type.h"
 #include "src/type_determiner.h"
@@ -99,15 +101,20 @@
   // Running TypeDeterminer as we require local_referenced_builtin_variables()
   // to be populated. TODO(bclayton) - it should not be necessary to re-run the
   // type determiner if semantic information is already generated. Remove.
-  // TODO(https://crbug.com/tint/390): Remove this const_cast hack!
-  TypeDeterminer td(const_cast<Program*>(in));
-  if (!td.Determine()) {
-    diag::Diagnostic err;
-    err.severity = diag::Severity::Error;
-    err.message = td.error();
-    Output out;
-    out.diagnostics.add(std::move(err));
-    return out;
+  Program program;
+  {
+    ProgramBuilder builder = in->CloneAsBuilder();
+    TypeDeterminer td(&builder);
+    if (!td.Determine()) {
+      diag::Diagnostic err;
+      err.severity = diag::Severity::Error;
+      err.message = td.error();
+      Output out;
+      out.diagnostics.add(std::move(err));
+      return out;
+    }
+    program = Program(std::move(builder));
+    in = &program;
   }
 
   Symbol vertex_index_sym;
@@ -116,9 +123,9 @@
   // Lazilly construct the UniformBuffer on first call to
   // maybe_create_buffer_var()
   ast::Variable* buffer_var = nullptr;
-  auto maybe_create_buffer_var = [&](Program* program) {
+  auto maybe_create_buffer_var = [&](ProgramBuilder* dst) {
     if (buffer_var == nullptr) {
-      buffer_var = AddUniformBuffer(program);
+      buffer_var = AddUniformBuffer(dst);
     }
   };
 
@@ -126,8 +133,8 @@
   // add a CreateFirstIndexOffset() statement to each function that uses one of
   // these builtins.
 
-  Output out;
-  CloneContext(&out.program, in)
+  ProgramBuilder out;
+  CloneContext(&out, in)
       .ReplaceAll([&](CloneContext* ctx, ast::Variable* var) -> ast::Variable* {
         for (ast::VariableDecoration* dec : var->decorations()) {
           if (auto* blt_dec = dec->As<ast::BuiltinDecoration>()) {
@@ -174,7 +181,7 @@
           })
       .Clone();
 
-  return out;
+  return Output(Program(std::move(out)));
 }
 
 bool FirstIndexOffset::HasVertexIndex() {
@@ -195,7 +202,7 @@
   return instance_index_offset_;
 }
 
-ast::Variable* FirstIndexOffset::AddUniformBuffer(Program* dst) {
+ast::Variable* FirstIndexOffset::AddUniformBuffer(ProgramBuilder* dst) {
   auto* u32_type = dst->create<type::U32>();
   ast::StructMemberList members;
   uint32_t offset = 0;
@@ -251,7 +258,7 @@
     const std::string& original_name,
     const std::string& field_name,
     ast::Variable* buffer_var,
-    Program* dst) {
+    ProgramBuilder* dst) {
   auto* buffer =
       dst->create<ast::IdentifierExpression>(Source{}, buffer_var->symbol());
 
diff --git a/src/transform/first_index_offset.h b/src/transform/first_index_offset.h
index b540ca8..ffbf496 100644
--- a/src/transform/first_index_offset.h
+++ b/src/transform/first_index_offset.h
@@ -89,19 +89,19 @@
   uint32_t GetFirstInstanceOffset();
 
  private:
-  /// Adds uniform buffer with firstVertex/Instance to `program`
+  /// Adds uniform buffer with firstVertex/Instance to the program builder
   /// @returns variable of new uniform buffer
-  ast::Variable* AddUniformBuffer(Program* program);
+  ast::Variable* AddUniformBuffer(ProgramBuilder* builder);
   /// Adds constant with modified original_name builtin to func
   /// @param original_name the name of the original builtin used in function
   /// @param field_name name of field in firstVertex/Instance buffer
   /// @param buffer_var variable of firstVertex/Instance buffer
-  /// @param program the target program to contain the new ast nodes
+  /// @param builder the target to contain the new ast nodes
   ast::VariableDeclStatement* CreateFirstIndexOffset(
       const std::string& original_name,
       const std::string& field_name,
       ast::Variable* buffer_var,
-      Program* program);
+      ProgramBuilder* builder);
 
   uint32_t binding_;
   uint32_t group_;
diff --git a/src/transform/manager.cc b/src/transform/manager.cc
index 1503d62..22e865f 100644
--- a/src/transform/manager.cc
+++ b/src/transform/manager.cc
@@ -14,6 +14,7 @@
 
 #include "src/transform/manager.h"
 
+#include "src/program_builder.h"
 #include "src/type_determiner.h"
 
 namespace tint {
@@ -35,16 +36,10 @@
       program = &out.program;
     }
   } else {
-    out.program = program->Clone();
+    out.program = Program(program->Clone());
   }
 
-  TypeDeterminer td(&out.program);
-  if (!td.Determine()) {
-    diag::Diagnostic err;
-    err.severity = diag::Severity::Error;
-    err.message = td.error();
-    out.diagnostics.add(std::move(err));
-  }
+  out.diagnostics.add(TypeDeterminer::Run(&out.program));
 
   return out;
 }
diff --git a/src/transform/test_helper.h b/src/transform/test_helper.h
index 74d6a7e..a0f64bd 100644
--- a/src/transform/test_helper.h
+++ b/src/transform/test_helper.h
@@ -21,6 +21,8 @@
 #include <vector>
 
 #include "gtest/gtest.h"
+#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/reader/wgsl/parser.h"
 #include "src/transform/manager.h"
 #include "src/type_determiner.h"
@@ -46,29 +48,33 @@
       return "WGSL reader failed:\n" + parser.error();
     }
 
+    diag::Formatter::Style style;
+    style.print_newline_at_end = false;
+
     auto program = parser.program();
-    TypeDeterminer td(&program);
-    if (!td.Determine()) {
-      return "Type determination failed:\n" + td.error();
+    {
+      auto diagnostics = TypeDeterminer::Run(&program);
+      if (diagnostics.contains_errors()) {
+        return "Type determination failed:\n" +
+               diag::Formatter(style).format(diagnostics);
+      }
     }
 
-    Manager manager;
-    for (auto& transform : transforms) {
-      manager.append(std::move(transform));
-    }
-    auto result = manager.Run(&program);
+    {
+      Manager manager;
+      for (auto& transform : transforms) {
+        manager.append(std::move(transform));
+      }
+      auto result = manager.Run(&program);
 
-    if (result.diagnostics.contains_errors()) {
-      diag::Formatter::Style style;
-      style.print_newline_at_end = false;
-      return "manager().Run() errored:\n" +
-             diag::Formatter(style).format(result.diagnostics);
+      if (result.diagnostics.contains_errors()) {
+        return "manager().Run() errored:\n" +
+               diag::Formatter(style).format(result.diagnostics);
+      }
+      program = std::move(result.program);
     }
 
-    // Release the source program to ensure there's no uncloned data in result
-    { auto tmp = std::move(program); }
-
-    writer::wgsl::Generator generator(&result.program);
+    writer::wgsl::Generator generator(&program);
     if (!generator.Generate()) {
       return "WGSL writer failed:\n" + generator.error();
     }
diff --git a/src/transform/transform.cc b/src/transform/transform.cc
index 63bf604..b0c8db5 100644
--- a/src/transform/transform.cc
+++ b/src/transform/transform.cc
@@ -17,6 +17,7 @@
 #include "src/ast/block_statement.h"
 #include "src/ast/function.h"
 #include "src/clone_context.h"
+#include "src/program_builder.h"
 
 namespace tint {
 namespace transform {
@@ -28,12 +29,6 @@
 Transform::Output::Output(Program&& p, diag::List&& d)
     : program(std::move(p)), diagnostics(std::move(d)) {}
 
-Transform::Output::~Output() = default;
-
-Transform::Output::Output(Output&&) = default;
-
-Transform::Output& Transform::Output::operator=(Output&& rhs) = default;
-
 Transform::Transform() = default;
 
 Transform::~Transform() = default;
diff --git a/src/transform/transform.h b/src/transform/transform.h
index 9f00ec0..c3f05c1 100644
--- a/src/transform/transform.h
+++ b/src/transform/transform.h
@@ -47,18 +47,6 @@
     /// @param diags the list of diagnostics to move into this Output
     Output(Program&& program, diag::List&& diags);
 
-    /// Move constructor
-    /// @param output the output to move into this Output
-    Output(Output&& output);
-
-    /// Destructor
-    ~Output();
-
-    /// Move assignment operator
-    /// @param rhs the Output to move into this Output
-    /// @returns this Output
-    Output& operator=(Output&& rhs);
-
     /// The transformed program. May be empty on error.
     Program program;
     /// Diagnostics raised while running the Transform.
diff --git a/src/transform/vertex_pulling.cc b/src/transform/vertex_pulling.cc
index caca8c2..effd571 100644
--- a/src/transform/vertex_pulling.cc
+++ b/src/transform/vertex_pulling.cc
@@ -33,6 +33,8 @@
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/clone_context.h"
+#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/array_type.h"
 #include "src/type/f32_type.h"
 #include "src/type/i32_type.h"
@@ -101,9 +103,9 @@
 
   // TODO(idanr): Make sure we covered all error cases, to guarantee the
   // following stages will pass
-  Output out;
+  ProgramBuilder out;
 
-  CloneContext ctx(&out.program, in);
+  CloneContext ctx(&out, in);
   State state{ctx, cfg};
   state.FindOrInsertVertexIndexIfUsed();
   state.FindOrInsertInstanceIndexIfUsed();
@@ -122,7 +124,7 @@
   });
   ctx.Clone();
 
-  return out;
+  return Output(Program(std::move(out)));
 }
 
 VertexPulling::Config::Config() = default;
diff --git a/src/type/access_control_type.cc b/src/type/access_control_type.cc
index 5dafaa3..9125cc8 100644
--- a/src/type/access_control_type.cc
+++ b/src/type/access_control_type.cc
@@ -17,7 +17,7 @@
 #include <assert.h>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::AccessControl);
 
diff --git a/src/type/alias_type.cc b/src/type/alias_type.cc
index 0d10ad3..cd89bda 100644
--- a/src/type/alias_type.cc
+++ b/src/type/alias_type.cc
@@ -17,7 +17,7 @@
 #include <assert.h>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Alias);
 
diff --git a/src/type/array_type.cc b/src/type/array_type.cc
index 469b4ea..ce90ad4 100644
--- a/src/type/array_type.cc
+++ b/src/type/array_type.cc
@@ -19,7 +19,7 @@
 
 #include "src/ast/stride_decoration.h"
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/vector_type.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Array);
diff --git a/src/type/bool_type.cc b/src/type/bool_type.cc
index de85009..5b97dec 100644
--- a/src/type/bool_type.cc
+++ b/src/type/bool_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/bool_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Bool);
 
diff --git a/src/type/depth_texture_type.cc b/src/type/depth_texture_type.cc
index bc8e584..c3cb32e 100644
--- a/src/type/depth_texture_type.cc
+++ b/src/type/depth_texture_type.cc
@@ -18,7 +18,7 @@
 #include <sstream>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::DepthTexture);
 
diff --git a/src/type/f32_type.cc b/src/type/f32_type.cc
index c60c1eb..976a549 100644
--- a/src/type/f32_type.cc
+++ b/src/type/f32_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/f32_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::F32);
 
diff --git a/src/type/i32_type.cc b/src/type/i32_type.cc
index 402a8a9..ded1932 100644
--- a/src/type/i32_type.cc
+++ b/src/type/i32_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/i32_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::I32);
 
diff --git a/src/type/matrix_type.cc b/src/type/matrix_type.cc
index 9b4d097..f32b396 100644
--- a/src/type/matrix_type.cc
+++ b/src/type/matrix_type.cc
@@ -17,7 +17,7 @@
 #include <assert.h>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/array_type.h"
 #include "src/type/vector_type.h"
 
diff --git a/src/type/multisampled_texture_type.cc b/src/type/multisampled_texture_type.cc
index 504019b..e9d415d 100644
--- a/src/type/multisampled_texture_type.cc
+++ b/src/type/multisampled_texture_type.cc
@@ -18,7 +18,7 @@
 #include <sstream>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::MultisampledTexture);
 
diff --git a/src/type/pointer_type.cc b/src/type/pointer_type.cc
index fd31228..8ac4a94 100644
--- a/src/type/pointer_type.cc
+++ b/src/type/pointer_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/pointer_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Pointer);
 
diff --git a/src/type/sampled_texture_type.cc b/src/type/sampled_texture_type.cc
index 119c547..a63577b 100644
--- a/src/type/sampled_texture_type.cc
+++ b/src/type/sampled_texture_type.cc
@@ -18,7 +18,7 @@
 #include <sstream>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::SampledTexture);
 
diff --git a/src/type/sampler_type.cc b/src/type/sampler_type.cc
index 60fc8ff..bff290b 100644
--- a/src/type/sampler_type.cc
+++ b/src/type/sampler_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/sampler_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Sampler);
 
diff --git a/src/type/storage_texture_type.cc b/src/type/storage_texture_type.cc
index 09afd48..bdd7610 100644
--- a/src/type/storage_texture_type.cc
+++ b/src/type/storage_texture_type.cc
@@ -18,7 +18,7 @@
 #include <sstream>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::StorageTexture);
 
diff --git a/src/type/storage_texture_type_test.cc b/src/type/storage_texture_type_test.cc
index 7860926..a6829ed 100644
--- a/src/type/storage_texture_type_test.cc
+++ b/src/type/storage_texture_type_test.cc
@@ -82,7 +82,7 @@
 TEST_F(StorageTextureTest, F32) {
   Type* s = create<StorageTexture>(TextureDimension::k2dArray,
                                    ImageFormat::kRgba32Float);
-  TypeDeterminer td(mod);
+  TypeDeterminer td(this);
 
   ASSERT_TRUE(td.Determine()) << td.error();
   ASSERT_TRUE(s->Is<Texture>());
@@ -93,7 +93,7 @@
 TEST_F(StorageTextureTest, U32) {
   Type* s = create<StorageTexture>(TextureDimension::k2dArray,
                                    ImageFormat::kRg32Uint);
-  TypeDeterminer td(mod);
+  TypeDeterminer td(this);
 
   ASSERT_TRUE(td.Determine()) << td.error();
   ASSERT_TRUE(s->Is<Texture>());
@@ -104,7 +104,7 @@
 TEST_F(StorageTextureTest, I32) {
   Type* s = create<StorageTexture>(TextureDimension::k2dArray,
                                    ImageFormat::kRgba32Sint);
-  TypeDeterminer td(mod);
+  TypeDeterminer td(this);
 
   ASSERT_TRUE(td.Determine()) << td.error();
   ASSERT_TRUE(s->Is<Texture>());
diff --git a/src/type/struct_type.cc b/src/type/struct_type.cc
index 458aabf..39efc95 100644
--- a/src/type/struct_type.cc
+++ b/src/type/struct_type.cc
@@ -18,7 +18,7 @@
 #include <utility>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 #include "src/type/alias_type.h"
 #include "src/type/array_type.h"
 #include "src/type/matrix_type.h"
diff --git a/src/type/test_helper.h b/src/type/test_helper.h
index c1006bb..68832b0 100644
--- a/src/type/test_helper.h
+++ b/src/type/test_helper.h
@@ -20,22 +20,21 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
 #include "src/demangler.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 namespace tint {
 namespace type {
 
 /// Helper class for testing
 template <typename BASE>
-class TestHelperBase : public BASE, public ast::BuilderWithProgram {
+class TestHelperBase : public BASE, public ProgramBuilder {
  public:
   /// Demangles the given string
   /// @param s the string to demangle
   /// @returns the demangled string
   std::string demangle(const std::string& s) {
-    return demanger.Demangle(Symbols(), s);
+    return demanger.Demangle(this, s);
   }
 
   /// A demangler
diff --git a/src/type/type.h b/src/type/type.h
index 847f5c8..ae8d245 100644
--- a/src/type/type.h
+++ b/src/type/type.h
@@ -18,12 +18,12 @@
 #include <string>
 
 #include "src/castable.h"
+#include "src/clone_context.h"
 
 namespace tint {
 
 // Forward declarations
-class CloneContext;
-class Program;
+class ProgramBuilder;
 
 namespace type {
 
@@ -101,12 +101,12 @@
 
   /// A helper method for cloning the `Type` `t` if it is not null.
   /// If `t` is null, then `Clone()` returns null.
-  /// @param p the program to clone `n` into
+  /// @param b the program builder to clone `n` into
   /// @param t the `Type` to clone (if not null)
   /// @return the cloned type
   template <typename T>
-  static T* Clone(Program* p, const T* t) {
-    return (t != nullptr) ? static_cast<T*>(t->Clone(p)) : nullptr;
+  static T* Clone(ProgramBuilder* b, const T* t) {
+    return (t != nullptr) ? static_cast<T*>(t->Clone(b)) : nullptr;
   }
 };
 
diff --git a/src/type/u32_type.cc b/src/type/u32_type.cc
index d98dbb6..0736b05 100644
--- a/src/type/u32_type.cc
+++ b/src/type/u32_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/u32_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::U32);
 
diff --git a/src/type/vector_type.cc b/src/type/vector_type.cc
index 85a1683..96e3b16 100644
--- a/src/type/vector_type.cc
+++ b/src/type/vector_type.cc
@@ -18,7 +18,7 @@
 #include <cmath>
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Vector);
 
diff --git a/src/type/void_type.cc b/src/type/void_type.cc
index 2f9b6f8..fe2a1a6 100644
--- a/src/type/void_type.cc
+++ b/src/type/void_type.cc
@@ -15,7 +15,7 @@
 #include "src/type/void_type.h"
 
 #include "src/clone_context.h"
-#include "src/program.h"
+#include "src/program_builder.h"
 
 TINT_INSTANTIATE_CLASS_ID(tint::type::Void);
 
diff --git a/src/type_determiner.cc b/src/type_determiner.cc
index 6c323d8..63f4d35 100644
--- a/src/type_determiner.cc
+++ b/src/type_determiner.cc
@@ -15,6 +15,7 @@
 #include "src/type_determiner.h"
 
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "src/ast/array_accessor_expression.h"
@@ -41,6 +42,7 @@
 #include "src/ast/type_constructor_expression.h"
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/program_builder.h"
 #include "src/type/array_type.h"
 #include "src/type/bool_type.h"
 #include "src/type/depth_texture_type.h"
@@ -59,18 +61,20 @@
 
 namespace tint {
 
-TypeDeterminer::TypeDeterminer(Program* program) : program_(program) {}
+TypeDeterminer::TypeDeterminer(ProgramBuilder* builder) : builder_(builder) {}
 
 TypeDeterminer::~TypeDeterminer() = default;
 
 diag::List TypeDeterminer::Run(Program* program) {
-  TypeDeterminer td(program);
+  ProgramBuilder builder = program->CloneAsBuilder();
+  TypeDeterminer td(&builder);
   if (!td.Determine()) {
     diag::Diagnostic err;
     err.severity = diag::Severity::Error;
     err.message = td.error();
     return {err};
   }
+  *program = Program(std::move(builder));
   return {};
 }
 
@@ -101,8 +105,9 @@
 
 bool TypeDeterminer::Determine() {
   std::vector<type::StorageTexture*> storage_textures;
-  for (auto* ty : program_->Types()) {
-    if (auto* storage = ty->UnwrapIfNeeded()->As<type::StorageTexture>()) {
+  for (auto& it : builder_->Types().types()) {
+    if (auto* storage =
+            it.second->UnwrapIfNeeded()->As<type::StorageTexture>()) {
       storage_textures.emplace_back(storage);
     }
   }
@@ -115,7 +120,7 @@
     }
   }
 
-  for (auto* var : program_->AST().GlobalVariables()) {
+  for (auto* var : builder_->AST().GlobalVariables()) {
     variable_stack_.set_global(var->symbol(), var);
 
     if (var->has_constructor()) {
@@ -125,13 +130,13 @@
     }
   }
 
-  if (!DetermineFunctions(program_->AST().Functions())) {
+  if (!DetermineFunctions(builder_->AST().Functions())) {
     return false;
   }
 
   // Walk over the caller to callee information and update functions with which
   // entry points call those functions.
-  for (auto* func : program_->AST().Functions()) {
+  for (auto* func : builder_->AST().Functions()) {
     if (!func->IsEntryPoint()) {
       continue;
     }
@@ -350,7 +355,7 @@
   } else if (auto* vec = parent_type->As<type::Vector>()) {
     ret = vec->type();
   } else if (auto* mat = parent_type->As<type::Matrix>()) {
-    ret = program_->create<type::Vector>(mat->type(), mat->rows());
+    ret = builder_->create<type::Vector>(mat->type(), mat->rows());
   } else {
     set_error(expr->source(), "invalid parent type (" +
                                   parent_type->type_name() +
@@ -360,13 +365,13 @@
 
   // If we're extracting from a pointer, we return a pointer.
   if (auto* ptr = res->As<type::Pointer>()) {
-    ret = program_->create<type::Pointer>(ret, ptr->storage_class());
+    ret = builder_->create<type::Pointer>(ret, ptr->storage_class());
   } else if (auto* arr = parent_type->As<type::Array>()) {
     if (!arr->type()->is_scalar()) {
       // If we extract a non-scalar from an array then we also get a pointer. We
       // will generate a Function storage class variable to store this
       // into.
-      ret = program_->create<type::Pointer>(ret, ast::StorageClass::kFunction);
+      ret = builder_->create<type::Pointer>(ret, ast::StorageClass::kFunction);
     }
   }
   expr->set_result_type(ret);
@@ -403,11 +408,11 @@
         caller_to_callee_[current_function_->symbol()].push_back(
             ident->symbol());
 
-        auto* callee_func = program_->AST().Functions().Find(ident->symbol());
+        auto* callee_func = builder_->AST().Functions().Find(ident->symbol());
         if (callee_func == nullptr) {
           set_error(expr->source(),
                     "unable to find called function: " +
-                        program_->Symbols().NameFor(ident->symbol()));
+                        builder_->Symbols().NameFor(ident->symbol()));
           return false;
         }
 
@@ -433,7 +438,7 @@
     auto func_sym = expr->func()->As<ast::IdentifierExpression>()->symbol();
     set_error(expr->source(),
               "v-0005: function must be declared before use: '" +
-                  program_->Symbols().NameFor(func_sym) + "'");
+                  builder_->Symbols().NameFor(func_sym) + "'");
     return false;
   }
 
@@ -521,7 +526,7 @@
     if (expr->params().size() != 1) {
       set_error(expr->source(),
                 "incorrect number of parameters for " +
-                    program_->Symbols().NameFor(ident->symbol()));
+                    builder_->Symbols().NameFor(ident->symbol()));
       return false;
     }
 
@@ -532,27 +537,27 @@
   }
   if (ident->intrinsic() == ast::Intrinsic::kAny ||
       ident->intrinsic() == ast::Intrinsic::kAll) {
-    expr->func()->set_result_type(program_->create<type::Bool>());
+    expr->func()->set_result_type(builder_->create<type::Bool>());
     return true;
   }
   if (ident->intrinsic() == ast::Intrinsic::kArrayLength) {
-    expr->func()->set_result_type(program_->create<type::U32>());
+    expr->func()->set_result_type(builder_->create<type::U32>());
     return true;
   }
   if (ast::intrinsic::IsFloatClassificationIntrinsic(ident->intrinsic())) {
     if (expr->params().size() != 1) {
       set_error(expr->source(),
                 "incorrect number of parameters for " +
-                    program_->Symbols().NameFor(ident->symbol()));
+                    builder_->Symbols().NameFor(ident->symbol()));
       return false;
     }
 
-    auto* bool_type = program_->create<type::Bool>();
+    auto* bool_type = builder_->create<type::Bool>();
 
     auto* param_type = expr->params()[0]->result_type()->UnwrapPtrIfNeeded();
     if (auto* vec = param_type->As<type::Vector>()) {
       expr->func()->set_result_type(
-          program_->create<type::Vector>(bool_type, vec->size()));
+          builder_->create<type::Vector>(bool_type, vec->size()));
     } else {
       expr->func()->set_result_type(bool_type);
     }
@@ -565,7 +570,7 @@
     if (!texture_param->result_type()->UnwrapAll()->Is<type::Texture>()) {
       set_error(expr->source(),
                 "invalid first argument for " +
-                    program_->Symbols().NameFor(ident->symbol()));
+                    builder_->Symbols().NameFor(ident->symbol()));
       return false;
     }
     type::Texture* texture =
@@ -677,7 +682,7 @@
     if (expr->params().size() != param.count) {
       set_error(expr->source(),
                 "incorrect number of parameters for " +
-                    program_->Symbols().NameFor(ident->symbol()) + ", got " +
+                    builder_->Symbols().NameFor(ident->symbol()) + ", got " +
                     std::to_string(expr->params().size()) + " and expected " +
                     std::to_string(param.count));
       return false;
@@ -690,7 +695,7 @@
     type::Type* return_type = nullptr;
     switch (ident->intrinsic()) {
       case ast::Intrinsic::kTextureDimensions: {
-        auto* i32 = program_->create<type::I32>();
+        auto* i32 = builder_->create<type::I32>();
         switch (texture->dim()) {
           default:
             set_error(expr->source(), "invalid texture dimensions");
@@ -701,12 +706,12 @@
             break;
           case type::TextureDimension::k2d:
           case type::TextureDimension::k2dArray:
-            return_type = program_->create<type::Vector>(i32, 2);
+            return_type = builder_->create<type::Vector>(i32, 2);
             break;
           case type::TextureDimension::k3d:
           case type::TextureDimension::kCube:
           case type::TextureDimension::kCubeArray:
-            return_type = program_->create<type::Vector>(i32, 3);
+            return_type = builder_->create<type::Vector>(i32, 3);
             break;
         }
         break;
@@ -714,14 +719,14 @@
       case ast::Intrinsic::kTextureNumLayers:
       case ast::Intrinsic::kTextureNumLevels:
       case ast::Intrinsic::kTextureNumSamples:
-        return_type = program_->create<type::I32>();
+        return_type = builder_->create<type::I32>();
         break;
       case ast::Intrinsic::kTextureStore:
-        return_type = program_->create<type::Void>();
+        return_type = builder_->create<type::Void>();
         break;
       default: {
         if (texture->Is<type::DepthTexture>()) {
-          return_type = program_->create<type::F32>();
+          return_type = builder_->create<type::F32>();
         } else {
           type::Type* type = nullptr;
           if (auto* storage = texture->As<type::StorageTexture>()) {
@@ -736,7 +741,7 @@
                       "unknown texture type for texture sampling");
             return false;
           }
-          return_type = program_->create<type::Vector>(type, 4);
+          return_type = builder_->create<type::Vector>(type, 4);
         }
       }
     }
@@ -745,14 +750,14 @@
     return true;
   }
   if (ident->intrinsic() == ast::Intrinsic::kDot) {
-    expr->func()->set_result_type(program_->create<type::F32>());
+    expr->func()->set_result_type(builder_->create<type::F32>());
     return true;
   }
   if (ident->intrinsic() == ast::Intrinsic::kSelect) {
     if (expr->params().size() != 3) {
       set_error(expr->source(),
                 "incorrect number of parameters for " +
-                    program_->Symbols().NameFor(ident->symbol()) +
+                    builder_->Symbols().NameFor(ident->symbol()) +
                     " expected 3 got " + std::to_string(expr->params().size()));
       return false;
     }
@@ -772,13 +777,13 @@
   }
   if (data == nullptr) {
     error_ = "unable to find intrinsic " +
-             program_->Symbols().NameFor(ident->symbol());
+             builder_->Symbols().NameFor(ident->symbol());
     return false;
   }
 
   if (expr->params().size() != data->param_count) {
     set_error(expr->source(), "incorrect number of parameters for " +
-                                  program_->Symbols().NameFor(ident->symbol()) +
+                                  builder_->Symbols().NameFor(ident->symbol()) +
                                   ". Expected " +
                                   std::to_string(data->param_count) + " got " +
                                   std::to_string(expr->params().size()));
@@ -796,7 +801,7 @@
             !result_types.back()->is_integer_scalar_or_vector()) {
           set_error(expr->source(),
                     "incorrect type for " +
-                        program_->Symbols().NameFor(ident->symbol()) + ". " +
+                        builder_->Symbols().NameFor(ident->symbol()) + ". " +
                         "Requires float or int, scalar or vector values");
           return false;
         }
@@ -805,7 +810,7 @@
         if (!result_types.back()->is_float_scalar_or_vector()) {
           set_error(expr->source(),
                     "incorrect type for " +
-                        program_->Symbols().NameFor(ident->symbol()) + ". " +
+                        builder_->Symbols().NameFor(ident->symbol()) + ". " +
                         "Requires float scalar or float vector values");
           return false;
         }
@@ -815,7 +820,7 @@
         if (!result_types.back()->is_integer_scalar_or_vector()) {
           set_error(expr->source(),
                     "incorrect type for " +
-                        program_->Symbols().NameFor(ident->symbol()) + ". " +
+                        builder_->Symbols().NameFor(ident->symbol()) + ". " +
                         "Requires integer scalar or integer vector values");
           return false;
         }
@@ -824,7 +829,7 @@
         if (!result_types.back()->is_float_vector()) {
           set_error(expr->source(),
                     "incorrect type for " +
-                        program_->Symbols().NameFor(ident->symbol()) + ". " +
+                        builder_->Symbols().NameFor(ident->symbol()) + ". " +
                         "Requires float vector values");
           return false;
         }
@@ -833,7 +838,7 @@
                 data->vector_size) {
           set_error(expr->source(),
                     "incorrect vector size for " +
-                        program_->Symbols().NameFor(ident->symbol()) + ". " +
+                        builder_->Symbols().NameFor(ident->symbol()) + ". " +
                         "Requires " + std::to_string(data->vector_size) +
                         " elements");
           return false;
@@ -843,7 +848,7 @@
         if (!result_types.back()->Is<type::Matrix>()) {
           set_error(expr->source(),
                     "incorrect type for " +
-                        program_->Symbols().NameFor(ident->symbol()) +
+                        builder_->Symbols().NameFor(ident->symbol()) +
                         ". Requires matrix value");
           return false;
         }
@@ -856,7 +861,7 @@
     if (result_types[0] != result_types[i]) {
       set_error(expr->source(),
                 "mismatched parameter types for " +
-                    program_->Symbols().NameFor(ident->symbol()));
+                    builder_->Symbols().NameFor(ident->symbol()));
       return false;
     }
   }
@@ -907,7 +912,7 @@
       expr->set_result_type(var->type());
     } else {
       expr->set_result_type(
-          program_->create<type::Pointer>(var->type(), var->storage_class()));
+          builder_->create<type::Pointer>(var->type(), var->storage_class()));
     }
 
     set_referenced_from_function_if_needed(var, true);
@@ -923,14 +928,14 @@
   if (!SetIntrinsicIfNeeded(expr)) {
     set_error(expr->source(),
               "v-0006: identifier must be declared before use: " +
-                  program_->Symbols().NameFor(symbol));
+                  builder_->Symbols().NameFor(symbol));
     return false;
   }
   return true;
 }
 
 bool TypeDeterminer::SetIntrinsicIfNeeded(ast::IdentifierExpression* ident) {
-  auto name = program_->Symbols().NameFor(ident->symbol());
+  auto name = builder_->Symbols().NameFor(ident->symbol());
   if (name == "abs") {
     ident->set_intrinsic(ast::Intrinsic::kAbs);
   } else if (name == "acos") {
@@ -1104,31 +1109,31 @@
 
     if (ret == nullptr) {
       set_error(expr->source(), "struct member " +
-                                    program_->Symbols().NameFor(symbol) +
+                                    builder_->Symbols().NameFor(symbol) +
                                     " not found");
       return false;
     }
 
     // If we're extracting from a pointer, we return a pointer.
     if (auto* ptr = res->As<type::Pointer>()) {
-      ret = program_->create<type::Pointer>(ret, ptr->storage_class());
+      ret = builder_->create<type::Pointer>(ret, ptr->storage_class());
     }
   } else if (auto* vec = data_type->As<type::Vector>()) {
     // TODO(dsinclair): Swizzle, record into the identifier experesion
 
-    auto size = program_->Symbols().NameFor(expr->member()->symbol()).size();
+    auto size = builder_->Symbols().NameFor(expr->member()->symbol()).size();
     if (size == 1) {
       // A single element swizzle is just the type of the vector.
       ret = vec->type();
       // If we're extracting from a pointer, we return a pointer.
       if (auto* ptr = res->As<type::Pointer>()) {
-        ret = program_->create<type::Pointer>(ret, ptr->storage_class());
+        ret = builder_->create<type::Pointer>(ret, ptr->storage_class());
       }
     } else {
       // The vector will have a number of components equal to the length of the
       // swizzle. This assumes the validator will check that the swizzle
       // is correct.
-      ret = program_->create<type::Vector>(vec->type(),
+      ret = builder_->create<type::Vector>(vec->type(),
                                            static_cast<uint32_t>(size));
     }
   } else {
@@ -1160,11 +1165,11 @@
   if (expr->IsLogicalAnd() || expr->IsLogicalOr() || expr->IsEqual() ||
       expr->IsNotEqual() || expr->IsLessThan() || expr->IsGreaterThan() ||
       expr->IsLessThanEqual() || expr->IsGreaterThanEqual()) {
-    auto* bool_type = program_->create<type::Bool>();
+    auto* bool_type = builder_->create<type::Bool>();
     auto* param_type = expr->lhs()->result_type()->UnwrapPtrIfNeeded();
     if (auto* vec = param_type->As<type::Vector>()) {
       expr->set_result_type(
-          program_->create<type::Vector>(bool_type, vec->size()));
+          builder_->create<type::Vector>(bool_type, vec->size()));
     } else {
       expr->set_result_type(bool_type);
     }
@@ -1181,14 +1186,14 @@
     auto* lhs_vec = lhs_type->As<type::Vector>();
     auto* rhs_vec = rhs_type->As<type::Vector>();
     if (lhs_mat && rhs_mat) {
-      expr->set_result_type(program_->create<type::Matrix>(
+      expr->set_result_type(builder_->create<type::Matrix>(
           lhs_mat->type(), lhs_mat->rows(), rhs_mat->columns()));
     } else if (lhs_mat && rhs_vec) {
       expr->set_result_type(
-          program_->create<type::Vector>(lhs_mat->type(), lhs_mat->rows()));
+          builder_->create<type::Vector>(lhs_mat->type(), lhs_mat->rows()));
     } else if (lhs_vec && rhs_mat) {
       expr->set_result_type(
-          program_->create<type::Vector>(rhs_mat->type(), rhs_mat->columns()));
+          builder_->create<type::Vector>(rhs_mat->type(), rhs_mat->columns()));
     } else if (lhs_mat) {
       // matrix * scalar
       expr->set_result_type(lhs_type);
@@ -1239,7 +1244,7 @@
     case type::ImageFormat::kRg32Uint:
     case type::ImageFormat::kRgba16Uint:
     case type::ImageFormat::kRgba32Uint: {
-      tex->set_type(program_->create<type::U32>());
+      tex->set_type(builder_->create<type::U32>());
       return true;
     }
 
@@ -1252,7 +1257,7 @@
     case type::ImageFormat::kRg32Sint:
     case type::ImageFormat::kRgba16Sint:
     case type::ImageFormat::kRgba32Sint: {
-      tex->set_type(program_->create<type::I32>());
+      tex->set_type(builder_->create<type::I32>());
       return true;
     }
 
@@ -1273,7 +1278,7 @@
     case type::ImageFormat::kRg32Float:
     case type::ImageFormat::kRgba16Float:
     case type::ImageFormat::kRgba32Float: {
-      tex->set_type(program_->create<type::F32>());
+      tex->set_type(builder_->create<type::F32>());
       return true;
     }
 
diff --git a/src/type_determiner.h b/src/type_determiner.h
index 5387714..c0532a0 100644
--- a/src/type_determiner.h
+++ b/src/type_determiner.h
@@ -21,7 +21,6 @@
 
 #include "src/ast/module.h"
 #include "src/diagnostic/diagnostic.h"
-#include "src/program.h"
 #include "src/scope_stack.h"
 #include "src/type/storage_texture_type.h"
 
@@ -45,8 +44,8 @@
 class TypeDeterminer {
  public:
   /// Constructor
-  /// @param program the program to update with typing information
-  explicit TypeDeterminer(Program* program);
+  /// @param builder the program builder
+  explicit TypeDeterminer(ProgramBuilder* builder);
 
   /// Destructor
   ~TypeDeterminer();
@@ -138,7 +137,7 @@
   bool DetermineMemberAccessor(ast::MemberAccessorExpression* expr);
   bool DetermineUnaryOp(ast::UnaryOpExpression* expr);
 
-  Program* program_;
+  ProgramBuilder* builder_;
   std::string error_;
   ScopeStack<ast::Variable*> variable_stack_;
   std::unordered_map<Symbol, ast::Function*> symbol_to_function_;
diff --git a/src/type_determiner_test.cc b/src/type_determiner_test.cc
index e2035c3..1dd3d6e 100644
--- a/src/type_determiner_test.cc
+++ b/src/type_determiner_test.cc
@@ -27,7 +27,6 @@
 #include "src/ast/block_statement.h"
 #include "src/ast/bool_literal.h"
 #include "src/ast/break_statement.h"
-#include "src/ast/builder.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/case_statement.h"
@@ -51,6 +50,7 @@
 #include "src/ast/uint_literal.h"
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/program_builder.h"
 #include "src/type/alias_type.h"
 #include "src/type/array_type.h"
 #include "src/type/bool_type.h"
@@ -87,9 +87,9 @@
   void to_str(std::ostream&, size_t) const override {}
 };
 
-class TypeDeterminerHelper : public ast::BuilderWithProgram {
+class TypeDeterminerHelper : public ProgramBuilder {
  public:
-  TypeDeterminerHelper() : td_(std::make_unique<TypeDeterminer>(program)) {}
+  TypeDeterminerHelper() : td_(std::make_unique<TypeDeterminer>(this)) {}
 
   TypeDeterminer* td() const { return td_.get(); }
 
diff --git a/src/validator/validator_function_test.cc b/src/validator/validator_function_test.cc
index 1526dcb..1d3653d 100644
--- a/src/validator/validator_function_test.cc
+++ b/src/validator/validator_function_test.cc
@@ -132,8 +132,9 @@
   EXPECT_TRUE(td()->DetermineFunctions(AST().Functions())) << td()->error();
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_TRUE(v.ValidateFunctions(AST().Functions())) << v.error();
+  EXPECT_TRUE(v.ValidateFunctions(program->AST().Functions())) << v.error();
 }
 
 TEST_F(ValidateFunctionTest, FunctionTypeMustMatchReturnStatementType_fail) {
diff --git a/src/validator/validator_impl.cc b/src/validator/validator_impl.cc
index 0ce8526..85297e4 100644
--- a/src/validator/validator_impl.cc
+++ b/src/validator/validator_impl.cc
@@ -23,12 +23,14 @@
 #include "src/ast/function.h"
 #include "src/ast/int_literal.h"
 #include "src/ast/intrinsic.h"
+#include "src/ast/module.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/struct.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/uint_literal.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/type/alias_type.h"
 #include "src/type/array_type.h"
 #include "src/type/i32_type.h"
 #include "src/type/matrix_type.h"
diff --git a/src/validator/validator_impl.h b/src/validator/validator_impl.h
index 0ee8c05..0119aa3 100644
--- a/src/validator/validator_impl.h
+++ b/src/validator/validator_impl.h
@@ -24,6 +24,7 @@
 #include "src/ast/call_expression.h"
 #include "src/ast/expression.h"
 #include "src/ast/identifier_expression.h"
+#include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/statement.h"
 #include "src/ast/switch_statement.h"
@@ -48,6 +49,9 @@
   /// @returns true if the validation was successful
   bool Validate();
 
+  /// @returns the program being validated
+  const Program* program() { return program_; }
+
   /// @returns the diagnostic messages
   const diag::List& diagnostics() const { return diags_; }
   /// @returns the diagnostic messages
diff --git a/src/validator/validator_test.cc b/src/validator/validator_test.cc
index 2cd648f..a7bca4c 100644
--- a/src/validator/validator_test.cc
+++ b/src/validator/validator_test.cc
@@ -73,9 +73,10 @@
   auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
   RegisterVariable(var);
 
+  EXPECT_TRUE(td()->DetermineResultType(assign));
+
   ValidatorImpl& v = Build();
 
-  EXPECT_TRUE(td()->DetermineResultType(assign));
   // TODO(sarahM0): Invalidate assignment to scalar.
   EXPECT_FALSE(v.ValidateAssign(assign));
   ASSERT_TRUE(v.has_error());
@@ -131,6 +132,7 @@
   EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
   ASSERT_NE(lhs->result_type(), nullptr);
   ASSERT_NE(rhs->result_type(), nullptr);
+
   ValidatorImpl& v = Build();
 
   EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
@@ -153,6 +155,7 @@
   EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
   ASSERT_NE(lhs->result_type(), nullptr);
   ASSERT_NE(rhs->result_type(), nullptr);
+
   ValidatorImpl& v = Build();
 
   EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
@@ -177,6 +180,7 @@
   EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
   ASSERT_NE(lhs->result_type(), nullptr);
   ASSERT_NE(rhs->result_type(), nullptr);
+
   ValidatorImpl& v = Build();
 
   EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
@@ -329,8 +333,10 @@
                               ast::VariableDecorationList{}));
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_TRUE(v.ValidateGlobalVariables(AST().GlobalVariables())) << v.error();
+  EXPECT_TRUE(v.ValidateGlobalVariables(program->AST().GlobalVariables()))
+      << v.error();
 }
 
 TEST_F(ValidatorTest, GlobalVariableNoStorageClass_Fail) {
@@ -509,8 +515,10 @@
   AST().AddGlobalVariable(var1);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_TRUE(v.ValidateGlobalVariables(AST().GlobalVariables())) << v.error();
+  EXPECT_TRUE(v.ValidateGlobalVariables(program->AST().GlobalVariables()))
+      << v.error();
 }
 
 TEST_F(ValidatorTest, GlobalVariableNotUnique_Fail) {
@@ -526,8 +534,9 @@
   AST().AddGlobalVariable(var1);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_FALSE(v.ValidateGlobalVariables(AST().GlobalVariables()));
+  EXPECT_FALSE(v.ValidateGlobalVariables(program->AST().GlobalVariables()));
   EXPECT_EQ(v.error(),
             "12:34 v-0011: redeclared global identifier 'global_var'");
 }
@@ -747,50 +756,78 @@
 }
 
 TEST_F(ValidatorTest, IsStorable_Void) {
+  auto* void_ty = ty.void_();
+
   ValidatorImpl& v = Build();
 
-  EXPECT_FALSE(v.IsStorable(ty.void_()));
+  EXPECT_FALSE(v.IsStorable(void_ty));
 }
 
 TEST_F(ValidatorTest, IsStorable_Scalar) {
+  auto* bool_ = ty.bool_();
+  auto* i32 = ty.i32();
+  auto* u32 = ty.u32();
+  auto* f32 = ty.f32();
+
   ValidatorImpl& v = Build();
 
-  EXPECT_TRUE(v.IsStorable(ty.bool_()));
-  EXPECT_TRUE(v.IsStorable(ty.i32()));
-  EXPECT_TRUE(v.IsStorable(ty.u32()));
-  EXPECT_TRUE(v.IsStorable(ty.f32()));
+  EXPECT_TRUE(v.IsStorable(bool_));
+  EXPECT_TRUE(v.IsStorable(i32));
+  EXPECT_TRUE(v.IsStorable(u32));
+  EXPECT_TRUE(v.IsStorable(f32));
 }
 
 TEST_F(ValidatorTest, IsStorable_Vector) {
+  auto* vec2_i32 = ty.vec2<int>();
+  auto* vec3_i32 = ty.vec3<int>();
+  auto* vec4_i32 = ty.vec4<int>();
+  auto* vec2_u32 = ty.vec2<unsigned>();
+  auto* vec3_u32 = ty.vec3<unsigned>();
+  auto* vec4_u32 = ty.vec4<unsigned>();
+  auto* vec2_f32 = ty.vec2<float>();
+  auto* vec3_f32 = ty.vec3<float>();
+  auto* vec4_f32 = ty.vec4<float>();
+
   ValidatorImpl& v = Build();
 
-  EXPECT_TRUE(v.IsStorable(ty.vec2<int>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec3<int>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec4<int>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec2<unsigned>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec3<unsigned>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec4<unsigned>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec2<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec3<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.vec4<float>()));
+  EXPECT_TRUE(v.IsStorable(vec2_i32));
+  EXPECT_TRUE(v.IsStorable(vec3_i32));
+  EXPECT_TRUE(v.IsStorable(vec4_i32));
+  EXPECT_TRUE(v.IsStorable(vec2_u32));
+  EXPECT_TRUE(v.IsStorable(vec3_u32));
+  EXPECT_TRUE(v.IsStorable(vec4_u32));
+  EXPECT_TRUE(v.IsStorable(vec2_f32));
+  EXPECT_TRUE(v.IsStorable(vec3_f32));
+  EXPECT_TRUE(v.IsStorable(vec4_f32));
 }
 
 TEST_F(ValidatorTest, IsStorable_Matrix) {
+  auto* mat2x2 = ty.mat2x2<float>();
+  auto* mat2x3 = ty.mat2x3<float>();
+  auto* mat2x4 = ty.mat2x4<float>();
+  auto* mat3x2 = ty.mat3x2<float>();
+  auto* mat3x3 = ty.mat3x3<float>();
+  auto* mat3x4 = ty.mat3x4<float>();
+  auto* mat4x2 = ty.mat4x2<float>();
+  auto* mat4x3 = ty.mat4x3<float>();
+  auto* mat4x4 = ty.mat4x4<float>();
+
   ValidatorImpl& v = Build();
 
-  EXPECT_TRUE(v.IsStorable(ty.mat2x2<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat2x3<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat2x4<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat3x2<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat3x3<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat3x4<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat4x2<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat4x3<float>()));
-  EXPECT_TRUE(v.IsStorable(ty.mat4x4<float>()));
+  EXPECT_TRUE(v.IsStorable(mat2x2));
+  EXPECT_TRUE(v.IsStorable(mat2x3));
+  EXPECT_TRUE(v.IsStorable(mat2x4));
+  EXPECT_TRUE(v.IsStorable(mat3x2));
+  EXPECT_TRUE(v.IsStorable(mat3x3));
+  EXPECT_TRUE(v.IsStorable(mat3x4));
+  EXPECT_TRUE(v.IsStorable(mat4x2));
+  EXPECT_TRUE(v.IsStorable(mat4x3));
+  EXPECT_TRUE(v.IsStorable(mat4x4));
 }
 
 TEST_F(ValidatorTest, IsStorable_Pointer) {
   auto* ptr_ty = ty.pointer<int>(ast::StorageClass::kPrivate);
+
   ValidatorImpl& v = Build();
 
   EXPECT_FALSE(v.IsStorable(ptr_ty));
@@ -798,6 +835,7 @@
 
 TEST_F(ValidatorTest, IsStorable_AliasVoid) {
   auto* alias = ty.alias("myalias", ty.void_());
+
   ValidatorImpl& v = Build();
 
   EXPECT_FALSE(v.IsStorable(alias));
@@ -805,39 +843,49 @@
 
 TEST_F(ValidatorTest, IsStorable_AliasI32) {
   auto* alias = ty.alias("myalias", ty.i32());
+
   ValidatorImpl& v = Build();
 
   EXPECT_TRUE(v.IsStorable(alias));
 }
 
 TEST_F(ValidatorTest, IsStorable_ArraySizedOfStorable) {
+  auto* arr = ty.array(ty.i32(), 5);
+
   ValidatorImpl& v = Build();
 
-  EXPECT_TRUE(v.IsStorable(ty.array(ty.i32(), 5)));
+  EXPECT_TRUE(v.IsStorable(arr));
 }
 
 TEST_F(ValidatorTest, IsStorable_ArraySizedOfNonStorable) {
+  auto* arr = ty.array(ty.void_(), 5);
+
   ValidatorImpl& v = Build();
 
-  EXPECT_FALSE(v.IsStorable(ty.array(ty.void_(), 5)));
+  EXPECT_FALSE(v.IsStorable(arr));
 }
 
 TEST_F(ValidatorTest, IsStorable_ArrayUnsizedOfStorable) {
+  auto* arr = ty.array<int>();
+
   ValidatorImpl& v = Build();
 
-  EXPECT_TRUE(v.IsStorable(ty.array<int>()));
+  EXPECT_TRUE(v.IsStorable(arr));
 }
 
 TEST_F(ValidatorTest, IsStorable_ArrayUnsizedOfNonStorable) {
+  auto* arr = ty.array<void>();
+
   ValidatorImpl& v = Build();
 
-  EXPECT_FALSE(v.IsStorable(ty.array<void>()));
+  EXPECT_FALSE(v.IsStorable(arr));
 }
 
 TEST_F(ValidatorTest, IsStorable_Struct_AllMembersStorable) {
   ast::StructMemberList members{Member("a", ty.i32()), Member("b", ty.f32())};
   auto* s = create<ast::Struct>(Source{}, members, ast::StructDecorationList{});
   auto* s_ty = ty.struct_("mystruct", s);
+
   ValidatorImpl& v = Build();
 
   EXPECT_TRUE(v.IsStorable(s_ty));
@@ -848,6 +896,7 @@
   ast::StructMemberList members{Member("a", ty.i32()), Member("b", ptr_ty)};
   auto* s = create<ast::Struct>(Source{}, members, ast::StructDecorationList{});
   auto* s_ty = ty.struct_("mystruct", s);
+
   ValidatorImpl& v = Build();
 
   EXPECT_FALSE(v.IsStorable(s_ty));
diff --git a/src/validator/validator_test_helper.cc b/src/validator/validator_test_helper.cc
index e1ed61b..10dd3cd 100644
--- a/src/validator/validator_test_helper.cc
+++ b/src/validator/validator_test_helper.cc
@@ -19,7 +19,7 @@
 namespace tint {
 
 ValidatorTestHelper::ValidatorTestHelper() {
-  td_ = std::make_unique<TypeDeterminer>(mod);
+  td_ = std::make_unique<TypeDeterminer>(this);
 }
 
 ValidatorTestHelper::~ValidatorTestHelper() = default;
diff --git a/src/validator/validator_test_helper.h b/src/validator/validator_test_helper.h
index 25a59dd..6e40632 100644
--- a/src/validator/validator_test_helper.h
+++ b/src/validator/validator_test_helper.h
@@ -20,7 +20,7 @@
 #include <utility>
 #include <vector>
 
-#include "src/ast/builder.h"
+#include "src/program_builder.h"
 #include "src/type/void_type.h"
 #include "src/type_determiner.h"
 #include "src/validator/validator_impl.h"
@@ -28,7 +28,7 @@
 namespace tint {
 
 /// A helper for testing validation
-class ValidatorTestHelper : public ast::BuilderWithProgram {
+class ValidatorTestHelper : public ProgramBuilder {
  public:
   /// Constructor
   ValidatorTestHelper();
@@ -42,7 +42,8 @@
     if (val_) {
       return *val_;
     }
-    val_ = std::make_unique<ValidatorImpl>(program);
+    program_ = std::make_unique<Program>(std::move(*this));
+    val_ = std::make_unique<ValidatorImpl>(program_.get());
     for (auto* var : vars_for_testing_) {
       val_->RegisterVariableForTesting(var);
     }
@@ -62,6 +63,7 @@
 
  private:
   std::unique_ptr<TypeDeterminer> td_;
+  std::unique_ptr<Program> program_;
   std::unique_ptr<ValidatorImpl> val_;
   std::vector<ast::Variable*> vars_for_testing_;
 };
diff --git a/src/validator/validator_type_test.cc b/src/validator/validator_type_test.cc
index b1b063b..879e0c6 100644
--- a/src/validator/validator_type_test.cc
+++ b/src/validator/validator_type_test.cc
@@ -54,8 +54,9 @@
   AST().AddConstructedType(struct_type);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_TRUE(v.ValidateConstructedTypes(AST().ConstructedTypes()));
+  EXPECT_TRUE(v.ValidateConstructedTypes(program->AST().ConstructedTypes()));
 }
 
 TEST_F(ValidatorTypeTest, RuntimeArrayIsLastNoBlock_Fail) {
@@ -74,8 +75,9 @@
   AST().AddConstructedType(struct_type);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_FALSE(v.ValidateConstructedTypes(AST().ConstructedTypes()));
+  EXPECT_FALSE(v.ValidateConstructedTypes(program->AST().ConstructedTypes()));
   EXPECT_EQ(v.error(),
             "v-0031: a struct containing a runtime-sized array must be "
             "in the 'storage' storage class: 'Foo'");
@@ -102,8 +104,9 @@
   AST().AddConstructedType(struct_type);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_FALSE(v.ValidateConstructedTypes(AST().ConstructedTypes()));
+  EXPECT_FALSE(v.ValidateConstructedTypes(program->AST().ConstructedTypes()));
   EXPECT_EQ(v.error(),
             "12:34 v-0015: runtime arrays may only appear as the last member "
             "of a struct");
@@ -128,8 +131,9 @@
   AST().AddConstructedType(struct_type);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_FALSE(v.ValidateConstructedTypes(AST().ConstructedTypes()));
+  EXPECT_FALSE(v.ValidateConstructedTypes(program->AST().ConstructedTypes()));
   EXPECT_EQ(v.error(),
             "v-0015: runtime arrays may only appear as the last member "
             "of a struct");
@@ -154,8 +158,9 @@
   AST().AddConstructedType(struct_type);
 
   ValidatorImpl& v = Build();
+  const Program* program = v.program();
 
-  EXPECT_TRUE(v.ValidateConstructedTypes(AST().ConstructedTypes()));
+  EXPECT_TRUE(v.ValidateConstructedTypes(program->AST().ConstructedTypes()));
 }
 
 TEST_F(ValidatorTypeTest, RuntimeArrayInFunction_Fail) {
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index d005725..bfc6cd9 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -35,6 +35,7 @@
 #include "src/ast/if_statement.h"
 #include "src/ast/loop_statement.h"
 #include "src/ast/member_accessor_expression.h"
+#include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/struct.h"
@@ -43,6 +44,7 @@
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/program_builder.h"
 #include "src/type/access_control_type.h"
 #include "src/type/alias_type.h"
 #include "src/type/array_type.h"
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index 4505071..d1ca0ab 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -365,7 +365,8 @@
   /// @param func the function to check
   /// @returns true if there are output struct variables used in the function
   bool has_referenced_out_var_needing_struct(ast::Function* func);
-  /// Determines if any used module variable requires an input or output struct.
+  /// Determines if any used program variable requires an input or output
+  /// struct.
   /// @param func the function to check
   /// @returns true if an input or output struct is required.
   bool has_referenced_var_needing_struct(ast::Function* func);
diff --git a/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc b/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
index a0194ec..4d06c5f 100644
--- a/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
+++ b/src/writer/hlsl/generator_impl_intrinsic_texture_test.cc
@@ -15,7 +15,6 @@
 #include <memory>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
 #include "src/ast/intrinsic_texture_helper_test.h"
 #include "src/type/depth_texture_type.h"
 #include "src/type/multisampled_texture_type.h"
diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc
index e7907bc..dc455f1 100644
--- a/src/writer/hlsl/generator_impl_type_test.cc
+++ b/src/writer/hlsl/generator_impl_type_test.cc
@@ -63,9 +63,11 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.array<bool, 4>(), "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, arr, "ary")) << gen.error();
   EXPECT_EQ(result(), "bool ary[4]");
 }
 
@@ -99,59 +101,75 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_NameCollision) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.array<bool, 4>(), "bool")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, arr, "bool")) << gen.error();
   EXPECT_EQ(result(), "bool bool_tint_0[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Array_WithoutName) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.array<bool, 4>(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, arr, "")) << gen.error();
   EXPECT_EQ(result(), "bool[4]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_RuntimeArray) {
+  auto* arr = ty.array<bool>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.array<bool>(), "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, arr, "ary")) << gen.error();
   EXPECT_EQ(result(), "bool ary[]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type,
        DISABLED_EmitType_RuntimeArray_NameCollision) {
+  auto* arr = ty.array<bool>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.array<bool>(), "double")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, arr, "double")) << gen.error();
   EXPECT_EQ(result(), "bool double_tint_0[]");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Bool) {
+  auto* bool_ = ty.bool_();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.bool_(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, bool_, "")) << gen.error();
   EXPECT_EQ(result(), "bool");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_F32) {
+  auto* f32 = ty.f32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.f32(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, f32, "")) << gen.error();
   EXPECT_EQ(result(), "float");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_I32) {
+  auto* i32 = ty.i32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.i32(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, i32, "")) << gen.error();
   EXPECT_EQ(result(), "int");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Matrix) {
+  auto* mat2x3 = ty.mat2x3<f32>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.mat2x3<f32>(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, mat2x3, "")) << gen.error();
   EXPECT_EQ(result(), "float3x2");
 }
 
@@ -172,6 +190,7 @@
       ast::StructDecorationList{});
 
   auto* s = ty.struct_("S", str);
+
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitStructType(out, s, "S")) << gen.error();
@@ -189,6 +208,7 @@
       ast::StructDecorationList{});
 
   auto* s = ty.struct_("S", str);
+
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
@@ -203,6 +223,7 @@
       ast::StructDecorationList{});
 
   auto* s = ty.struct_("S", str);
+
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitType(out, s, "")) << gen.error();
@@ -223,6 +244,7 @@
                           ast::StructDecorationList{});
 
   auto* s = ty.struct_("S", str);
+
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitStructType(out, s, "S")) << gen.error();
@@ -244,6 +266,7 @@
       decos);
 
   auto* s = ty.struct_("S", str);
+
   GeneratorImpl& gen = Build();
 
   ASSERT_TRUE(gen.EmitStructType(out, s, "B")) << gen.error();
@@ -254,23 +277,29 @@
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_U32) {
+  auto* u32 = ty.u32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.u32(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, u32, "")) << gen.error();
   EXPECT_EQ(result(), "uint");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Vector) {
+  auto* vec3 = ty.vec3<f32>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.vec3<f32>(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, vec3, "")) << gen.error();
   EXPECT_EQ(result(), "float3");
 }
 
 TEST_F(HlslGeneratorImplTest_Type, EmitType_Void) {
+  auto* void_ = ty.void_();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(out, ty.void_(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(out, void_, "")) << gen.error();
   EXPECT_EQ(result(), "void");
 }
 
diff --git a/src/writer/hlsl/test_helper.h b/src/writer/hlsl/test_helper.h
index 530b261..344ee66 100644
--- a/src/writer/hlsl/test_helper.h
+++ b/src/writer/hlsl/test_helper.h
@@ -21,7 +21,7 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
+#include "src/program_builder.h"
 #include "src/type_determiner.h"
 #include "src/writer/hlsl/generator_impl.h"
 
@@ -31,9 +31,9 @@
 
 /// Helper class for testing
 template <typename BODY>
-class TestHelperBase : public BODY, public ast::BuilderWithProgram {
+class TestHelperBase : public BODY, public ProgramBuilder {
  public:
-  TestHelperBase() : td(program) {}
+  TestHelperBase() : td(this) {}
   ~TestHelperBase() = default;
 
   /// Builds and returns a GeneratorImpl from the program.
@@ -44,7 +44,8 @@
     if (gen_) {
       return *gen_;
     }
-    gen_ = std::make_unique<GeneratorImpl>(program);
+    program_ = std::make_unique<Program>(std::move(*this));
+    gen_ = std::make_unique<GeneratorImpl>(program_.get());
     return *gen_;
   }
 
@@ -63,6 +64,7 @@
   std::ostringstream pre;
 
  private:
+  std::unique_ptr<Program> program_;
   std::unique_ptr<GeneratorImpl> gen_;
 };
 using TestHelper = TestHelperBase<testing::Test>;
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index c570a3b..67a8843 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -40,6 +40,7 @@
 #include "src/ast/location_decoration.h"
 #include "src/ast/loop_statement.h"
 #include "src/ast/member_accessor_expression.h"
+#include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/struct_member_offset_decoration.h"
@@ -48,6 +49,7 @@
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/program.h"
 #include "src/type/access_control_type.h"
 #include "src/type/alias_type.h"
 #include "src/type/array_type.h"
diff --git a/src/writer/msl/generator_impl_intrinsic_texture_test.cc b/src/writer/msl/generator_impl_intrinsic_texture_test.cc
index a2ac3e4..50a407f 100644
--- a/src/writer/msl/generator_impl_intrinsic_texture_test.cc
+++ b/src/writer/msl/generator_impl_intrinsic_texture_test.cc
@@ -15,7 +15,6 @@
 #include <memory>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
 #include "src/ast/intrinsic_texture_helper_test.h"
 #include "src/type/depth_texture_type.h"
 #include "src/type/multisampled_texture_type.h"
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
index f3f85f2..fc26f40 100644
--- a/src/writer/msl/generator_impl_test.cc
+++ b/src/writer/msl/generator_impl_test.cc
@@ -82,6 +82,7 @@
 
 TEST_F(MslGeneratorImplTest, NameConflictWith_InputStructName) {
   auto* ident = Expr("func_main_in");
+
   GeneratorImpl& gen = Build();
 
   ASSERT_EQ(gen.generate_name("func_main_in"), "func_main_in");
@@ -124,43 +125,55 @@
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_alias) {
   auto* alias = ty.alias("a", ty.f32());
+
   GeneratorImpl& gen = Build();
 
   EXPECT_EQ(4u, gen.calculate_alignment_size(alias));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_array) {
+  auto* array = ty.array<f32, 4>();
+
   GeneratorImpl& gen = Build();
 
-  EXPECT_EQ(4u * 4u, gen.calculate_alignment_size(ty.array<f32, 4>()));
+  EXPECT_EQ(4u * 4u, gen.calculate_alignment_size(array));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_bool) {
+  auto* bool_ = ty.bool_();
+
   GeneratorImpl& gen = Build();
 
-  EXPECT_EQ(1u, gen.calculate_alignment_size(ty.bool_()));
+  EXPECT_EQ(1u, gen.calculate_alignment_size(bool_));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_f32) {
+  auto* f32 = ty.f32();
+
   GeneratorImpl& gen = Build();
 
-  EXPECT_EQ(4u, gen.calculate_alignment_size(ty.f32()));
+  EXPECT_EQ(4u, gen.calculate_alignment_size(f32));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_i32) {
+  auto* i32 = ty.i32();
+
   GeneratorImpl& gen = Build();
 
-  EXPECT_EQ(4u, gen.calculate_alignment_size(ty.i32()));
+  EXPECT_EQ(4u, gen.calculate_alignment_size(i32));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_matrix) {
+  auto* mat3x2 = ty.mat3x2<f32>();
+
   GeneratorImpl& gen = Build();
 
-  EXPECT_EQ(4u * 3u * 2u, gen.calculate_alignment_size(ty.mat3x2<f32>()));
+  EXPECT_EQ(4u * 3u * 2u, gen.calculate_alignment_size(mat3x2));
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_pointer) {
   type::Pointer ptr(ty.bool_(), ast::StorageClass::kPrivate);
+
   GeneratorImpl& gen = Build();
 
   EXPECT_EQ(0u, gen.calculate_alignment_size(&ptr));
@@ -203,9 +216,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, calculate_alignment_size_u32) {
+  auto* u32 = ty.u32();
+
   GeneratorImpl& gen = Build();
 
-  EXPECT_EQ(4u, gen.calculate_alignment_size(ty.u32()));
+  EXPECT_EQ(4u, gen.calculate_alignment_size(u32));
 }
 
 struct MslVectorSizeData {
@@ -221,6 +236,7 @@
   auto param = GetParam();
 
   type::Vector vec(ty.bool_(), param.elements);
+
   GeneratorImpl& gen = Build();
 
   EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
@@ -252,6 +268,7 @@
   auto param = GetParam();
 
   type::Vector vec(ty.u32(), param.elements);
+
   GeneratorImpl& gen = Build();
 
   EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
@@ -267,6 +284,7 @@
   auto param = GetParam();
 
   type::Vector vec(ty.f32(), param.elements);
+
   GeneratorImpl& gen = Build();
 
   EXPECT_EQ(param.byte_size, gen.calculate_alignment_size(&vec));
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index b065ad8..caa9e3e 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -66,9 +66,11 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.array<bool, 4>(), "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(arr, "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[4]");
 }
 
@@ -106,59 +108,74 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Array_NameCollision) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.array<bool, 4>(), "bool")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(arr, "bool")) << gen.error();
   EXPECT_EQ(gen.result(), "bool bool_tint_0[4]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Array_WithoutName) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.array<bool, 4>(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(arr, "")) << gen.error();
   EXPECT_EQ(gen.result(), "bool[4]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray) {
+  auto* arr = ty.array<bool, 1>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.array<bool, 1>(), "ary")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(arr, "ary")) << gen.error();
   EXPECT_EQ(gen.result(), "bool ary[1]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_RuntimeArray_NameCollision) {
+  auto* arr = ty.array<bool, 1>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.array<bool, 1>(), "discard_fragment"))
-      << gen.error();
+  ASSERT_TRUE(gen.EmitType(arr, "discard_fragment")) << gen.error();
   EXPECT_EQ(gen.result(), "bool discard_fragment_tint_0[1]");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Bool) {
+  auto* bool_ = ty.bool_();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.bool_(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(bool_, "")) << gen.error();
   EXPECT_EQ(gen.result(), "bool");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_F32) {
+  auto* f32 = ty.f32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.f32(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(f32, "")) << gen.error();
   EXPECT_EQ(gen.result(), "float");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_I32) {
+  auto* i32 = ty.i32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.i32(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(i32, "")) << gen.error();
   EXPECT_EQ(gen.result(), "int");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Matrix) {
+  auto* mat2x3 = ty.mat2x3<f32>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.mat2x3<f32>(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(mat2x3, "")) << gen.error();
   EXPECT_EQ(gen.result(), "float2x3");
 }
 
@@ -268,23 +285,29 @@
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_U32) {
+  auto* u32 = ty.u32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.u32(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(u32, "")) << gen.error();
   EXPECT_EQ(gen.result(), "uint");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Vector) {
+  auto* vec3 = ty.vec3<f32>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.vec3<f32>(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(vec3, "")) << gen.error();
   EXPECT_EQ(gen.result(), "float3");
 }
 
 TEST_F(MslGeneratorImplTest, EmitType_Void) {
+  auto* void_ = ty.void_();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.void_(), "")) << gen.error();
+  ASSERT_TRUE(gen.EmitType(void_, "")) << gen.error();
   EXPECT_EQ(gen.result(), "void");
 }
 
diff --git a/src/writer/msl/test_helper.h b/src/writer/msl/test_helper.h
index 5d91a3a..6fe1797 100644
--- a/src/writer/msl/test_helper.h
+++ b/src/writer/msl/test_helper.h
@@ -19,8 +19,8 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
-#include "src/program.h"
+#include "src/ast/module.h"
+#include "src/program_builder.h"
 #include "src/type_determiner.h"
 #include "src/writer/msl/generator_impl.h"
 
@@ -30,9 +30,9 @@
 
 /// Helper class for testing
 template <typename BASE>
-class TestHelperBase : public BASE, public ast::BuilderWithProgram {
+class TestHelperBase : public BASE, public ProgramBuilder {
  public:
-  TestHelperBase() : td(program) {}
+  TestHelperBase() : td(this) {}
   ~TestHelperBase() = default;
 
   /// Builds and returns a GeneratorImpl from the program.
@@ -43,7 +43,8 @@
     if (gen_) {
       return *gen_;
     }
-    gen_ = std::make_unique<GeneratorImpl>(program);
+    program_ = std::make_unique<Program>(std::move(*this));
+    gen_ = std::make_unique<GeneratorImpl>(program_.get());
     return *gen_;
   }
 
@@ -51,6 +52,7 @@
   TypeDeterminer td;
 
  private:
+  std::unique_ptr<Program> program_;
   std::unique_ptr<GeneratorImpl> gen_;
 };
 using TestHelper = TestHelperBase<testing::Test>;
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 8f946e9..3d1b071 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -44,6 +44,7 @@
 #include "src/ast/location_decoration.h"
 #include "src/ast/loop_statement.h"
 #include "src/ast/member_accessor_expression.h"
+#include "src/ast/module.h"
 #include "src/ast/null_literal.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
@@ -57,7 +58,9 @@
 #include "src/ast/unary_op_expression.h"
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/program.h"
 #include "src/type/access_control_type.h"
+#include "src/type/alias_type.h"
 #include "src/type/array_type.h"
 #include "src/type/bool_type.h"
 #include "src/type/depth_texture_type.h"
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 024e357..dee56c9 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -15,7 +15,6 @@
 #include <memory>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/float_literal.h"
 #include "src/ast/identifier_expression.h"
diff --git a/src/writer/spirv/builder_intrinsic_texture_test.cc b/src/writer/spirv/builder_intrinsic_texture_test.cc
index 4035d3f..49dab51 100644
--- a/src/writer/spirv/builder_intrinsic_texture_test.cc
+++ b/src/writer/spirv/builder_intrinsic_texture_test.cc
@@ -16,7 +16,6 @@
 
 #include "gmock/gmock.h"
 #include "spirv-tools/libspirv.hpp"
-#include "src/ast/builder.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/intrinsic_texture_helper_test.h"
 #include "src/ast/stage_decoration.h"
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index ebad710..0bf323b 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -66,17 +66,19 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedAlias) {
-  auto* alias_type = ty.alias("my_type", ty.f32());
+  auto* i32 = ty.i32();
+  auto* f32 = ty.f32();
+  auto* alias_type = ty.alias("my_type", f32);
 
   spirv::Builder& b = Build();
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(alias_type), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(b.GenerateTypeIfNeeded(alias_type), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
@@ -163,9 +165,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateBool) {
+  auto* bool_ = ty.bool_();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.bool_());
+  auto id = b.GenerateTypeIfNeeded(bool_);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -175,20 +179,25 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedBool) {
+  auto* bool_ = ty.bool_();
+  auto* i32 = ty.i32();
+
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.bool_()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.bool_()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(bool_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateF32) {
+  auto* f32 = ty.f32();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.f32());
+  auto id = b.GenerateTypeIfNeeded(f32);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -198,20 +207,25 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedF32) {
+  auto* f32 = ty.f32();
+  auto* i32 = ty.i32();
+
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateI32) {
+  auto* i32 = ty.i32();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.i32());
+  auto id = b.GenerateTypeIfNeeded(i32);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -221,20 +235,25 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedI32) {
+  auto* f32 = ty.f32();
+  auto* i32 = ty.i32();
+
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateMatrix) {
+  auto* mat2x3 = ty.mat2x3<f32>();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.mat2x3<f32>());
+  auto id = b.GenerateTypeIfNeeded(mat2x3);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -247,12 +266,13 @@
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedMatrix) {
   auto* mat = ty.mat4x3<i32>();
+  auto* i32 = ty.i32();
 
   spirv::Builder& b = Build();
 
   EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 3u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 3u);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(b.GenerateTypeIfNeeded(mat), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
@@ -498,9 +518,11 @@
 }
 
 TEST_F(BuilderTest_Type, GenerateU32) {
+  auto* u32 = ty.u32();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.u32());
+  auto id = b.GenerateTypeIfNeeded(u32);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -510,20 +532,25 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedU32) {
+  auto* u32 = ty.u32();
+  auto* f32 = ty.f32();
+
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.u32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.f32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(f32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.u32()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(u32), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateVector) {
+  auto* vec = ty.vec3<f32>();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.vec3<f32>());
+  auto id = b.GenerateTypeIfNeeded(vec);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -534,22 +561,25 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedVector) {
-  auto* vec_type = ty.vec3<i32>();
+  auto* vec = ty.vec3<i32>();
+  auto* i32 = ty.i32();
 
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(vec_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(vec_type), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(vec), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
 TEST_F(BuilderTest_Type, GenerateVoid) {
+  auto* void_ = ty.void_();
+
   spirv::Builder& b = Build();
 
-  auto id = b.GenerateTypeIfNeeded(ty.void_());
+  auto id = b.GenerateTypeIfNeeded(void_);
   ASSERT_FALSE(b.has_error()) << b.error();
   EXPECT_EQ(id, 1u);
 
@@ -559,13 +589,16 @@
 }
 
 TEST_F(BuilderTest_Type, ReturnsGeneratedVoid) {
+  auto* void_ = ty.void_();
+  auto* i32 = ty.i32();
+
   spirv::Builder& b = Build();
 
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.void_()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.i32()), 2u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(i32), 2u);
   ASSERT_FALSE(b.has_error()) << b.error();
-  EXPECT_EQ(b.GenerateTypeIfNeeded(ty.void_()), 1u);
+  EXPECT_EQ(b.GenerateTypeIfNeeded(void_), 1u);
   ASSERT_FALSE(b.has_error()) << b.error();
 }
 
diff --git a/src/writer/spirv/test_helper.h b/src/writer/spirv/test_helper.h
index 52c1aae..fdd084f 100644
--- a/src/writer/spirv/test_helper.h
+++ b/src/writer/spirv/test_helper.h
@@ -19,8 +19,8 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
-#include "src/program.h"
+#include "src/ast/module.h"
+#include "src/program_builder.h"
 #include "src/type_determiner.h"
 #include "src/writer/spirv/builder.h"
 
@@ -30,9 +30,9 @@
 
 /// Helper class for testing
 template <typename BASE>
-class TestHelperBase : public ast::BuilderWithProgram, public BASE {
+class TestHelperBase : public ProgramBuilder, public BASE {
  public:
-  TestHelperBase() : td(program) {}
+  TestHelperBase() : td(this) {}
   ~TestHelperBase() override = default;
 
   /// Builds and returns a spirv::Builder from the program.
@@ -43,7 +43,8 @@
     if (spirv_builder) {
       return *spirv_builder;
     }
-    spirv_builder = std::make_unique<spirv::Builder>(program);
+    program_ = std::make_unique<Program>(std::move(*this));
+    spirv_builder = std::make_unique<spirv::Builder>(program_.get());
     return *spirv_builder;
   }
 
@@ -58,6 +59,7 @@
   }
 
  private:
+  std::unique_ptr<Program> program_;
   std::unique_ptr<spirv::Builder> spirv_builder;
 };
 using TestHelper = TestHelperBase<testing::Test>;
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 91b9e43..e2add77 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -40,6 +40,7 @@
 #include "src/ast/location_decoration.h"
 #include "src/ast/loop_statement.h"
 #include "src/ast/member_accessor_expression.h"
+#include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
@@ -56,7 +57,9 @@
 #include "src/ast/variable.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/ast/workgroup_decoration.h"
+#include "src/program.h"
 #include "src/type/access_control_type.h"
+#include "src/type/alias_type.h"
 #include "src/type/array_type.h"
 #include "src/type/bool_type.h"
 #include "src/type/depth_texture_type.h"
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 56ea3db..a6056fb 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -57,9 +57,11 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Array) {
+  auto* arr = ty.array<bool, 4>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.array<bool, 4>())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(arr)) << gen.error();
   EXPECT_EQ(gen.result(), "array<bool, 4>");
 }
 
@@ -132,30 +134,38 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Bool) {
+  auto* bool_ = ty.bool_();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.bool_())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(bool_)) << gen.error();
   EXPECT_EQ(gen.result(), "bool");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_F32) {
+  auto* f32 = ty.f32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.f32())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(f32)) << gen.error();
   EXPECT_EQ(gen.result(), "f32");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_I32) {
+  auto* i32 = ty.i32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.i32())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(i32)) << gen.error();
   EXPECT_EQ(gen.result(), "i32");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Matrix) {
+  auto* mat2x3 = ty.mat2x3<f32>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.mat2x3<f32>())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(mat2x3)) << gen.error();
   EXPECT_EQ(gen.result(), "mat2x3<f32>");
 }
 
@@ -222,23 +232,29 @@
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_U32) {
+  auto* u32 = ty.u32();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.u32())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(u32)) << gen.error();
   EXPECT_EQ(gen.result(), "u32");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Vector) {
+  auto* vec3 = ty.vec3<f32>();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.vec3<f32>())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(vec3)) << gen.error();
   EXPECT_EQ(gen.result(), "vec3<f32>");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitType_Void) {
+  auto* void_ = ty.void_();
+
   GeneratorImpl& gen = Build();
 
-  ASSERT_TRUE(gen.EmitType(ty.void_())) << gen.error();
+  ASSERT_TRUE(gen.EmitType(void_)) << gen.error();
   EXPECT_EQ(gen.result(), "void");
 }
 
diff --git a/src/writer/wgsl/test_helper.h b/src/writer/wgsl/test_helper.h
index 5195c04..0cbb2a5 100644
--- a/src/writer/wgsl/test_helper.h
+++ b/src/writer/wgsl/test_helper.h
@@ -19,7 +19,7 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/ast/builder.h"
+#include "src/program_builder.h"
 #include "src/type_determiner.h"
 #include "src/writer/wgsl/generator_impl.h"
 
@@ -29,9 +29,9 @@
 
 /// Helper class for testing
 template <typename BASE>
-class TestHelperBase : public BASE, public ast::BuilderWithProgram {
+class TestHelperBase : public BASE, public ProgramBuilder {
  public:
-  TestHelperBase() : td(program) {}
+  TestHelperBase() : td(this) {}
 
   ~TestHelperBase() = default;
 
@@ -43,7 +43,8 @@
     if (gen_) {
       return *gen_;
     }
-    gen_ = std::make_unique<GeneratorImpl>(program);
+    program_ = std::make_unique<Program>(std::move(*this));
+    gen_ = std::make_unique<GeneratorImpl>(program_.get());
     return *gen_;
   }
 
@@ -51,6 +52,7 @@
   TypeDeterminer td;
 
  private:
+  std::unique_ptr<Program> program_;
   std::unique_ptr<GeneratorImpl> gen_;
 };
 using TestHelper = TestHelperBase<testing::Test>;