[tint][ir] Pass ir::Module by reference, not pointer

This must never be never null.

Bug: tint:1698
Change-Id: I9c297cd79665003ef316703d2118b7d8293cf6c1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/152547
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point.cc b/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
index 1da621f..86bcf0e 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
@@ -24,25 +24,25 @@
 
 namespace {
 
-void Run(ir::Module* ir) {
-    for (auto* func : ir->functions) {
+void Run(ir::Module& ir) {
+    for (auto* func : ir.functions) {
         if (func->Stage() != Function::PipelineStage::kUndefined) {
             return;
         }
     }
 
-    ir::Builder builder(*ir);
-    auto* ep = builder.Function("unused_entry_point", ir->Types().void_(),
+    ir::Builder builder{ir};
+    auto* ep = builder.Function("unused_entry_point", ir.Types().void_(),
                                 Function::PipelineStage::kCompute, std::array{1u, 1u, 1u});
     ep->Block()->Append(builder.Return(ep));
 }
 
 }  // namespace
 
-Result<SuccessType> AddEmptyEntryPoint(Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "AddEmptyEntryPoint transform");
+Result<SuccessType> AddEmptyEntryPoint(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "AddEmptyEntryPoint transform");
     if (!result) {
-        return result;
+        return result.Failure();
     }
 
     Run(ir);
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point.h b/src/tint/lang/core/ir/transform/add_empty_entry_point.h
index 3f419bb..8b9e0ef 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point.h
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point.h
@@ -29,7 +29,7 @@
 /// Add an empty entry point to the module, if no other entry points exist.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> AddEmptyEntryPoint(Module* module);
+Result<SuccessType> AddEmptyEntryPoint(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
index b9be567..fe183f1 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
@@ -32,20 +32,20 @@
 /// PIMPL state for the transform.
 struct State {
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// Process the module.
     void Process() {
         // Find module-scope variables that need to be replaced.
-        if (ir->root_block) {
+        if (ir.root_block) {
             Vector<Instruction*, 4> to_remove;
-            for (auto inst : *ir->root_block) {
+            for (auto inst : *ir.root_block) {
                 auto* var = inst->As<Var>();
                 if (!var) {
                     continue;
@@ -67,7 +67,7 @@
         }
 
         // Find function parameters that need to be replaced.
-        for (auto* func : ir->functions) {
+        for (auto* func : ir.functions) {
             for (uint32_t index = 0; index < func->Params().Length(); index++) {
                 auto* param = func->Params()[index];
                 auto* storage_texture = param->Type()->As<core::type::StorageTexture>();
@@ -90,8 +90,8 @@
         auto bp = old_var->BindingPoint();
         new_var->SetBindingPoint(bp->group, bp->binding);
         new_var->InsertBefore(old_var);
-        if (auto name = ir->NameOf(old_var)) {
-            ir->SetName(new_var, name.NameView());
+        if (auto name = ir.NameOf(old_var)) {
+            ir.SetName(new_var, name.NameView());
         }
 
         // Replace all uses of the old variable with the new one.
@@ -111,8 +111,8 @@
         auto* rgba8 = ty.Get<core::type::StorageTexture>(
             bgra8->dim(), core::TexelFormat::kRgba8Unorm, bgra8->access(), bgra8->type());
         auto* new_param = b.FunctionParam(rgba8);
-        if (auto name = ir->NameOf(old_param)) {
-            ir->SetName(new_param, name.NameView());
+        if (auto name = ir.NameOf(old_param)) {
+            ir.SetName(new_param, name.NameView());
         }
 
         Vector<FunctionParam*, 4> new_params = func->Params();
@@ -170,8 +170,8 @@
 
 }  // namespace
 
-Result<SuccessType> Bgra8UnormPolyfill(Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "Bgra8UnormPolyfill transform");
+Result<SuccessType> Bgra8UnormPolyfill(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "Bgra8UnormPolyfill transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
index 2b67c52..1311056 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
@@ -30,7 +30,7 @@
 /// bgra8unorm to rgba8unorm, inserting swizzles before and after texture accesses as necessary.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> Bgra8UnormPolyfill(Module* module);
+Result<SuccessType> Bgra8UnormPolyfill(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.cc b/src/tint/lang/core/ir/transform/binary_polyfill.cc
index 1988398..cd8e09c 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.cc
@@ -33,16 +33,16 @@
     const BinaryPolyfillConfig& config;
 
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The symbol table.
-    SymbolTable& sym{ir->symbols};
+    SymbolTable& sym{ir.symbols};
 
     /// Map from integer type to its divide helper function.
     Hashmap<const type::Type*, Function*, 4> int_div_helpers{};
@@ -54,7 +54,7 @@
     void Process() {
         // Find the binary instructions that need to be polyfilled.
         Vector<ir::Binary*, 64> worklist;
-        for (auto* inst : ir->instructions.Objects()) {
+        for (auto* inst : ir.instructions.Objects()) {
             if (!inst->Alive()) {
                 continue;
             }
@@ -98,8 +98,8 @@
 
             if (replacement != binary->Result()) {
                 // Replace the old binary instruction result with the new value.
-                if (auto name = ir->NameOf(binary->Result())) {
-                    ir->SetName(replacement, name);
+                if (auto name = ir.NameOf(binary->Result())) {
+                    ir.SetName(replacement, name);
                 }
                 binary->Result()->ReplaceAllUsesWith(replacement);
                 binary->Destroy();
@@ -232,8 +232,8 @@
 
 }  // namespace
 
-Result<SuccessType> BinaryPolyfill(Module* ir, const BinaryPolyfillConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "BinaryPolyfill transform");
+Result<SuccessType> BinaryPolyfill(Module& ir, const BinaryPolyfillConfig& config) {
+    auto result = ValidateAndDumpIfNeeded(ir, "BinaryPolyfill transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.h b/src/tint/lang/core/ir/transform/binary_polyfill.h
index ff41255..cc2dc1a 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.h
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.h
@@ -39,7 +39,7 @@
 /// @param module the module to transform
 /// @param config the polyfill configuration
 /// @returns success or failure
-Result<SuccessType> BinaryPolyfill(Module* module, const BinaryPolyfillConfig& config);
+Result<SuccessType> BinaryPolyfill(Module& module, const BinaryPolyfillConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.cc b/src/tint/lang/core/ir/transform/binding_remapper.cc
index 24118ee..747f2db 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper.cc
@@ -28,19 +28,19 @@
 
 namespace {
 
-Result<SuccessType> Run(ir::Module* ir, const BindingRemapperOptions& options) {
+Result<SuccessType> Run(ir::Module& ir, const BindingRemapperOptions& options) {
     if (!options.access_controls.empty()) {
         return Failure{"remapping access controls is currently unsupported"};
     }
     if (options.binding_points.empty()) {
         return Success;
     }
-    if (!ir->root_block) {
+    if (!ir.root_block) {
         return Success;
     }
 
     // Find binding resources.
-    for (auto inst : *ir->root_block) {
+    for (auto inst : *ir.root_block) {
         auto* var = inst->As<Var>();
         if (!var || !var->Alive()) {
             continue;
@@ -63,8 +63,8 @@
 
 }  // namespace
 
-Result<SuccessType> BindingRemapper(Module* ir, const BindingRemapperOptions& options) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "BindingRemapper transform");
+Result<SuccessType> BindingRemapper(Module& ir, const BindingRemapperOptions& options) {
+    auto result = ValidateAndDumpIfNeeded(ir, "BindingRemapper transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.h b/src/tint/lang/core/ir/transform/binding_remapper.h
index 232d2de..5d7a2a0 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.h
+++ b/src/tint/lang/core/ir/transform/binding_remapper.h
@@ -31,7 +31,7 @@
 /// @param module the module to transform
 /// @param options the remapping options
 /// @returns success or failure
-Result<SuccessType> BindingRemapper(Module* module, const BindingRemapperOptions& options);
+Result<SuccessType> BindingRemapper(Module& module, const BindingRemapperOptions& options);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.cc b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
index 78125bc..eef33a4 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
@@ -28,16 +28,16 @@
 
 namespace {
 
-void Run(Module* ir) {
-    Builder builder(*ir);
+void Run(Module& ir) {
+    Builder builder{ir};
 
-    if (!ir->root_block) {
+    if (!ir.root_block) {
         return;
     }
 
     // Loop over module-scope declarations, looking for storage or uniform buffers.
     Vector<Var*, 8> buffer_variables;
-    for (auto inst : *ir->root_block) {
+    for (auto inst : *ir.root_block) {
         auto* var = inst->As<Var>();
         if (!var) {
             continue;
@@ -67,8 +67,8 @@
         } else {
             // The original struct might be used in other places, so create a new block-decorated
             // struct that wraps the original struct.
-            members.Push(ir->Types().Get<core::type::StructMember>(
-                /* name */ ir->symbols.New(),
+            members.Push(ir.Types().Get<core::type::StructMember>(
+                /* name */ ir.symbols.New(),
                 /* type */ store_ty,
                 /* index */ 0u,
                 /* offset */ 0u,
@@ -79,8 +79,8 @@
         }
 
         // Create the block-decorated struct.
-        auto* block_struct = ir->Types().Get<core::type::Struct>(
-            /* name */ ir->symbols.New(),
+        auto* block_struct = ir.Types().Get<core::type::Struct>(
+            /* name */ ir.symbols.New(),
             /* members */ members,
             /* align */ store_ty->Align(),
             /* size */ tint::RoundUp(store_ty->Align(), store_ty->Size()),
@@ -89,7 +89,7 @@
 
         // Replace the old variable declaration with one that uses the block-decorated struct type.
         auto* new_var =
-            builder.Var(ir->Types().ptr(ptr->AddressSpace(), block_struct, ptr->Access()));
+            builder.Var(ir.Types().ptr(ptr->AddressSpace(), block_struct, ptr->Access()));
         if (var->BindingPoint()) {
             new_var->SetBindingPoint(var->BindingPoint()->group, var->BindingPoint()->binding);
         }
@@ -111,8 +111,8 @@
 
 }  // namespace
 
-Result<SuccessType> BlockDecoratedStructs(Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "BlockDecoratedStructs transform");
+Result<SuccessType> BlockDecoratedStructs(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "BlockDecoratedStructs transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.h b/src/tint/lang/core/ir/transform/block_decorated_structs.h
index e4a2216..c8a6f69 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.h
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.h
@@ -31,7 +31,7 @@
 /// existing store type in a new structure if necessary.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> BlockDecoratedStructs(Module* module);
+Result<SuccessType> BlockDecoratedStructs(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index bf9626b..992cf86 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -34,22 +34,22 @@
     const BuiltinPolyfillConfig& config;
 
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The symbol table.
-    SymbolTable& sym{ir->symbols};
+    SymbolTable& sym{ir.symbols};
 
     /// Process the module.
     void Process() {
         // Find the builtin call instructions that may need to be polyfilled.
         Vector<ir::CoreBuiltinCall*, 4> worklist;
-        for (auto* inst : ir->instructions.Objects()) {
+        for (auto* inst : ir.instructions.Objects()) {
             if (!inst->Alive()) {
                 continue;
             }
@@ -124,8 +124,8 @@
             TINT_ASSERT_OR_RETURN(replacement);
 
             // Replace the old builtin call result with the new value.
-            if (auto name = ir->NameOf(builtin->Result())) {
-                ir->SetName(replacement, name);
+            if (auto name = ir.NameOf(builtin->Result())) {
+                ir.SetName(replacement, name);
             }
             builtin->Result()->ReplaceAllUsesWith(replacement);
             builtin->Destroy();
@@ -457,8 +457,8 @@
 
 }  // namespace
 
-Result<SuccessType> BuiltinPolyfill(Module* ir, const BuiltinPolyfillConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "BuiltinPolyfill transform");
+Result<SuccessType> BuiltinPolyfill(Module& ir, const BuiltinPolyfillConfig& config) {
+    auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h
index 5f52875..da24f49 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.h
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h
@@ -47,7 +47,7 @@
 /// @param module the module to transform
 /// @param config the polyfill configuration
 /// @returns success or failure
-Result<SuccessType> BuiltinPolyfill(Module* module, const BuiltinPolyfillConfig& config);
+Result<SuccessType> BuiltinPolyfill(Module& module, const BuiltinPolyfillConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper.cc b/src/tint/lang/core/ir/transform/demote_to_helper.cc
index 5a8bb39..5608828 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper.cc
@@ -31,33 +31,29 @@
 /// PIMPL state for the transform.
 struct State {
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The global "has not discarded" flag.
     Var* continue_execution = nullptr;
 
     /// Map from function to a flag that indicates whether it (transitively) contains a discard.
-    Hashmap<Function*, bool, 4> function_discard_status;
+    Hashmap<Function*, bool, 4> function_discard_status{};
 
     /// Set of functions that have been processed.
-    Hashset<Function*, 4> processed_functions;
-
-    /// Constructor
-    /// @param mod the module
-    explicit State(Module* mod) : ir(mod) {}
+    Hashset<Function*, 4> processed_functions{};
 
     /// Process the module.
     void Process() {
         // Check each fragment shader entry point for discard instruction, potentially inside
         // functions called (transitively) by the entry point.
         Vector<Function*, 4> to_process;
-        for (auto* func : ir->functions) {
+        for (auto* func : ir.functions) {
             // If the function is a fragment shader that contains a discard, we need to process it.
             if (func->Stage() == Function::PipelineStage::kFragment) {
                 if (HasDiscard(func)) {
@@ -203,8 +199,8 @@
 
 }  // namespace
 
-Result<SuccessType> DemoteToHelper(Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "DemoteToHelper transform");
+Result<SuccessType> DemoteToHelper(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "DemoteToHelper transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper.h b/src/tint/lang/core/ir/transform/demote_to_helper.h
index 8866962..4ba5887 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper.h
+++ b/src/tint/lang/core/ir/transform/demote_to_helper.h
@@ -34,7 +34,7 @@
 /// buffers and textures.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> DemoteToHelper(Module* module);
+Result<SuccessType> DemoteToHelper(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/helper_test.h b/src/tint/lang/core/ir/transform/helper_test.h
index 4363b94..b188428 100644
--- a/src/tint/lang/core/ir/transform/helper_test.h
+++ b/src/tint/lang/core/ir/transform/helper_test.h
@@ -37,7 +37,7 @@
     template <typename TRANSFORM, typename... ARGS>
     void Run(TRANSFORM&& transform_func, ARGS&&... args) {
         // Run the transform.
-        auto result = transform_func(&mod, args...);
+        auto result = transform_func(mod, args...);
         EXPECT_TRUE(result) << result.Failure();
         if (!result) {
             return;
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
index a62490d..921d828 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -35,16 +35,16 @@
     const ExternalTextureOptions& options;
 
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The symbol table.
-    SymbolTable& sym{ir->symbols};
+    SymbolTable& sym{ir.symbols};
 
     /// The gamma transfer parameters structure.
     const core::type::Struct* gamma_transfer_params_struct = nullptr;
@@ -64,9 +64,9 @@
     /// Process the module.
     void Process() {
         // Find module-scope variables that need to be replaced.
-        if (ir->root_block) {
+        if (ir.root_block) {
             Vector<Instruction*, 4> to_remove;
-            for (auto inst : *ir->root_block) {
+            for (auto inst : *ir.root_block) {
                 auto* var = inst->As<Var>();
                 if (!var) {
                     continue;
@@ -83,7 +83,7 @@
         }
 
         // Find function parameters that need to be replaced.
-        for (auto* func : ir->functions) {
+        for (auto* func : ir.functions) {
             for (uint32_t index = 0; index < func->Params().Length(); index++) {
                 auto* param = func->Params()[index];
                 if (param->Type()->Is<core::type::ExternalTexture>()) {
@@ -101,7 +101,7 @@
     /// Replace an external texture variable declaration.
     /// @param old_var the variable declaration to replace
     void ReplaceVar(Var* old_var) {
-        auto name = ir->NameOf(old_var);
+        auto name = ir.NameOf(old_var);
         auto bp = old_var->BindingPoint();
         auto itr = options.bindings_map.find(bp.value());
         TINT_ASSERT_OR_RETURN(itr != options.bindings_map.end());
@@ -112,7 +112,7 @@
         plane_0->SetBindingPoint(bp->group, bp->binding);
         plane_0->InsertBefore(old_var);
         if (name) {
-            ir->SetName(plane_0, name.Name() + "_plane0");
+            ir.SetName(plane_0, name.Name() + "_plane0");
         }
 
         // Create a sampled texture for the second plane.
@@ -121,7 +121,7 @@
                                  new_binding_points.plane_1.binding);
         plane_1->InsertBefore(old_var);
         if (name) {
-            ir->SetName(plane_1, name.Name() + "_plane1");
+            ir.SetName(plane_1, name.Name() + "_plane1");
         }
 
         // Create a uniform buffer for the external texture parameters.
@@ -130,7 +130,7 @@
                                                  new_binding_points.params.binding);
         external_texture_params->InsertBefore(old_var);
         if (name) {
-            ir->SetName(external_texture_params, name.Name() + "_params");
+            ir.SetName(external_texture_params, name.Name() + "_params");
         }
 
         // Replace all uses of the old variable with the new ones.
@@ -143,24 +143,24 @@
     /// @param old_param the function parameter to replace
     /// @param index the index of the function parameter
     void ReplaceParameter(Function* func, FunctionParam* old_param, uint32_t index) {
-        auto name = ir->NameOf(old_param);
+        auto name = ir.NameOf(old_param);
 
         // Create a sampled texture for the first plane.
         auto* plane_0 = b.FunctionParam(SampledTexture());
         if (name) {
-            ir->SetName(plane_0, name.Name() + "_plane0");
+            ir.SetName(plane_0, name.Name() + "_plane0");
         }
 
         // Create a sampled texture for the second plane.
         auto* plane_1 = b.FunctionParam(SampledTexture());
         if (name) {
-            ir->SetName(plane_1, name.Name() + "_plane1");
+            ir.SetName(plane_1, name.Name() + "_plane1");
         }
 
         // Create the external texture parameters struct.
         auto* external_texture_params = b.FunctionParam(ExternalTextureParams());
         if (name) {
-            ir->SetName(external_texture_params, name.Name() + "_params");
+            ir.SetName(external_texture_params, name.Name() + "_params");
         }
 
         Vector<FunctionParam*, 4> new_params;
@@ -568,8 +568,8 @@
 
 }  // namespace
 
-Result<SuccessType> MultiplanarExternalTexture(Module* ir, const ExternalTextureOptions& options) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "MultiplanarExternalTexture transform");
+Result<SuccessType> MultiplanarExternalTexture(Module& ir, const ExternalTextureOptions& options) {
+    auto result = ValidateAndDumpIfNeeded(ir, "MultiplanarExternalTexture transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
index 4837fcc..6114d5e 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
@@ -33,7 +33,7 @@
 /// @param module the module to transform
 /// @param options the external texture options
 /// @returns success or failure
-Result<SuccessType> MultiplanarExternalTexture(Module* module,
+Result<SuccessType> MultiplanarExternalTexture(Module& module,
                                                const ExternalTextureOptions& options);
 
 }  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 72ea908..2996d52 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -37,13 +37,13 @@
     const RobustnessConfig& config;
 
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// Process the module.
     void Process() {
@@ -52,7 +52,7 @@
         Vector<ir::LoadVectorElement*, 64> vector_loads;
         Vector<ir::StoreVectorElement*, 64> vector_stores;
         Vector<ir::CoreBuiltinCall*, 64> texture_calls;
-        for (auto* inst : ir->instructions.Objects()) {
+        for (auto* inst : ir.instructions.Objects()) {
             if (inst->Alive()) {
                 tint::Switch(
                     inst,  //
@@ -335,8 +335,8 @@
 
 }  // namespace
 
-Result<SuccessType> Robustness(Module* ir, const RobustnessConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "Robustness transform");
+Result<SuccessType> Robustness(Module& ir, const RobustnessConfig& config) {
+    auto result = ValidateAndDumpIfNeeded(ir, "Robustness transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/robustness.h b/src/tint/lang/core/ir/transform/robustness.h
index ebe4f91..71a7c22 100644
--- a/src/tint/lang/core/ir/transform/robustness.h
+++ b/src/tint/lang/core/ir/transform/robustness.h
@@ -60,7 +60,7 @@
 /// @param module the module to transform
 /// @param config the robustness configuration
 /// @returns success or failure
-Result<SuccessType> Robustness(Module* module, const RobustnessConfig& config);
+Result<SuccessType> Robustness(Module& module, const RobustnessConfig& config);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/shader_io.cc b/src/tint/lang/core/ir/transform/shader_io.cc
index 3edc00d..df9987c 100644
--- a/src/tint/lang/core/ir/transform/shader_io.cc
+++ b/src/tint/lang/core/ir/transform/shader_io.cc
@@ -75,23 +75,19 @@
 /// PIMPL state for the transform.
 struct State {
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
     /// The set of struct members that need to have their IO attributes stripped.
-    Hashset<const core::type::StructMember*, 8> members_to_strip;
+    Hashset<const core::type::StructMember*, 8> members_to_strip{};
 
     /// The entry point currently being processed.
     Function* func = nullptr;
 
     /// The backend state object for the current entry point.
-    std::unique_ptr<ShaderIOBackendState> backend;
-
-    /// Constructor
-    /// @param mod the module
-    explicit State(Module* mod) : ir(mod) {}
+    std::unique_ptr<ShaderIOBackendState> backend{};
 
     /// Process an entry point.
     /// @param f the original entry point function
@@ -109,7 +105,7 @@
         std::optional<uint32_t> vertex_point_size_index;
         if (func->Stage() == Function::PipelineStage::kVertex && backend->NeedsVertexPointSize()) {
             vertex_point_size_index =
-                backend->AddOutput(ir->symbols.New("vertex_point_size"), ty.f32(),
+                backend->AddOutput(ir.symbols.New("vertex_point_size"), ty.f32(),
                                    {{}, {}, {BuiltinValue::kPointSize}, {}, false});
         }
 
@@ -118,10 +114,10 @@
 
         // Rename the old function and remove its pipeline stage and workgroup size, as we will be
         // wrapping it with a new entry point.
-        auto name = ir->NameOf(func).Name();
+        auto name = ir.NameOf(func).Name();
         auto stage = func->Stage();
         auto wgsize = func->WorkgroupSize();
-        ir->SetName(func, name + "_inner");
+        ir.SetName(func, name + "_inner");
         func->SetStage(Function::PipelineStage::kUndefined);
         func->ClearWorkgroupSize();
 
@@ -156,7 +152,7 @@
                         func->Stage() != Function::PipelineStage::kFragment) {
                         attributes.interpolation = {};
                     }
-                    backend->AddInput(ir->symbols.Register(name), member->Type(), attributes);
+                    backend->AddInput(ir.symbols.Register(name), member->Type(), attributes);
                     members_to_strip.Add(member);
                 }
             } else {
@@ -175,7 +171,7 @@
                 attributes.invariant = param->Invariant();
                 param->SetInvariant(false);
 
-                auto name = ir->NameOf(param);
+                auto name = ir.NameOf(param);
                 backend->AddInput(name, param->Type(), std::move(attributes));
             }
         }
@@ -194,7 +190,7 @@
                 if (attributes.interpolation && func->Stage() != Function::PipelineStage::kVertex) {
                     attributes.interpolation = {};
                 }
-                backend->AddOutput(ir->symbols.Register(name), member->Type(), attributes);
+                backend->AddOutput(ir.symbols.Register(name), member->Type(), attributes);
                 members_to_strip.Add(member);
             }
         } else {
@@ -213,7 +209,7 @@
             attributes.invariant = func->ReturnInvariant();
             func->SetReturnInvariant(false);
 
-            backend->AddOutput(ir->symbols.New(), func->ReturnType(), std::move(attributes));
+            backend->AddOutput(ir.symbols.New(), func->ReturnType(), std::move(attributes));
         }
     }
 
@@ -265,9 +261,9 @@
 
 }  // namespace
 
-void RunShaderIOBase(Module* module, std::function<MakeBackendStateFunc> make_backend_state) {
-    State state(module);
-    for (auto* func : module->functions) {
+void RunShaderIOBase(Module& module, std::function<MakeBackendStateFunc> make_backend_state) {
+    State state{module};
+    for (auto* func : module.functions) {
         // Only process entry points.
         if (func->Stage() == Function::PipelineStage::kUndefined) {
             continue;
diff --git a/src/tint/lang/core/ir/transform/shader_io.h b/src/tint/lang/core/ir/transform/shader_io.h
index 22210c0..108bf87 100644
--- a/src/tint/lang/core/ir/transform/shader_io.h
+++ b/src/tint/lang/core/ir/transform/shader_io.h
@@ -28,7 +28,7 @@
     /// Constructor
     /// @param mod the IR module
     /// @param f the entry point function
-    ShaderIOBackendState(Module* mod, Function* f) : ir(mod), func(f) {}
+    ShaderIOBackendState(Module& mod, Function* f) : ir(mod), func(f) {}
 
     /// Destructor
     virtual ~ShaderIOBackendState();
@@ -82,13 +82,13 @@
 
   protected:
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The original entry point function.
     Function* func = nullptr;
@@ -101,11 +101,11 @@
 };
 
 /// The signature for a function that creates a backend state object.
-using MakeBackendStateFunc = std::unique_ptr<ShaderIOBackendState>(Module*, Function*);
+using MakeBackendStateFunc = std::unique_ptr<ShaderIOBackendState>(Module&, Function*);
 
 /// @param module the module to transform
 /// @param make_backend_state a function that creates a backend state object
-void RunShaderIOBase(Module* module, std::function<MakeBackendStateFunc> make_backend_state);
+void RunShaderIOBase(Module& module, std::function<MakeBackendStateFunc> make_backend_state);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/core/ir/transform/std140.cc b/src/tint/lang/core/ir/transform/std140.cc
index dbf0c0e..5961802 100644
--- a/src/tint/lang/core/ir/transform/std140.cc
+++ b/src/tint/lang/core/ir/transform/std140.cc
@@ -33,16 +33,16 @@
 /// PIMPL state for the transform.
 struct State {
     /// The IR module.
-    Module* ir = nullptr;
+    Module& ir;
 
     /// The IR builder.
-    Builder b{*ir};
+    Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The symbol table.
-    SymbolTable& sym{ir->symbols};
+    SymbolTable& sym{ir.symbols};
 
     /// Map from original type to a new type with decomposed matrices.
     Hashmap<const core::type::Type*, const core::type::Type*, 4> rewritten_types{};
@@ -55,13 +55,13 @@
 
     /// Process the module.
     void Process() {
-        if (!ir->root_block) {
+        if (!ir.root_block) {
             return;
         }
 
         // Find uniform buffers that contain matrices that need to be decomposed.
         Vector<Var*, 8> buffer_variables;
-        for (auto inst : *ir->root_block) {
+        for (auto inst : *ir.root_block) {
             auto* var = inst->As<Var>();
             if (!var || !var->Alive()) {
                 continue;
@@ -83,8 +83,8 @@
             auto* store_type = var->Result()->Type()->As<core::type::Pointer>()->StoreType();
             auto* new_var = b.Var(ty.ptr(uniform, RewriteType(store_type)));
             new_var->SetBindingPoint(bp->group, bp->binding);
-            if (auto name = ir->NameOf(var)) {
-                ir->SetName(new_var->Result(), name);
+            if (auto name = ir.NameOf(var)) {
+                ir.SetName(new_var->Result(), name);
             }
 
             // Replace every instruction that uses the original variable.
@@ -329,8 +329,8 @@
 
 }  // namespace
 
-Result<SuccessType> Std140(Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "Std140 transform");
+Result<SuccessType> Std140(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "Std140 transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/std140.h b/src/tint/lang/core/ir/transform/std140.h
index 768d0ac..db6545c 100644
--- a/src/tint/lang/core/ir/transform/std140.h
+++ b/src/tint/lang/core/ir/transform/std140.h
@@ -30,7 +30,7 @@
 /// GLSL's std140 layout rules.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> Std140(Module* module);
+Result<SuccessType> Std140(Module& module);
 
 }  // namespace tint::core::ir::transform
 
diff --git a/src/tint/lang/msl/writer/printer/helper_test.h b/src/tint/lang/msl/writer/printer/helper_test.h
index 4623149..0214845 100644
--- a/src/tint/lang/msl/writer/printer/helper_test.h
+++ b/src/tint/lang/msl/writer/printer/helper_test.h
@@ -48,7 +48,7 @@
 template <typename BASE>
 class MslPrinterTestHelperBase : public BASE {
   public:
-    MslPrinterTestHelperBase() : writer_(&mod) {}
+    MslPrinterTestHelperBase() : writer_(mod) {}
 
     /// The test module.
     core::ir::Module mod;
@@ -70,7 +70,7 @@
     /// Run the writer on the IR module and validate the result.
     /// @returns true if generation and validation succeeded
     bool Generate() {
-        auto raised = raise::Raise(&mod);
+        auto raised = raise::Raise(mod);
         if (!raised) {
             err_ = raised.Failure().reason.str();
             return false;
diff --git a/src/tint/lang/msl/writer/printer/printer.cc b/src/tint/lang/msl/writer/printer/printer.cc
index 1bb4532..4e85d3b 100644
--- a/src/tint/lang/msl/writer/printer/printer.cc
+++ b/src/tint/lang/msl/writer/printer/printer.cc
@@ -63,12 +63,12 @@
     TINT_UNIMPLEMENTED() << "unhandled case in Switch(): " \
                          << (object_ptr ? object_ptr->TypeInfo().name : "<null>")
 
-Printer::Printer(core::ir::Module* module) : ir_(module) {}
+Printer::Printer(core::ir::Module& module) : ir_(module) {}
 
 Printer::~Printer() = default;
 
 tint::Result<SuccessType> Printer::Generate() {
-    auto valid = core::ir::ValidateAndDumpIfNeeded(*ir_, "MSL writer");
+    auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "MSL writer");
     if (!valid) {
         return std::move(valid.Failure());
     }
@@ -80,12 +80,12 @@
     }
 
     // Emit module-scope declarations.
-    if (ir_->root_block) {
-        EmitBlockInstructions(ir_->root_block);
+    if (ir_.root_block) {
+        EmitBlockInstructions(ir_.root_block);
     }
 
     // Emit functions.
-    for (auto* func : ir_->functions) {
+    for (auto* func : ir_.functions) {
         EmitFunction(func);
     }
 
@@ -135,7 +135,7 @@
         // TODO(dsinclair): Handle return type attributes
 
         EmitType(out, func->ReturnType());
-        out << " " << ir_->NameOf(func).Name() << "() {";
+        out << " " << ir_.NameOf(func).Name() << "() {";
 
         // TODO(dsinclair): Emit Function parameters
     }
@@ -252,7 +252,7 @@
             return;
     }
 
-    auto name = ir_->NameOf(v);
+    auto name = ir_.NameOf(v);
 
     EmitType(out, ptr->UnwrapPtr());
     out << " " << name.Name();
@@ -276,11 +276,11 @@
 void Printer::EmitIf(core::ir::If* if_) {
     // Emit any nodes that need to be used as PHI nodes
     for (auto* phi : if_->Results()) {
-        if (!ir_->NameOf(phi).IsValid()) {
-            ir_->SetName(phi, ir_->symbols.New());
+        if (!ir_.NameOf(phi).IsValid()) {
+            ir_.SetName(phi, ir_.symbols.New());
         }
 
-        auto name = ir_->NameOf(phi);
+        auto name = ir_.NameOf(phi);
 
         auto out = Line();
         EmitType(out, phi->Type());
@@ -313,7 +313,7 @@
         auto* phi = results[i];
         auto* val = args[i];
 
-        Line() << ir_->NameOf(phi).Name() << " = " << Expr(val) << ";";
+        Line() << ir_.NameOf(phi).Name() << " = " << Expr(val) << ";";
     }
 }
 
@@ -531,7 +531,7 @@
         std::string name;
         do {
             name = UniqueIdentifier("tint_pad");
-        } while (str->FindMember(ir_->symbols.Get(name)));
+        } while (str->FindMember(ir_.symbols.Get(name)));
 
         auto out = Line(&str_buf);
         add_byte_offset_comment(out, msl_offset);
@@ -743,7 +743,7 @@
 }
 
 std::string Printer::UniqueIdentifier(const std::string& prefix /* = "" */) {
-    return ir_->symbols.New(prefix).Name();
+    return ir_.symbols.New(prefix).Name();
 }
 
 TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
@@ -825,12 +825,12 @@
             return;
         }
     } else {
-        auto mod_name = ir_->NameOf(value);
+        auto mod_name = ir_.NameOf(value);
         if (value->Usages().IsEmpty() && !mod_name.IsValid()) {
             // Drop phonies.
         } else {
             if (mod_name.Name().empty()) {
-                mod_name = ir_->symbols.New("v");
+                mod_name = ir_.symbols.New("v");
             }
 
             auto out = Line();
@@ -879,7 +879,7 @@
         auto* result = inst->Result();
         // Only values with a single usage can be inlined.
         // Named values are not inlined, as we want to emit the name for a let.
-        if (result->Usages().Count() == 1 && !ir_->NameOf(result).IsValid()) {
+        if (result->Usages().Count() == 1 && !ir_.NameOf(result).IsValid()) {
             if (sequenced) {
                 // The value comes from a sequenced instruction.  Don't inline.
             } else {
diff --git a/src/tint/lang/msl/writer/printer/printer.h b/src/tint/lang/msl/writer/printer/printer.h
index 1e1d84c..5b6d2e4 100644
--- a/src/tint/lang/msl/writer/printer/printer.h
+++ b/src/tint/lang/msl/writer/printer/printer.h
@@ -44,7 +44,7 @@
   public:
     /// Constructor
     /// @param module the Tint IR module to generate
-    explicit Printer(core::ir::Module* module);
+    explicit Printer(core::ir::Module& module);
     ~Printer() override;
 
     /// @returns success or failure
@@ -162,7 +162,7 @@
     /// Map of builtin structure to unique generated name
     std::unordered_map<const core::type::Struct*, std::string> builtin_struct_names_;
 
-    core::ir::Module* const ir_;
+    core::ir::Module& ir_;
 
     /// The buffer holding preamble text
     TextBuffer preamble_buffer_;
diff --git a/src/tint/lang/msl/writer/raise/raise.cc b/src/tint/lang/msl/writer/raise/raise.cc
index bd87432..9190f0d 100644
--- a/src/tint/lang/msl/writer/raise/raise.cc
+++ b/src/tint/lang/msl/writer/raise/raise.cc
@@ -18,7 +18,7 @@
 
 namespace tint::msl::raise {
 
-Result<SuccessType> Raise(core::ir::Module*) {
+Result<SuccessType> Raise(core::ir::Module&) {
     // #define RUN_TRANSFORM(name)
     //     do {
     //         auto result = core::ir::transform::name(module);
diff --git a/src/tint/lang/msl/writer/raise/raise.h b/src/tint/lang/msl/writer/raise/raise.h
index 124b5f2..0da1f45 100644
--- a/src/tint/lang/msl/writer/raise/raise.h
+++ b/src/tint/lang/msl/writer/raise/raise.h
@@ -30,7 +30,7 @@
 /// Raise a core IR module to the MSL dialect of the IR.
 /// @param mod the core IR module to raise to MSL dialect
 /// @returns success or failure
-Result<SuccessType> Raise(core::ir::Module* mod);
+Result<SuccessType> Raise(core::ir::Module& mod);
 
 }  // namespace tint::msl::raise
 
diff --git a/src/tint/lang/msl/writer/writer.cc b/src/tint/lang/msl/writer/writer.cc
index 4f1e460..aa69687 100644
--- a/src/tint/lang/msl/writer/writer.cc
+++ b/src/tint/lang/msl/writer/writer.cc
@@ -41,13 +41,13 @@
         auto ir = converted.Move();
 
         // Raise the IR to the MSL dialect.
-        auto raised = raise::Raise(&ir);
+        auto raised = raise::Raise(ir);
         if (!raised) {
             return raised.Failure();
         }
 
         // Generate the MSL code.
-        auto impl = std::make_unique<Printer>(&ir);
+        auto impl = std::make_unique<Printer>(ir);
         auto result = impl->Generate();
         if (!result) {
             return result.Failure();
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc b/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
index 1affcf3..318671e 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer.cc
@@ -190,7 +190,7 @@
     if (builder_.Build()) {
         auto& module = builder_.Module();
         writer_.WriteHeader(module.IdBound());
-        writer_.WriteModule(&module);
+        writer_.WriteModule(module);
         return true;
     }
     return false;
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index 76bfba3..94c6d60 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -3055,7 +3055,7 @@
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::ImageMemory);
     } else {
-        TINT_ICE() << "unexpected barrier builtin type " << core::str(builtin->Fn());
+        TINT_ICE() << "unexpected barrier builtin type " << builtin->Fn();
         return false;
     }
 
diff --git a/src/tint/lang/spirv/writer/ast_printer/helper_test.h b/src/tint/lang/spirv/writer/ast_printer/helper_test.h
index 28ac79b..2bb30da 100644
--- a/src/tint/lang/spirv/writer/ast_printer/helper_test.h
+++ b/src/tint/lang/spirv/writer/ast_printer/helper_test.h
@@ -101,7 +101,7 @@
     void Validate(Builder& b) {
         BinaryWriter writer;
         writer.WriteHeader(b.Module().IdBound());
-        writer.WriteModule(&b.Module());
+        writer.WriteModule(b.Module());
         auto binary = writer.Result();
 
         std::string spv_errors;
diff --git a/src/tint/lang/spirv/writer/common/binary_writer.cc b/src/tint/lang/spirv/writer/common/binary_writer.cc
index 92d527f..d9e83fa 100644
--- a/src/tint/lang/spirv/writer/common/binary_writer.cc
+++ b/src/tint/lang/spirv/writer/common/binary_writer.cc
@@ -28,9 +28,9 @@
 
 BinaryWriter::~BinaryWriter() = default;
 
-void BinaryWriter::WriteModule(const Module* module) {
-    out_.reserve(module->TotalSize());
-    module->Iterate([this](const Instruction& inst) { this->process_instruction(inst); });
+void BinaryWriter::WriteModule(const Module& module) {
+    out_.reserve(module.TotalSize());
+    module.Iterate([this](const Instruction& inst) { this->process_instruction(inst); });
 }
 
 void BinaryWriter::WriteInstruction(const Instruction& inst) {
diff --git a/src/tint/lang/spirv/writer/common/binary_writer.h b/src/tint/lang/spirv/writer/common/binary_writer.h
index d3de4b5..b2d4d45 100644
--- a/src/tint/lang/spirv/writer/common/binary_writer.h
+++ b/src/tint/lang/spirv/writer/common/binary_writer.h
@@ -36,7 +36,7 @@
     /// Writes the given module data into a binary. Note, this does not emit the SPIR-V header. You
     /// **must** call WriteHeader() before WriteModule() if you want the SPIR-V to be emitted.
     /// @param module the module to assemble from
-    void WriteModule(const Module* module);
+    void WriteModule(const Module& module);
 
     /// Writes the given instruction into the binary.
     /// @param inst the instruction to assemble
diff --git a/src/tint/lang/spirv/writer/common/binary_writer_test.cc b/src/tint/lang/spirv/writer/common/binary_writer_test.cc
index 6b44272..c991677 100644
--- a/src/tint/lang/spirv/writer/common/binary_writer_test.cc
+++ b/src/tint/lang/spirv/writer/common/binary_writer_test.cc
@@ -38,7 +38,7 @@
 
     m.PushAnnot(spv::Op::OpKill, {Operand(2.4f)});
     BinaryWriter bw;
-    bw.WriteModule(&m);
+    bw.WriteModule(m);
 
     auto res = bw.Result();
     ASSERT_EQ(res.size(), 2u);
@@ -52,7 +52,7 @@
 
     m.PushAnnot(spv::Op::OpKill, {Operand(2u)});
     BinaryWriter bw;
-    bw.WriteModule(&m);
+    bw.WriteModule(m);
 
     auto res = bw.Result();
     ASSERT_EQ(res.size(), 2u);
@@ -64,7 +64,7 @@
 
     m.PushAnnot(spv::Op::OpKill, {Operand("my_string")});
     BinaryWriter bw;
-    bw.WriteModule(&m);
+    bw.WriteModule(m);
 
     auto res = bw.Result();
     ASSERT_EQ(res.size(), 4u);
@@ -89,7 +89,7 @@
 
     m.PushAnnot(spv::Op::OpKill, {Operand("mystring")});
     BinaryWriter bw;
-    bw.WriteModule(&m);
+    bw.WriteModule(m);
 
     auto res = bw.Result();
     ASSERT_EQ(res.size(), 4u);
diff --git a/src/tint/lang/spirv/writer/common/helper_test.h b/src/tint/lang/spirv/writer/common/helper_test.h
index 6597f9d..9969eff 100644
--- a/src/tint/lang/spirv/writer/common/helper_test.h
+++ b/src/tint/lang/spirv/writer/common/helper_test.h
@@ -76,7 +76,7 @@
 template <typename BASE>
 class SpirvWriterTestHelperBase : public BASE {
   public:
-    SpirvWriterTestHelperBase() : writer_(&mod, false) {}
+    SpirvWriterTestHelperBase() : writer_(mod, false) {}
 
     /// The test module.
     core::ir::Module mod;
@@ -103,7 +103,7 @@
     /// @param options the optional writer options to use when raising the IR
     /// @returns true if generation and validation succeeded
     bool Generate(Printer& writer, Options options = {}) {
-        auto raised = raise::Raise(&mod, options);
+        auto raised = raise::Raise(mod, options);
         if (!raised) {
             err_ = raised.Failure().reason.str();
             return false;
diff --git a/src/tint/lang/spirv/writer/common/spv_dump_test.cc b/src/tint/lang/spirv/writer/common/spv_dump_test.cc
index 3fcba23..0f36a1b 100644
--- a/src/tint/lang/spirv/writer/common/spv_dump_test.cc
+++ b/src/tint/lang/spirv/writer/common/spv_dump_test.cc
@@ -58,7 +58,7 @@
 std::string DumpModule(Module& module) {
     BinaryWriter writer;
     writer.WriteHeader(module.IdBound());
-    writer.WriteModule(&module);
+    writer.WriteModule(module);
     return Disassemble(writer.Result());
 }
 
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 5141be9..7561f70 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -150,11 +150,11 @@
 
 }  // namespace
 
-Printer::Printer(core::ir::Module* module, bool zero_init_workgroup_mem)
-    : ir_(module), b_(*module), zero_init_workgroup_memory_(zero_init_workgroup_mem) {}
+Printer::Printer(core::ir::Module& module, bool zero_init_workgroup_mem)
+    : ir_(module), b_(module), zero_init_workgroup_memory_(zero_init_workgroup_mem) {}
 
 Result<std::vector<uint32_t>> Printer::Generate() {
-    auto valid = core::ir::ValidateAndDumpIfNeeded(*ir_, "SPIR-V writer");
+    auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "SPIR-V writer");
     if (!valid) {
         return valid.Failure();
     }
@@ -168,19 +168,19 @@
     // TODO(crbug.com/tint/1906): Emit extensions.
 
     // Emit module-scope declarations.
-    if (ir_->root_block) {
-        EmitRootBlock(ir_->root_block);
+    if (ir_.root_block) {
+        EmitRootBlock(ir_.root_block);
     }
 
     // Emit functions.
-    for (auto* func : ir_->functions) {
+    for (auto* func : ir_.functions) {
         EmitFunction(func);
     }
 
     // Serialize the module into binary SPIR-V.
     BinaryWriter writer;
     writer.WriteHeader(module_.IdBound(), kWriterVersion);
-    writer.WriteModule(&module_);
+    writer.WriteModule(module_);
     return std::move(writer.Result());
 }
 
@@ -240,7 +240,7 @@
     auto id = Constant(constant->Value());
 
     // Set the name for the SPIR-V result ID if provided in the module.
-    if (auto name = ir_->NameOf(constant)) {
+    if (auto name = ir_.NameOf(constant)) {
         module_.PushDebug(spv::Op::OpName, {id, Operand(name.Name())});
     }
 
@@ -324,7 +324,7 @@
 }
 
 uint32_t Printer::Type(const core::type::Type* ty) {
-    ty = DedupType(ty, ir_->Types());
+    ty = DedupType(ty, ir_.Types());
     return types_.GetOrCreate(ty, [&] {
         auto id = module_.NextId();
         Switch(
@@ -526,7 +526,7 @@
     auto id = Value(func);
 
     // Emit the function name.
-    module_.PushDebug(spv::Op::OpName, {id, Operand(ir_->NameOf(func).Name())});
+    module_.PushDebug(spv::Op::OpName, {id, Operand(ir_.NameOf(func).Name())});
 
     // Emit OpEntryPoint and OpExecutionMode declarations if needed.
     if (func->Stage() != core::ir::Function::PipelineStage::kUndefined) {
@@ -545,7 +545,7 @@
         auto param_id = Value(param);
         params.push_back(Instruction(spv::Op::OpFunctionParameter, {param_type_id, param_id}));
         function_type.param_type_ids.Push(param_type_id);
-        if (auto name = ir_->NameOf(param)) {
+        if (auto name = ir_.NameOf(param)) {
             module_.PushDebug(spv::Op::OpName, {param_id, Operand(name.Name())});
         }
     }
@@ -603,11 +603,11 @@
             return;
     }
 
-    OperandList operands = {U32Operand(stage), id, ir_->NameOf(func).Name()};
+    OperandList operands = {U32Operand(stage), id, ir_.NameOf(func).Name()};
 
     // Add the list of all referenced shader IO variables.
-    if (ir_->root_block) {
-        for (auto* global : *ir_->root_block) {
+    if (ir_.root_block) {
+        for (auto* global : *ir_.root_block) {
             auto* var = global->As<core::ir::Var>();
             if (!var) {
                 continue;
@@ -725,7 +725,7 @@
 
         // Set the name for the SPIR-V result ID if provided in the module.
         if (inst->Result() && !inst->Is<core::ir::Var>()) {
-            if (auto name = ir_->NameOf(inst)) {
+            if (auto name = ir_.NameOf(inst)) {
                 module_.PushDebug(spv::Op::OpName, {Value(inst), Operand(name.Name())});
             }
         }
@@ -1425,13 +1425,13 @@
         case core::BuiltinFn::kSubgroupBallot:
             module_.PushCapability(SpvCapabilityGroupNonUniformBallot);
             op = spv::Op::OpGroupNonUniformBallot;
-            operands.push_back(Constant(ir_->constant_values.Get(u32(spv::Scope::Subgroup))));
-            operands.push_back(Constant(ir_->constant_values.Get(true)));
+            operands.push_back(Constant(ir_.constant_values.Get(u32(spv::Scope::Subgroup))));
+            operands.push_back(Constant(ir_.constant_values.Get(true)));
             break;
         case core::BuiltinFn::kSubgroupBroadcast:
             module_.PushCapability(SpvCapabilityGroupNonUniformBallot);
             op = spv::Op::OpGroupNonUniformBroadcast;
-            operands.push_back(Constant(ir_->constant_values.Get(u32(spv::Scope::Subgroup))));
+            operands.push_back(Constant(ir_.constant_values.Get(u32(spv::Scope::Subgroup))));
             break;
         case core::BuiltinFn::kTan:
             glsl_ext_inst(GLSLstd450Tan);
@@ -1606,7 +1606,7 @@
 void Printer::EmitLoadVectorElement(core::ir::LoadVectorElement* load) {
     auto* vec_ptr_ty = load->From()->Type()->As<core::type::Pointer>();
     auto* el_ty = load->Result()->Type();
-    auto* el_ptr_ty = ir_->Types().ptr(vec_ptr_ty->AddressSpace(), el_ty, vec_ptr_ty->Access());
+    auto* el_ptr_ty = ir_.Types().ptr(vec_ptr_ty->AddressSpace(), el_ty, vec_ptr_ty->Access());
     auto el_ptr_id = module_.NextId();
     current_function_.push_inst(
         spv::Op::OpAccessChain,
@@ -1725,7 +1725,7 @@
 void Printer::EmitStoreVectorElement(core::ir::StoreVectorElement* store) {
     auto* vec_ptr_ty = store->To()->Type()->As<core::type::Pointer>();
     auto* el_ty = store->Value()->Type();
-    auto* el_ptr_ty = ir_->Types().ptr(vec_ptr_ty->AddressSpace(), el_ty, vec_ptr_ty->Access());
+    auto* el_ptr_ty = ir_.Types().ptr(vec_ptr_ty->AddressSpace(), el_ty, vec_ptr_ty->Access());
     auto el_ptr_id = module_.NextId();
     current_function_.push_inst(
         spv::Op::OpAccessChain,
@@ -1897,7 +1897,7 @@
     }
 
     // Set the name if present.
-    if (auto name = ir_->NameOf(var)) {
+    if (auto name = ir_.NameOf(var)) {
         module_.PushDebug(spv::Op::OpName, {id, Operand(name.Name())});
     }
 }
diff --git a/src/tint/lang/spirv/writer/printer/printer.h b/src/tint/lang/spirv/writer/printer/printer.h
index b087781..7512685 100644
--- a/src/tint/lang/spirv/writer/printer/printer.h
+++ b/src/tint/lang/spirv/writer/printer/printer.h
@@ -81,7 +81,7 @@
     /// @param module the Tint IR module to generate
     /// @param zero_init_workgroup_memory `true` to initialize all the variables in the Workgroup
     ///                                   storage class with OpConstantNull
-    Printer(core::ir::Module* module, bool zero_init_workgroup_memory);
+    Printer(core::ir::Module& module, bool zero_init_workgroup_memory);
 
     /// @returns the generated SPIR-V binary on success, or failure
     tint::Result<std::vector<uint32_t>> Generate();
@@ -279,7 +279,7 @@
     /// @returns the label ID
     uint32_t GetTerminatorBlockLabel(core::ir::Terminator* t);
 
-    core::ir::Module* ir_;
+    core::ir::Module& ir_;
     core::ir::Builder b_;
     writer::Module module_;
     BinaryWriter writer_;
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
index 89f90f3..678397f 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -44,19 +44,19 @@
 /// PIMPL state for the transform.
 struct State {
     /// The IR module.
-    core::ir::Module* ir = nullptr;
+    core::ir::Module& ir;
 
     /// The IR builder.
-    core::ir::Builder b{*ir};
+    core::ir::Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// Process the module.
     void Process() {
         // Find the builtins that need replacing.
         Vector<core::ir::CoreBuiltinCall*, 4> worklist;
-        for (auto* inst : ir->instructions.Objects()) {
+        for (auto* inst : ir.instructions.Objects()) {
             if (!inst->Alive()) {
                 continue;
             }
@@ -160,8 +160,8 @@
             TINT_ASSERT_OR_RETURN(replacement);
 
             // Replace the old builtin result with the new value.
-            if (auto name = ir->NameOf(builtin->Result())) {
-                ir->SetName(replacement, name);
+            if (auto name = ir.NameOf(builtin->Result())) {
+                ir.SetName(replacement, name);
             }
             builtin->Result()->ReplaceAllUsesWith(replacement);
             builtin->Destroy();
@@ -172,7 +172,7 @@
     /// @param value the literal value
     /// @returns the literal operand
     LiteralOperand* Literal(u32 value) {
-        return ir->values.Create<LiteralOperand>(b.ConstantValue(value));
+        return ir.values.Create<LiteralOperand>(b.ConstantValue(value));
     }
 
     /// Handle an `arrayLength()` builtin.
@@ -255,7 +255,7 @@
 
                 // Construct the atomicCompareExchange result structure.
                 call = b.Construct(
-                    core::type::CreateAtomicCompareExchangeResult(ty, ir->symbols, int_ty),
+                    core::type::CreateAtomicCompareExchangeResult(ty, ir.symbols, int_ty),
                     Vector{original, compare->Result()});
                 break;
             }
@@ -851,8 +851,8 @@
 
 }  // namespace
 
-Result<SuccessType> BuiltinPolyfill(core::ir::Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "BuiltinPolyfill transform");
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
     if (!result) {
         return result.Failure();
     }
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.h b/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
index 2a3b206..469d6a4 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.h
@@ -34,7 +34,7 @@
 /// SPIR-V backend intrinsic functions.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> BuiltinPolyfill(core::ir::Module* module);
+Result<SuccessType> BuiltinPolyfill(core::ir::Module& module);
 
 /// LiteralOperand is a type of constant value that is intended to be emitted as a literal in
 /// the SPIR-V instruction stream.
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
index 23f943d..35353ec 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
@@ -28,14 +28,14 @@
 
 namespace {
 
-void Run(core::ir::Module* ir) {
-    core::ir::Builder b(*ir);
+void Run(core::ir::Module& ir) {
+    core::ir::Builder b{ir};
 
     // Find the instructions that use implicit splats and either modify them in place or record them
     // to be replaced in a second pass.
     Vector<core::ir::Binary*, 4> binary_worklist;
     Vector<core::ir::CoreBuiltinCall*, 4> builtin_worklist;
-    for (auto* inst : ir->instructions.Objects()) {
+    for (auto* inst : ir.instructions.Objects()) {
         if (!inst->Alive()) {
             continue;
         }
@@ -99,8 +99,8 @@
                 vts->AppendArg(binary->LHS());
                 vts->AppendArg(binary->RHS());
             }
-            if (auto name = ir->NameOf(binary)) {
-                ir->SetName(vts->Result(), name);
+            if (auto name = ir.NameOf(binary)) {
+                ir.SetName(vts->Result(), name);
             }
             binary->Result()->ReplaceAllUsesWith(vts->Result());
             binary->ReplaceWith(vts);
@@ -131,8 +131,8 @@
 
 }  // namespace
 
-Result<SuccessType> ExpandImplicitSplats(core::ir::Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "ExpandImplicitSplats transform");
+Result<SuccessType> ExpandImplicitSplats(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "ExpandImplicitSplats transform");
     if (!result) {
         return result.Failure();
     }
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h
index f9ecdb6..0a58bfe 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.h
@@ -31,7 +31,7 @@
 /// instructions and binary instructions where not supported by SPIR-V.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> ExpandImplicitSplats(core::ir::Module* module);
+Result<SuccessType> ExpandImplicitSplats(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
index a514416..b475ebb 100644
--- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
+++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
@@ -31,13 +31,13 @@
 
 namespace {
 
-void Run(core::ir::Module* ir) {
-    core::ir::Builder b(*ir);
+void Run(core::ir::Module& ir) {
+    core::ir::Builder b{ir};
 
     // Find the instructions that need to be modified.
     Vector<core::ir::Binary*, 4> binary_worklist;
     Vector<core::ir::Convert*, 4> convert_worklist;
-    for (auto* inst : ir->instructions.Objects()) {
+    for (auto* inst : ir.instructions.Objects()) {
         if (!inst->Alive()) {
             continue;
         }
@@ -64,8 +64,8 @@
 
         // Helper to replace the instruction with a new one.
         auto replace = [&](core::ir::Instruction* inst) {
-            if (auto name = ir->NameOf(binary)) {
-                ir->SetName(inst->Result(), name);
+            if (auto name = ir.NameOf(binary)) {
+                ir.SetName(inst->Result(), name);
             }
             binary->Result()->ReplaceAllUsesWith(inst->Result());
             binary->ReplaceWith(inst);
@@ -142,8 +142,8 @@
 
         // Reconstruct the result matrix from the converted columns.
         auto* construct = b.Construct(out_mat, std::move(args));
-        if (auto name = ir->NameOf(convert)) {
-            ir->SetName(construct->Result(), name);
+        if (auto name = ir.NameOf(convert)) {
+            ir.SetName(construct->Result(), name);
         }
         convert->Result()->ReplaceAllUsesWith(construct->Result());
         convert->ReplaceWith(construct);
@@ -153,8 +153,8 @@
 
 }  // namespace
 
-Result<SuccessType> HandleMatrixArithmetic(core::ir::Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "HandleMatrixArithmetic transform");
+Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "HandleMatrixArithmetic transform");
     if (!result) {
         return result.Failure();
     }
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h
index 1b27c9b..40cf739 100644
--- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h
+++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.h
@@ -31,7 +31,7 @@
 /// SPIR-V intrinsics or polyfills.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> HandleMatrixArithmetic(core::ir::Module* module);
+Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.cc b/src/tint/lang/spirv/writer/raise/merge_return.cc
index ee08c71..75e40e6 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return.cc
@@ -33,13 +33,13 @@
 /// PIMPL state for the transform, for a single function.
 struct State {
     /// The IR module.
-    core::ir::Module* ir = nullptr;
+    core::ir::Module& ir;
 
     /// The IR builder.
-    core::ir::Builder b{*ir};
+    core::ir::Builder b{ir};
 
     /// The type manager.
-    core::type::Manager& ty{ir->Types()};
+    core::type::Manager& ty{ir.Types()};
 
     /// The "has not returned" flag.
     core::ir::Var* continue_execution = nullptr;
@@ -53,11 +53,7 @@
     core::ir::Return* fn_return = nullptr;
 
     /// A set of control instructions that transitively hold a return instruction
-    Hashset<core::ir::ControlInstruction*, 8> holds_return_;
-
-    /// Constructor
-    /// @param mod the module
-    explicit State(core::ir::Module* mod) : ir(mod) {}
+    Hashset<core::ir::ControlInstruction*, 8> holds_return_{};
 
     /// Process the function.
     /// @param fn the function to process
@@ -290,14 +286,14 @@
 
 }  // namespace
 
-Result<SuccessType> MergeReturn(core::ir::Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "MergeReturn transform");
+Result<SuccessType> MergeReturn(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "MergeReturn transform");
     if (!result) {
         return result;
     }
 
     // Process each function.
-    for (auto* fn : ir->functions) {
+    for (auto* fn : ir.functions) {
         State{ir}.Process(fn);
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.h b/src/tint/lang/spirv/writer/raise/merge_return.h
index abc98ad..182165c 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.h
+++ b/src/tint/lang/spirv/writer/raise/merge_return.h
@@ -31,7 +31,7 @@
 /// at the end of the function.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> MergeReturn(core::ir::Module* module);
+Result<SuccessType> MergeReturn(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 3324eb6..6a5c39f 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -34,7 +34,7 @@
 
 namespace tint::spirv::writer::raise {
 
-Result<SuccessType> Raise(core::ir::Module* module, const Options& options) {
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
diff --git a/src/tint/lang/spirv/writer/raise/raise.h b/src/tint/lang/spirv/writer/raise/raise.h
index f1213e8..18aa923 100644
--- a/src/tint/lang/spirv/writer/raise/raise.h
+++ b/src/tint/lang/spirv/writer/raise/raise.h
@@ -32,7 +32,7 @@
 /// @param module the core IR module to raise to SPIR-V dialect
 /// @param options the SPIR-V writer options
 /// @returns success or failure
-Result<SuccessType> Raise(core::ir::Module* module, const Options& options);
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 4f798f3..9d814d8 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -47,7 +47,7 @@
     core::ir::Value* frag_depth_clamp_args = nullptr;
 
     /// Constructor
-    StateImpl(core::ir::Module* mod, core::ir::Function* f, const ShaderIOConfig& cfg)
+    StateImpl(core::ir::Module& mod, core::ir::Function* f, const ShaderIOConfig& cfg)
         : ShaderIOBackendState(mod, f), config(cfg) {}
 
     /// Destructor
@@ -66,7 +66,7 @@
                   const char* name_suffix) {
         for (auto io : entries) {
             StringStream name;
-            name << ir->NameOf(func).Name();
+            name << ir.NameOf(func).Name();
 
             if (io.attributes.builtin) {
                 // SampleMask must be an array for Vulkan.
@@ -168,10 +168,10 @@
             }
 
             // Declare the struct.
-            auto* str = ty.Struct(ir->symbols.Register("FragDepthClampArgs"),
+            auto* str = ty.Struct(ir.symbols.Register("FragDepthClampArgs"),
                                   {
-                                      {ir->symbols.Register("min"), ty.f32()},
-                                      {ir->symbols.Register("max"), ty.f32()},
+                                      {ir.symbols.Register("min"), ty.f32()},
+                                      {ir.symbols.Register("max"), ty.f32()},
                                   });
             str->SetStructFlag(core::type::kBlock);
 
@@ -195,13 +195,13 @@
 };
 }  // namespace
 
-Result<SuccessType> ShaderIO(core::ir::Module* ir, const ShaderIOConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "ShaderIO transform");
+Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
+    auto result = ValidateAndDumpIfNeeded(ir, "ShaderIO transform");
     if (!result) {
         return result;
     }
 
-    core::ir::transform::RunShaderIOBase(ir, [&](core::ir::Module* mod, core::ir::Function* func) {
+    core::ir::transform::RunShaderIOBase(ir, [&](core::ir::Module& mod, core::ir::Function* func) {
         return std::make_unique<StateImpl>(mod, func, config);
     });
 
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.h b/src/tint/lang/spirv/writer/raise/shader_io.h
index ee8247c..adfd325 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.h
+++ b/src/tint/lang/spirv/writer/raise/shader_io.h
@@ -40,7 +40,7 @@
 /// @param module the module to transform
 /// @param config the configuration
 /// @returns success or failure
-Result<SuccessType> ShaderIO(core::ir::Module* module, const ShaderIOConfig& config);
+Result<SuccessType> ShaderIO(core::ir::Module& module, const ShaderIOConfig& config);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
index 99ce357..88d564f 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
@@ -111,12 +111,12 @@
     return result;
 }
 
-void Run(core::ir::Module* ir) {
-    core::ir::Builder builder(*ir);
+void Run(core::ir::Module& ir) {
+    core::ir::Builder builder(ir);
 
     // Find the access instructions that need replacing.
     Vector<AccessToReplace, 4> worklist;
-    for (auto* inst : ir->instructions.Objects()) {
+    for (auto* inst : ir.instructions.Objects()) {
         if (auto* access = inst->As<core::ir::Access>()) {
             if (auto to_replace = ShouldReplace(access)) {
                 worklist.Push(to_replace.value());
@@ -146,7 +146,7 @@
 
         // Declare a local variable and copy the source object to it.
         auto* local = object_to_local.GetOrCreate(source_object, [&] {
-            auto* decl = builder.Var(ir->Types().ptr(
+            auto* decl = builder.Var(ir.Types().ptr(
                 core::AddressSpace::kFunction, source_object->Type(), core::Access::kReadWrite));
             decl->SetInitializer(source_object);
             decl->InsertBefore(access);
@@ -170,7 +170,7 @@
         }
 
         core::ir::Instruction* new_access = builder.Access(
-            ir->Types().ptr(core::AddressSpace::kFunction, access_type, core::Access::kReadWrite),
+            ir.Types().ptr(core::AddressSpace::kFunction, access_type, core::Access::kReadWrite),
             local, indices);
         new_access->InsertBefore(access);
 
@@ -189,8 +189,8 @@
 
 }  // namespace
 
-Result<SuccessType> VarForDynamicIndex(core::ir::Module* ir) {
-    auto result = ValidateAndDumpIfNeeded(*ir, "VarForDynamicIndex transform");
+Result<SuccessType> VarForDynamicIndex(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "VarForDynamicIndex transform");
     if (!result) {
         return result;
     }
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
index e6ca4d7..c731558 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
@@ -33,7 +33,7 @@
 /// composite.
 /// @param module the module to transform
 /// @returns success or failure
-Result<SuccessType> VarForDynamicIndex(core::ir::Module* module);
+Result<SuccessType> VarForDynamicIndex(core::ir::Module& module);
 
 }  // namespace tint::spirv::writer::raise
 
diff --git a/src/tint/lang/spirv/writer/var_test.cc b/src/tint/lang/spirv/writer/var_test.cc
index 2e37175..38dcf5b 100644
--- a/src/tint/lang/spirv/writer/var_test.cc
+++ b/src/tint/lang/spirv/writer/var_test.cc
@@ -169,7 +169,7 @@
     b.RootBlock()->Append(b.Var("v", ty.ptr<workgroup, i32>()));
 
     // Create a writer with the zero_init_workgroup_memory flag set to `true`.
-    Printer gen(&mod, true);
+    Printer gen(mod, true);
     ASSERT_TRUE(Generate(gen)) << Error() << output_;
     EXPECT_INST("%4 = OpConstantNull %int");
     EXPECT_INST("%v = OpVariable %_ptr_Workgroup_int Workgroup %4");
diff --git a/src/tint/lang/spirv/writer/writer.cc b/src/tint/lang/spirv/writer/writer.cc
index e614c71..08a83fd 100644
--- a/src/tint/lang/spirv/writer/writer.cc
+++ b/src/tint/lang/spirv/writer/writer.cc
@@ -52,19 +52,19 @@
         auto ir = converted.Move();
 
         // Apply transforms as required by writer options.
-        auto remapper = core::ir::transform::BindingRemapper(&ir, options.binding_remapper_options);
+        auto remapper = core::ir::transform::BindingRemapper(ir, options.binding_remapper_options);
         if (!remapper) {
             return remapper.Failure();
         }
 
         // Raise the IR to the SPIR-V dialect.
-        auto raised = raise::Raise(&ir, options);
+        auto raised = raise::Raise(ir, options);
         if (!raised) {
             return std::move(raised.Failure());
         }
 
         // Generate the SPIR-V code.
-        auto impl = std::make_unique<Printer>(&ir, zero_initialize_workgroup_memory);
+        auto impl = std::make_unique<Printer>(ir, zero_initialize_workgroup_memory);
         auto spirv = impl->Generate();
         if (!spirv) {
             return std::move(spirv.Failure());