[ir] Make the instruction and value allocators private.

This CL moves the `allocators` in an IR module to be private and adds
`CreateInstruction` and `CreateValue` helpers.

Bug: 362490745
Change-Id: I8900382f5e5907bccd81fc392c59216fca4b32de
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/205237
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/access.cc b/src/tint/lang/core/ir/access.cc
index b57ba8f..94d86d0 100644
--- a/src/tint/lang/core/ir/access.cc
+++ b/src/tint/lang/core/ir/access.cc
@@ -52,8 +52,7 @@
     auto new_result = ctx.Clone(Result(0));
     auto obj = ctx.Remap(Object());
     auto indices = ctx.Remap<Access::kDefaultNumOperands>(Indices());
-    return ctx.ir.allocators.instructions.Create<Access>(ctx.ir.NextInstructionId(), new_result,
-                                                         obj, indices);
+    return ctx.ir.CreateInstruction<Access>(new_result, obj, indices);
 }
 //! @endcond
 
diff --git a/src/tint/lang/core/ir/binary/decode.cc b/src/tint/lang/core/ir/binary/decode.cc
index 0f3e2a5..b81d19a 100644
--- a/src/tint/lang/core/ir/binary/decode.cc
+++ b/src/tint/lang/core/ir/binary/decode.cc
@@ -236,7 +236,7 @@
     // Functions
     ////////////////////////////////////////////////////////////////////////////
     ir::Function* CreateFunction(const pb::Function&) {
-        return mod_out_.allocators.values.Create<ir::Function>();
+        return mod_out_.CreateValue<ir::Function>();
     }
 
     void PopulateFunction(ir::Function* fn_out, const pb::Function& fn_in) {
@@ -466,99 +466,89 @@
     }
 
     ir::Access* CreateInstructionAccess(const pb::InstructionAccess&) {
-        return mod_out_.allocators.instructions.Create<ir::Access>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Access>();
     }
 
     ir::CoreBinary* CreateInstructionBinary(const pb::InstructionBinary& binary_in) {
-        auto* binary_out =
-            mod_out_.allocators.instructions.Create<ir::CoreBinary>(mod_out_.NextInstructionId());
+        auto* binary_out = mod_out_.CreateInstruction<ir::CoreBinary>();
         binary_out->SetOp(BinaryOp(binary_in.op()));
         return binary_out;
     }
 
     ir::Bitcast* CreateInstructionBitcast(const pb::InstructionBitcast&) {
-        return mod_out_.allocators.instructions.Create<ir::Bitcast>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Bitcast>();
     }
 
     ir::BreakIf* CreateInstructionBreakIf(const pb::InstructionBreakIf&) {
-        auto* break_if_out =
-            mod_out_.allocators.instructions.Create<ir::BreakIf>(mod_out_.NextInstructionId());
+        auto* break_if_out = mod_out_.CreateInstruction<ir::BreakIf>();
         break_ifs_.Push(break_if_out);
         return break_if_out;
     }
 
     ir::CoreBuiltinCall* CreateInstructionBuiltinCall(const pb::InstructionBuiltinCall& call_in) {
-        auto* call_out = mod_out_.allocators.instructions.Create<ir::CoreBuiltinCall>(
-            mod_out_.NextInstructionId());
+        auto* call_out = mod_out_.CreateInstruction<ir::CoreBuiltinCall>();
         call_out->SetFunc(BuiltinFn(call_in.builtin()));
         return call_out;
     }
 
     ir::Construct* CreateInstructionConstruct(const pb::InstructionConstruct&) {
-        return mod_out_.allocators.instructions.Create<ir::Construct>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Construct>();
     }
 
     ir::Continue* CreateInstructionContinue(const pb::InstructionContinue&) {
-        auto* continue_ =
-            mod_out_.allocators.instructions.Create<ir::Continue>(mod_out_.NextInstructionId());
+        auto* continue_ = mod_out_.CreateInstruction<ir::Continue>();
         continues_.Push(continue_);
         return continue_;
     }
 
     ir::Convert* CreateInstructionConvert(const pb::InstructionConvert&) {
-        return mod_out_.allocators.instructions.Create<ir::Convert>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Convert>();
     }
 
     ir::ExitIf* CreateInstructionExitIf(const pb::InstructionExitIf&) {
-        auto* exit_out =
-            mod_out_.allocators.instructions.Create<ir::ExitIf>(mod_out_.NextInstructionId());
+        auto* exit_out = mod_out_.CreateInstruction<ir::ExitIf>();
         exit_ifs_.Push(exit_out);
         return exit_out;
     }
 
     ir::ExitLoop* CreateInstructionExitLoop(const pb::InstructionExitLoop&) {
-        auto* exit_out =
-            mod_out_.allocators.instructions.Create<ir::ExitLoop>(mod_out_.NextInstructionId());
+        auto* exit_out = mod_out_.CreateInstruction<ir::ExitLoop>();
         exit_loops_.Push(exit_out);
         return exit_out;
     }
 
     ir::ExitSwitch* CreateInstructionExitSwitch(const pb::InstructionExitSwitch&) {
-        auto* exit_out =
-            mod_out_.allocators.instructions.Create<ir::ExitSwitch>(mod_out_.NextInstructionId());
+        auto* exit_out = mod_out_.CreateInstruction<ir::ExitSwitch>();
         exit_switches_.Push(exit_out);
         return exit_out;
     }
 
     ir::Discard* CreateInstructionDiscard(const pb::InstructionDiscard&) {
-        return mod_out_.allocators.instructions.Create<ir::Discard>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Discard>();
     }
 
     ir::If* CreateInstructionIf(const pb::InstructionIf& if_in) {
-        auto* if_out =
-            mod_out_.allocators.instructions.Create<ir::If>(mod_out_.NextInstructionId());
+        auto* if_out = mod_out_.CreateInstruction<ir::If>();
         if_out->SetTrue(if_in.has_true_() ? Block(if_in.true_()) : b.Block());
         if_out->SetFalse(if_in.has_false_() ? Block(if_in.false_()) : b.Block());
         return if_out;
     }
 
     ir::Let* CreateInstructionLet(const pb::InstructionLet&) {
-        return mod_out_.allocators.instructions.Create<ir::Let>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Let>();
     }
 
     ir::Load* CreateInstructionLoad(const pb::InstructionLoad&) {
-        return mod_out_.allocators.instructions.Create<ir::Load>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Load>();
     }
 
     ir::LoadVectorElement* CreateInstructionLoadVectorElement(
         const pb::InstructionLoadVectorElement&) {
-        return mod_out_.allocators.instructions.Create<ir::LoadVectorElement>(
-            mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::LoadVectorElement>();
     }
 
     ir::Loop* CreateInstructionLoop(const pb::InstructionLoop& loop_in) {
-        auto* loop_out =
-            mod_out_.allocators.instructions.Create<ir::Loop>(mod_out_.NextInstructionId());
+        auto* loop_out = mod_out_.CreateInstruction<ir::Loop>();
         if (loop_in.has_initializer()) {
             loop_out->SetInitializer(Block(loop_in.initializer()));
         } else {
@@ -574,29 +564,26 @@
     }
 
     ir::NextIteration* CreateInstructionNextIteration(const pb::InstructionNextIteration&) {
-        auto* next_it_out = mod_out_.allocators.instructions.Create<ir::NextIteration>(
-            mod_out_.NextInstructionId());
+        auto* next_it_out = mod_out_.CreateInstruction<ir::NextIteration>();
         next_iterations_.Push(next_it_out);
         return next_it_out;
     }
 
     ir::Return* CreateInstructionReturn(const pb::InstructionReturn&) {
-        return mod_out_.allocators.instructions.Create<ir::Return>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Return>();
     }
 
     ir::Store* CreateInstructionStore(const pb::InstructionStore&) {
-        return mod_out_.allocators.instructions.Create<ir::Store>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::Store>();
     }
 
     ir::StoreVectorElement* CreateInstructionStoreVectorElement(
         const pb::InstructionStoreVectorElement&) {
-        return mod_out_.allocators.instructions.Create<ir::StoreVectorElement>(
-            mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::StoreVectorElement>();
     }
 
     ir::Swizzle* CreateInstructionSwizzle(const pb::InstructionSwizzle& swizzle_in) {
-        auto* swizzle_out =
-            mod_out_.allocators.instructions.Create<ir::Swizzle>(mod_out_.NextInstructionId());
+        auto* swizzle_out = mod_out_.CreateInstruction<ir::Swizzle>();
         Vector<uint32_t, 4> indices;
         for (auto idx : swizzle_in.indices()) {
             indices.Push(idx);
@@ -606,8 +593,7 @@
     }
 
     ir::Switch* CreateInstructionSwitch(const pb::InstructionSwitch& switch_in) {
-        auto* switch_out =
-            mod_out_.allocators.instructions.Create<ir::Switch>(mod_out_.NextInstructionId());
+        auto* switch_out = mod_out_.CreateInstruction<ir::Switch>();
         for (auto& case_in : switch_in.cases()) {
             ir::Switch::Case case_out{};
             case_out.block = Block(case_in.block());
@@ -627,19 +613,17 @@
     }
 
     ir::CoreUnary* CreateInstructionUnary(const pb::InstructionUnary& unary_in) {
-        auto* unary_out =
-            mod_out_.allocators.instructions.Create<ir::CoreUnary>(mod_out_.NextInstructionId());
+        auto* unary_out = mod_out_.CreateInstruction<ir::CoreUnary>();
         unary_out->SetOp(UnaryOp(unary_in.op()));
         return unary_out;
     }
 
     ir::UserCall* CreateInstructionUserCall(const pb::InstructionUserCall&) {
-        return mod_out_.allocators.instructions.Create<ir::UserCall>(mod_out_.NextInstructionId());
+        return mod_out_.CreateInstruction<ir::UserCall>();
     }
 
     ir::Var* CreateInstructionVar(const pb::InstructionVar& var_in) {
-        auto* var_out =
-            mod_out_.allocators.instructions.Create<ir::Var>(mod_out_.NextInstructionId());
+        auto* var_out = mod_out_.CreateInstruction<ir::Var>();
         if (var_in.has_binding_point()) {
             auto& bp_in = var_in.binding_point();
             var_out->SetBindingPoint(bp_in.group(), bp_in.binding());
diff --git a/src/tint/lang/core/ir/bitcast.cc b/src/tint/lang/core/ir/bitcast.cc
index 0045833..f35346c 100644
--- a/src/tint/lang/core/ir/bitcast.cc
+++ b/src/tint/lang/core/ir/bitcast.cc
@@ -46,8 +46,7 @@
 Bitcast* Bitcast::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Val());
-    return ctx.ir.allocators.instructions.Create<Bitcast>(ctx.ir.NextInstructionId(), new_result,
-                                                          val);
+    return ctx.ir.CreateInstruction<Bitcast>(new_result, val);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/block_param.cc b/src/tint/lang/core/ir/block_param.cc
index 0866efb..a0157cc 100644
--- a/src/tint/lang/core/ir/block_param.cc
+++ b/src/tint/lang/core/ir/block_param.cc
@@ -42,7 +42,7 @@
 BlockParam::~BlockParam() = default;
 
 BlockParam* BlockParam::Clone(CloneContext& ctx) {
-    auto* new_bp = ctx.ir.allocators.values.Create<BlockParam>(type_);
+    auto* new_bp = ctx.ir.CreateValue<BlockParam>(type_);
 
     auto name = ctx.ir.NameOf(this);
     if (name.IsValid()) {
diff --git a/src/tint/lang/core/ir/break_if.cc b/src/tint/lang/core/ir/break_if.cc
index 7a1b96f..0191868 100644
--- a/src/tint/lang/core/ir/break_if.cc
+++ b/src/tint/lang/core/ir/break_if.cc
@@ -66,8 +66,7 @@
     auto* loop = ctx.Remap(loop_);
     auto* cond = ctx.Remap(Condition());
     auto args = ctx.Remap<BreakIf::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<BreakIf>(ctx.ir.NextInstructionId(), cond, loop,
-                                                          args);
+    return ctx.ir.CreateInstruction<BreakIf>(cond, loop, args);
 }
 
 void BreakIf::SetLoop(ir::Loop* loop) {
diff --git a/src/tint/lang/core/ir/builder.cc b/src/tint/lang/core/ir/builder.cc
index 658cda0..8640105 100644
--- a/src/tint/lang/core/ir/builder.cc
+++ b/src/tint/lang/core/ir/builder.cc
@@ -54,7 +54,7 @@
 Function* Builder::Function(const core::type::Type* return_type,
                             Function::PipelineStage stage,
                             std::optional<std::array<uint32_t, 3>> wg_size) {
-    auto* ir_func = ir.allocators.values.Create<ir::Function>(return_type, stage, wg_size);
+    auto* ir_func = ir.CreateValue<ir::Function>(return_type, stage, wg_size);
     ir_func->SetBlock(Block());
     ir.functions.Push(ir_func);
     return ir_func;
@@ -70,8 +70,7 @@
 }
 
 ir::Loop* Builder::Loop() {
-    return Append(ir.allocators.instructions.Create<ir::Loop>(ir.NextInstructionId(), Block(),
-                                                              MultiInBlock(), MultiInBlock()));
+    return Append(ir.CreateInstruction<ir::Loop>(Block(), MultiInBlock(), MultiInBlock()));
 }
 
 Block* Builder::Case(ir::Switch* s, VectorRef<ir::Constant*> values) {
@@ -96,12 +95,11 @@
 }
 
 ir::Discard* Builder::Discard() {
-    return Append(ir.allocators.instructions.Create<ir::Discard>(ir.NextInstructionId()));
+    return Append(ir.CreateInstruction<ir::Discard>());
 }
 
 ir::Var* Builder::Var(const core::type::MemoryView* type) {
-    return Append(ir.allocators.instructions.Create<ir::Var>(ir.NextInstructionId(),
-                                                             InstructionResult(type)));
+    return Append(ir.CreateInstruction<ir::Var>(InstructionResult(type)));
 }
 
 ir::Var* Builder::Var(std::string_view name, const core::type::MemoryView* type) {
@@ -111,36 +109,35 @@
 }
 
 ir::BlockParam* Builder::BlockParam(const core::type::Type* type) {
-    return ir.allocators.values.Create<ir::BlockParam>(type);
+    return ir.CreateValue<ir::BlockParam>(type);
 }
 
 ir::BlockParam* Builder::BlockParam(std::string_view name, const core::type::Type* type) {
-    auto* param = ir.allocators.values.Create<ir::BlockParam>(type);
+    auto* param = ir.CreateValue<ir::BlockParam>(type);
     ir.SetName(param, name);
     return param;
 }
 
 ir::FunctionParam* Builder::FunctionParam(const core::type::Type* type) {
-    return ir.allocators.values.Create<ir::FunctionParam>(type);
+    return ir.CreateValue<ir::FunctionParam>(type);
 }
 
 ir::FunctionParam* Builder::FunctionParam(std::string_view name, const core::type::Type* type) {
-    auto* param = ir.allocators.values.Create<ir::FunctionParam>(type);
+    auto* param = ir.CreateValue<ir::FunctionParam>(type);
     ir.SetName(param, name);
     return param;
 }
 
 ir::TerminateInvocation* Builder::TerminateInvocation() {
-    return Append(
-        ir.allocators.instructions.Create<ir::TerminateInvocation>(ir.NextInstructionId()));
+    return Append(ir.CreateInstruction<ir::TerminateInvocation>());
 }
 
 ir::Unreachable* Builder::Unreachable() {
-    return Append(ir.allocators.instructions.Create<ir::Unreachable>(ir.NextInstructionId()));
+    return Append(ir.CreateInstruction<ir::Unreachable>());
 }
 
 ir::Unused* Builder::Unused() {
-    return ir.allocators.values.Create<ir::Unused>();
+    return ir.CreateValue<ir::Unused>();
 }
 
 const core::type::Type* Builder::VectorPtrElementType(const core::type::Type* type) {
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index 5e052df..10b26f0 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -283,8 +283,7 @@
     template <typename T>
     ir::If* If(T&& condition) {
         auto* cond_val = Value(std::forward<T>(condition));
-        return Append(ir.allocators.instructions.Create<ir::If>(ir.NextInstructionId(), cond_val,
-                                                                Block(), Block()));
+        return Append(ir.CreateInstruction<ir::If>(cond_val, Block(), Block()));
     }
 
     /// Creates a loop instruction
@@ -297,8 +296,7 @@
     template <typename T>
     ir::Switch* Switch(T&& condition) {
         auto* cond_val = Value(std::forward<T>(condition));
-        return Append(
-            ir.allocators.instructions.Create<ir::Switch>(ir.NextInstructionId(), cond_val));
+        return Append(ir.CreateInstruction<ir::Switch>(cond_val));
     }
 
     /// Creates a default case for the switch @p s
@@ -322,8 +320,7 @@
     /// @param val the constant value
     /// @returns the new constant
     ir::Constant* Constant(const core::constant::Value* val) {
-        return ir.constants.GetOrAdd(
-            val, [&] { return ir.allocators.values.Create<ir::Constant>(val); });
+        return ir.constants.GetOrAdd(val, [&] { return ir.CreateValue<ir::Constant>(val); });
     }
 
     /// Creates a ir::Constant for an i32 Scalar
@@ -544,8 +541,8 @@
         CheckForNonDeterministicEvaluation<LHS, RHS>();
         auto* lhs_val = Value(std::forward<LHS>(lhs));
         auto* rhs_val = Value(std::forward<RHS>(rhs));
-        return Append(ir.allocators.instructions.Create<ir::CoreBinary>(
-            ir.NextInstructionId(), InstructionResult(type), op, lhs_val, rhs_val));
+        return Append(
+            ir.CreateInstruction<ir::CoreBinary>(InstructionResult(type), op, lhs_val, rhs_val));
     }
 
     /// Creates an op for `lhs kind rhs`
@@ -560,8 +557,7 @@
         CheckForNonDeterministicEvaluation<LHS, RHS>();
         auto* lhs_val = Value(std::forward<LHS>(lhs));
         auto* rhs_val = Value(std::forward<RHS>(rhs));
-        return Append(ir.allocators.instructions.Create<KLASS>(
-            ir.NextInstructionId(), InstructionResult(type), op, lhs_val, rhs_val));
+        return Append(ir.CreateInstruction<KLASS>(InstructionResult(type), op, lhs_val, rhs_val));
     }
 
     /// Creates an And operation
@@ -910,8 +906,7 @@
     template <typename VAL>
     ir::CoreUnary* Unary(UnaryOp op, const core::type::Type* type, VAL&& val) {
         auto* value = Value(std::forward<VAL>(val));
-        return Append(ir.allocators.instructions.Create<ir::CoreUnary>(
-            ir.NextInstructionId(), InstructionResult(type), op, value));
+        return Append(ir.CreateInstruction<ir::CoreUnary>(InstructionResult(type), op, value));
     }
 
     /// Creates an op for `op val`
@@ -989,8 +984,7 @@
     template <typename VAL>
     ir::Bitcast* Bitcast(const core::type::Type* type, VAL&& val) {
         auto* value = Value(std::forward<VAL>(val));
-        return Append(ir.allocators.instructions.Create<ir::Bitcast>(
-            ir.NextInstructionId(), InstructionResult(type), value));
+        return Append(ir.CreateInstruction<ir::Bitcast>(InstructionResult(type), value));
     }
 
     /// Creates a bitcast instruction
@@ -1017,8 +1011,8 @@
     ir::UserCall* CallWithResult(ir::InstructionResult* result,
                                  ir::Function* func,
                                  ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::UserCall>(
-            ir.NextInstructionId(), result, func, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<ir::UserCall>(result, func, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a user function call instruction
@@ -1060,8 +1054,8 @@
     ir::CoreBuiltinCall* CallWithResult(core::ir::InstructionResult* result,
                                         core::BuiltinFn func,
                                         ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::CoreBuiltinCall>(
-            ir.NextInstructionId(), result, func, Values(std::forward<ARGS>(args)...)));
+        return Append(ir.CreateInstruction<ir::CoreBuiltinCall>(
+            result, func, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a core builtin call instruction
@@ -1093,8 +1087,8 @@
     template <typename KLASS, typename FUNC, typename... ARGS>
     tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::BuiltinCall>, KLASS*>
     CallWithResult(ir::InstructionResult* result, FUNC func, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<KLASS>(
-            ir.NextInstructionId(), result, func, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<KLASS>(result, func, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a builtin call instruction
@@ -1118,9 +1112,8 @@
     template <typename KLASS, typename FUNC, typename OBJ, typename... ARGS>
     tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::MemberBuiltinCall>, KLASS*>
     MemberCallWithResult(ir::InstructionResult* result, FUNC func, OBJ&& obj, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<KLASS>(
-            ir.NextInstructionId(), result, func, Value(std::forward<OBJ>(obj)),
-            Values(std::forward<ARGS>(args)...)));
+        return Append(ir.CreateInstruction<KLASS>(result, func, Value(std::forward<OBJ>(obj)),
+                                                  Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a member builtin call instruction.
@@ -1143,8 +1136,7 @@
     /// @returns the instruction
     template <typename VAL>
     ir::Convert* ConvertWithResult(ir::InstructionResult* result, VAL&& val) {
-        return Append(ir.allocators.instructions.Create<ir::Convert>(
-            ir.NextInstructionId(), result, Value(std::forward<VAL>(val))));
+        return Append(ir.CreateInstruction<ir::Convert>(result, Value(std::forward<VAL>(val))));
     }
 
     /// Creates a value conversion instruction to the template type T
@@ -1171,8 +1163,8 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::Construct* ConstructWithResult(ir::InstructionResult* result, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::Construct>(
-            ir.NextInstructionId(), result, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<ir::Construct>(result, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a value constructor instruction to the template type T
@@ -1200,8 +1192,7 @@
     template <typename VAL>
     ir::Load* LoadWithResult(ir::InstructionResult* result, VAL&& from) {
         auto* value = Value(std::forward<VAL>(from));
-        return Append(
-            ir.allocators.instructions.Create<ir::Load>(ir.NextInstructionId(), result, value));
+        return Append(ir.CreateInstruction<ir::Load>(result, value));
     }
 
     /// Creates a load instruction
@@ -1222,8 +1213,7 @@
         CheckForNonDeterministicEvaluation<TO, FROM>();
         auto* to_val = Value(std::forward<TO>(to));
         auto* from_val = Value(std::forward<FROM>(from));
-        return Append(
-            ir.allocators.instructions.Create<ir::Store>(ir.NextInstructionId(), to_val, from_val));
+        return Append(ir.CreateInstruction<ir::Store>(to_val, from_val));
     }
 
     /// Creates a store vector element instruction
@@ -1237,8 +1227,7 @@
         auto* to_val = Value(std::forward<TO>(to));
         auto* index_val = Value(std::forward<INDEX>(index));
         auto* value_val = Value(std::forward<VALUE>(value));
-        return Append(ir.allocators.instructions.Create<ir::StoreVectorElement>(
-            ir.NextInstructionId(), to_val, index_val, value_val));
+        return Append(ir.CreateInstruction<ir::StoreVectorElement>(to_val, index_val, value_val));
     }
 
     /// Creates a load vector element instruction with an existing instruction result
@@ -1253,8 +1242,7 @@
         CheckForNonDeterministicEvaluation<FROM, INDEX>();
         auto* from_val = Value(std::forward<FROM>(from));
         auto* index_val = Value(std::forward<INDEX>(index));
-        return Append(ir.allocators.instructions.Create<ir::LoadVectorElement>(
-            ir.NextInstructionId(), result, from_val, index_val));
+        return Append(ir.CreateInstruction<ir::LoadVectorElement>(result, from_val, index_val));
     }
 
     /// Creates a load vector element instruction
@@ -1354,8 +1342,7 @@
             TINT_ASSERT(val);
             return nullptr;
         }
-        auto* let = Append(ir.allocators.instructions.Create<ir::Let>(
-            ir.NextInstructionId(), InstructionResult(val->Type()), val));
+        auto* let = Append(ir.CreateInstruction<ir::Let>(InstructionResult(val->Type()), val));
         ir.SetName(let->Result(0), name);
         return let;
     }
@@ -1364,8 +1351,7 @@
     /// @param type the let type
     /// @returns the instruction
     ir::Let* Let(const type::Type* type) {
-        auto* let = ir.allocators.instructions.Create<ir::Let>(ir.NextInstructionId(),
-                                                               InstructionResult(type), nullptr);
+        auto* let = ir.CreateInstruction<ir::Let>(InstructionResult(type), nullptr);
         Append(let);
         return let;
     }
@@ -1374,7 +1360,7 @@
     /// @param func the function being returned
     /// @returns the instruction
     ir::Return* Return(ir::Function* func) {
-        return Append(ir.allocators.instructions.Create<ir::Return>(ir.NextInstructionId(), func));
+        return Append(ir.CreateInstruction<ir::Return>(func));
     }
 
     /// Creates a return instruction
@@ -1385,13 +1371,11 @@
     ir::Return* Return(ir::Function* func, ARG&& value) {
         if constexpr (std::is_same_v<std::decay_t<ARG>, ir::Value*>) {
             if (value == nullptr) {
-                return Append(
-                    ir.allocators.instructions.Create<ir::Return>(ir.NextInstructionId(), func));
+                return Append(ir.CreateInstruction<ir::Return>(func));
             }
         }
         auto* val = Value(std::forward<ARG>(value));
-        return Append(
-            ir.allocators.instructions.Create<ir::Return>(ir.NextInstructionId(), func, val));
+        return Append(ir.CreateInstruction<ir::Return>(func, val));
     }
 
     /// Creates a loop next iteration instruction
@@ -1400,8 +1384,8 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::NextIteration* NextIteration(ir::Loop* loop, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::NextIteration>(
-            ir.NextInstructionId(), loop, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<ir::NextIteration>(loop, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a loop break-if instruction
@@ -1412,8 +1396,7 @@
     ir::BreakIf* BreakIf(ir::Loop* loop, CONDITION&& condition) {
         CheckForNonDeterministicEvaluation<CONDITION>();
         auto* cond_val = Value(std::forward<CONDITION>(condition));
-        return Append(
-            ir.allocators.instructions.Create<ir::BreakIf>(ir.NextInstructionId(), cond_val, loop));
+        return Append(ir.CreateInstruction<ir::BreakIf>(cond_val, loop));
     }
 
     /// Creates a loop break-if instruction
@@ -1431,9 +1414,8 @@
                          EXIT_VALUES&& exit_values) {
         CheckForNonDeterministicEvaluation<CONDITION, NEXT_ITER_VALUES, EXIT_VALUES>();
         auto* cond_val = Value(std::forward<CONDITION>(condition));
-        return Append(ir.allocators.instructions.Create<ir::BreakIf>(
-            ir.NextInstructionId(), cond_val, loop,
-            Values(std::forward<NEXT_ITER_VALUES>(next_iter_values)),
+        return Append(ir.CreateInstruction<ir::BreakIf>(
+            cond_val, loop, Values(std::forward<NEXT_ITER_VALUES>(next_iter_values)),
             Values(std::forward<EXIT_VALUES>(exit_values))));
     }
 
@@ -1443,8 +1425,8 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::Continue* Continue(ir::Loop* loop, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::Continue>(
-            ir.NextInstructionId(), loop, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<ir::Continue>(loop, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit switch instruction
@@ -1453,8 +1435,8 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::ExitSwitch* ExitSwitch(ir::Switch* sw, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::ExitSwitch>(
-            ir.NextInstructionId(), sw, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<ir::ExitSwitch>(sw, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit loop instruction
@@ -1463,8 +1445,8 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::ExitLoop* ExitLoop(ir::Loop* loop, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::ExitLoop>(
-            ir.NextInstructionId(), loop, Values(std::forward<ARGS>(args)...)));
+        return Append(
+            ir.CreateInstruction<ir::ExitLoop>(loop, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit if instruction
@@ -1473,8 +1455,7 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::ExitIf* ExitIf(ir::If* i, ARGS&&... args) {
-        return Append(ir.allocators.instructions.Create<ir::ExitIf>(
-            ir.NextInstructionId(), i, Values(std::forward<ARGS>(args)...)));
+        return Append(ir.CreateInstruction<ir::ExitIf>(i, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit instruction for the given control instruction
@@ -1559,8 +1540,8 @@
     ir::Access* AccessWithResult(ir::InstructionResult* result, OBJ&& object, ARGS&&... indices) {
         CheckForNonDeterministicEvaluation<OBJ, ARGS...>();
         auto* obj_val = Value(std::forward<OBJ>(object));
-        return Append(ir.allocators.instructions.Create<ir::Access>(
-            ir.NextInstructionId(), result, obj_val, Values(std::forward<ARGS>(indices)...)));
+        return Append(ir.CreateInstruction<ir::Access>(result, obj_val,
+                                                       Values(std::forward<ARGS>(indices)...)));
     }
 
     /// Creates a new `Access`
@@ -1593,8 +1574,8 @@
     template <typename OBJ>
     ir::Swizzle* Swizzle(const core::type::Type* type, OBJ&& object, VectorRef<uint32_t> indices) {
         auto* obj_val = Value(std::forward<OBJ>(object));
-        return Append(ir.allocators.instructions.Create<ir::Swizzle>(
-            ir.NextInstructionId(), InstructionResult(type), obj_val, std::move(indices)));
+        return Append(ir.CreateInstruction<ir::Swizzle>(InstructionResult(type), obj_val,
+                                                        std::move(indices)));
     }
 
     /// Creates a new `Swizzle`
@@ -1618,9 +1599,8 @@
                          OBJ&& object,
                          std::initializer_list<uint32_t> indices) {
         auto* obj_val = Value(std::forward<OBJ>(object));
-        return Append(ir.allocators.instructions.Create<ir::Swizzle>(
-            ir.NextInstructionId(), InstructionResult(type), obj_val,
-            Vector<uint32_t, 4>(indices)));
+        return Append(ir.CreateInstruction<ir::Swizzle>(InstructionResult(type), obj_val,
+                                                        Vector<uint32_t, 4>(indices)));
     }
 
     /// Name names the value or instruction with @p name
@@ -1649,7 +1629,7 @@
     /// @param type the return type
     /// @returns the value
     ir::InstructionResult* InstructionResult(const core::type::Type* type) {
-        return ir.allocators.values.Create<ir::InstructionResult>(type);
+        return ir.CreateValue<ir::InstructionResult>(type);
     }
 
     /// Creates a new runtime value
diff --git a/src/tint/lang/core/ir/construct.cc b/src/tint/lang/core/ir/construct.cc
index 697458c..9c4d98a 100644
--- a/src/tint/lang/core/ir/construct.cc
+++ b/src/tint/lang/core/ir/construct.cc
@@ -48,8 +48,7 @@
 Construct* Construct::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto args = ctx.Remap<Construct::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<Construct>(ctx.ir.NextInstructionId(), new_result,
-                                                            args);
+    return ctx.ir.CreateInstruction<Construct>(new_result, args);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/continue.cc b/src/tint/lang/core/ir/continue.cc
index 9e0bb1f..c36abe1 100644
--- a/src/tint/lang/core/ir/continue.cc
+++ b/src/tint/lang/core/ir/continue.cc
@@ -58,7 +58,7 @@
     auto* loop = ctx.Remap(Loop());
     auto args = ctx.Remap<Continue::kDefaultNumOperands>(Args());
 
-    return ctx.ir.allocators.instructions.Create<Continue>(ctx.ir.NextInstructionId(), loop, args);
+    return ctx.ir.CreateInstruction<Continue>(loop, args);
 }
 
 void Continue::SetLoop(ir::Loop* loop) {
diff --git a/src/tint/lang/core/ir/convert.cc b/src/tint/lang/core/ir/convert.cc
index 767f810..e6274e6 100644
--- a/src/tint/lang/core/ir/convert.cc
+++ b/src/tint/lang/core/ir/convert.cc
@@ -48,8 +48,7 @@
 Convert* Convert::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Args()[0]);
-    return ctx.ir.allocators.instructions.Create<Convert>(ctx.ir.NextInstructionId(), new_result,
-                                                          val);
+    return ctx.ir.CreateInstruction<Convert>(new_result, val);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_binary.cc b/src/tint/lang/core/ir/core_binary.cc
index 82517f6..8019a94 100644
--- a/src/tint/lang/core/ir/core_binary.cc
+++ b/src/tint/lang/core/ir/core_binary.cc
@@ -46,8 +46,7 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* lhs = ctx.Remap(LHS());
     auto* rhs = ctx.Remap(RHS());
-    return ctx.ir.allocators.instructions.Create<CoreBinary>(ctx.ir.NextInstructionId(), new_result,
-                                                             Op(), lhs, rhs);
+    return ctx.ir.CreateInstruction<CoreBinary>(new_result, Op(), lhs, rhs);
 }
 
 const core::intrinsic::TableData& CoreBinary::TableData() const {
diff --git a/src/tint/lang/core/ir/core_builtin_call.cc b/src/tint/lang/core/ir/core_builtin_call.cc
index 10ed0eb3..d7457d7 100644
--- a/src/tint/lang/core/ir/core_builtin_call.cc
+++ b/src/tint/lang/core/ir/core_builtin_call.cc
@@ -52,8 +52,7 @@
 CoreBuiltinCall* CoreBuiltinCall::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto args = ctx.Remap<CoreBuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<CoreBuiltinCall>(ctx.ir.NextInstructionId(),
-                                                                  new_result, func_, args);
+    return ctx.ir.CreateInstruction<CoreBuiltinCall>(new_result, func_, args);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_unary.cc b/src/tint/lang/core/ir/core_unary.cc
index 239a7ff..90d376d 100644
--- a/src/tint/lang/core/ir/core_unary.cc
+++ b/src/tint/lang/core/ir/core_unary.cc
@@ -45,8 +45,7 @@
 CoreUnary* CoreUnary::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Val());
-    return ctx.ir.allocators.instructions.Create<CoreUnary>(ctx.ir.NextInstructionId(), new_result,
-                                                            Op(), val);
+    return ctx.ir.CreateInstruction<CoreUnary>(new_result, Op(), val);
 }
 
 const core::intrinsic::TableData& CoreUnary::TableData() const {
diff --git a/src/tint/lang/core/ir/discard.cc b/src/tint/lang/core/ir/discard.cc
index 1625018..5cab0fe 100644
--- a/src/tint/lang/core/ir/discard.cc
+++ b/src/tint/lang/core/ir/discard.cc
@@ -41,7 +41,7 @@
 Discard::~Discard() = default;
 
 Discard* Discard::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.instructions.Create<Discard>(ctx.ir.NextInstructionId());
+    return ctx.ir.CreateInstruction<Discard>();
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/exit_if.cc b/src/tint/lang/core/ir/exit_if.cc
index 2fc5e5a..b10249f 100644
--- a/src/tint/lang/core/ir/exit_if.cc
+++ b/src/tint/lang/core/ir/exit_if.cc
@@ -50,7 +50,7 @@
 ExitIf* ExitIf::Clone(CloneContext& ctx) {
     auto* if_ = ctx.Remap(If());
     auto args = ctx.Remap<ExitIf::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<ExitIf>(ctx.ir.NextInstructionId(), if_, args);
+    return ctx.ir.CreateInstruction<ExitIf>(if_, args);
 }
 
 void ExitIf::SetIf(ir::If* i) {
diff --git a/src/tint/lang/core/ir/exit_loop.cc b/src/tint/lang/core/ir/exit_loop.cc
index ddbe6e1..50b62cd 100644
--- a/src/tint/lang/core/ir/exit_loop.cc
+++ b/src/tint/lang/core/ir/exit_loop.cc
@@ -51,7 +51,7 @@
 ExitLoop* ExitLoop::Clone(CloneContext& ctx) {
     auto* loop = ctx.Remap(Loop());
     auto args = ctx.Remap<ExitLoop::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<ExitLoop>(ctx.ir.NextInstructionId(), loop, args);
+    return ctx.ir.CreateInstruction<ExitLoop>(loop, args);
 }
 
 void ExitLoop::SetLoop(ir::Loop* l) {
diff --git a/src/tint/lang/core/ir/exit_switch.cc b/src/tint/lang/core/ir/exit_switch.cc
index ade9843..acbceb9 100644
--- a/src/tint/lang/core/ir/exit_switch.cc
+++ b/src/tint/lang/core/ir/exit_switch.cc
@@ -51,8 +51,7 @@
 ExitSwitch* ExitSwitch::Clone(CloneContext& ctx) {
     auto* switch_ = ctx.Remap(Switch());
     auto args = ctx.Remap<ExitSwitch::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<ExitSwitch>(ctx.ir.NextInstructionId(), switch_,
-                                                             args);
+    return ctx.ir.CreateInstruction<ExitSwitch>(switch_, args);
 }
 
 void ExitSwitch::SetSwitch(ir::Switch* s) {
diff --git a/src/tint/lang/core/ir/function.cc b/src/tint/lang/core/ir/function.cc
index 4795ddf..32a22e6 100644
--- a/src/tint/lang/core/ir/function.cc
+++ b/src/tint/lang/core/ir/function.cc
@@ -50,8 +50,7 @@
 Function::~Function() = default;
 
 Function* Function::Clone(CloneContext& ctx) {
-    auto* new_func =
-        ctx.ir.allocators.values.Create<Function>(return_.type, pipeline_stage_, workgroup_size_);
+    auto* new_func = ctx.ir.CreateValue<Function>(return_.type, pipeline_stage_, workgroup_size_);
     new_func->block_ = ctx.ir.blocks.Create<ir::Block>();
     new_func->SetParams(ctx.Clone<1>(params_.Slice()));
     new_func->return_.attributes = return_.attributes;
diff --git a/src/tint/lang/core/ir/function_param.cc b/src/tint/lang/core/ir/function_param.cc
index 9eff761..1fb50c6 100644
--- a/src/tint/lang/core/ir/function_param.cc
+++ b/src/tint/lang/core/ir/function_param.cc
@@ -41,7 +41,7 @@
 FunctionParam::~FunctionParam() = default;
 
 FunctionParam* FunctionParam::Clone(CloneContext& ctx) {
-    auto* out = ctx.ir.allocators.values.Create<FunctionParam>(type_);
+    auto* out = ctx.ir.CreateValue<FunctionParam>(type_);
     out->binding_point_ = binding_point_;
     out->attributes_ = attributes_;
 
diff --git a/src/tint/lang/core/ir/if.cc b/src/tint/lang/core/ir/if.cc
index e4ebd0c..f4c4130 100644
--- a/src/tint/lang/core/ir/if.cc
+++ b/src/tint/lang/core/ir/if.cc
@@ -77,8 +77,7 @@
     auto* new_true = ctx.ir.blocks.Create<ir::Block>();
     auto* new_false = ctx.ir.blocks.Create<ir::Block>();
 
-    auto* new_if = ctx.ir.allocators.instructions.Create<If>(ctx.ir.NextInstructionId(), cond,
-                                                             new_true, new_false);
+    auto* new_if = ctx.ir.CreateInstruction<If>(cond, new_true, new_false);
     ctx.Replace(this, new_if);
 
     true_->CloneInto(ctx, new_true);
diff --git a/src/tint/lang/core/ir/if_test.cc b/src/tint/lang/core/ir/if_test.cc
index 30f193f..6e7f6ae 100644
--- a/src/tint/lang/core/ir/if_test.cc
+++ b/src/tint/lang/core/ir/if_test.cc
@@ -60,7 +60,7 @@
         {
             Module mod;
             Builder b{mod};
-            If if_(mod.NextInstructionId(), b.Constant(false), nullptr, b.Block());
+            mod.CreateInstruction<If>(b.Constant(false), nullptr, b.Block());
         },
         "internal compiler error");
 }
@@ -70,7 +70,7 @@
         {
             Module mod;
             Builder b{mod};
-            If if_(mod.NextInstructionId(), b.Constant(false), b.Block(), nullptr);
+            mod.CreateInstruction<If>(b.Constant(false), b.Block(), nullptr);
         },
         "internal compiler error");
 }
diff --git a/src/tint/lang/core/ir/instruction_result.cc b/src/tint/lang/core/ir/instruction_result.cc
index 6960792..e88463b 100644
--- a/src/tint/lang/core/ir/instruction_result.cc
+++ b/src/tint/lang/core/ir/instruction_result.cc
@@ -51,7 +51,7 @@
 InstructionResult* InstructionResult::Clone(CloneContext& ctx) {
     // Do not clone the `Instruction`. It will be set when this result is placed in the new parent
     // instruction.
-    return ctx.ir.allocators.values.Create<InstructionResult>(type_);
+    return ctx.ir.CreateValue<InstructionResult>(type_);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/let.cc b/src/tint/lang/core/ir/let.cc
index de01f25..62a0941 100644
--- a/src/tint/lang/core/ir/let.cc
+++ b/src/tint/lang/core/ir/let.cc
@@ -47,8 +47,7 @@
 Let* Let::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Value());
-    auto* new_let =
-        ctx.ir.allocators.instructions.Create<Let>(ctx.ir.NextInstructionId(), new_result, val);
+    auto* new_let = ctx.ir.CreateInstruction<Let>(new_result, val);
 
     auto name = ctx.ir.NameOf(this);
     ctx.ir.SetName(new_let, name.Name());
diff --git a/src/tint/lang/core/ir/load.cc b/src/tint/lang/core/ir/load.cc
index 4ac424a..9d2add5 100644
--- a/src/tint/lang/core/ir/load.cc
+++ b/src/tint/lang/core/ir/load.cc
@@ -52,8 +52,7 @@
 Load* Load::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* from = ctx.Remap(From());
-    return ctx.ir.allocators.instructions.Create<Load>(ctx.ir.NextInstructionId(), new_result,
-                                                       from);
+    return ctx.ir.CreateInstruction<Load>(new_result, from);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load_vector_element.cc b/src/tint/lang/core/ir/load_vector_element.cc
index 876da47..a32d360 100644
--- a/src/tint/lang/core/ir/load_vector_element.cc
+++ b/src/tint/lang/core/ir/load_vector_element.cc
@@ -56,8 +56,7 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* from = ctx.Remap(From());
     auto* index = ctx.Remap(Index());
-    return ctx.ir.allocators.instructions.Create<LoadVectorElement>(ctx.ir.NextInstructionId(),
-                                                                    new_result, from, index);
+    return ctx.ir.CreateInstruction<LoadVectorElement>(new_result, from, index);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/loop.cc b/src/tint/lang/core/ir/loop.cc
index cb8ff03..20cb75a 100644
--- a/src/tint/lang/core/ir/loop.cc
+++ b/src/tint/lang/core/ir/loop.cc
@@ -62,8 +62,7 @@
     auto* new_body = ctx.ir.blocks.Create<MultiInBlock>();
     auto* new_continuing = ctx.ir.blocks.Create<MultiInBlock>();
 
-    auto* new_loop = ctx.ir.allocators.instructions.Create<Loop>(
-        ctx.ir.NextInstructionId(), new_init, new_body, new_continuing);
+    auto* new_loop = ctx.ir.CreateInstruction<Loop>(new_init, new_body, new_continuing);
     ctx.Replace(this, new_loop);
 
     initializer_->CloneInto(ctx, new_init);
diff --git a/src/tint/lang/core/ir/loop_test.cc b/src/tint/lang/core/ir/loop_test.cc
index b0bc859..1c7a35d 100644
--- a/src/tint/lang/core/ir/loop_test.cc
+++ b/src/tint/lang/core/ir/loop_test.cc
@@ -52,7 +52,7 @@
         {
             Module mod;
             Builder b{mod};
-            Loop loop(mod.NextInstructionId(), nullptr, b.MultiInBlock(), b.MultiInBlock());
+            mod.CreateInstruction<Loop>(nullptr, b.MultiInBlock(), b.MultiInBlock());
         },
         "internal compiler error");
 }
@@ -62,7 +62,7 @@
         {
             Module mod;
             Builder b{mod};
-            Loop loop(mod.NextInstructionId(), b.Block(), nullptr, b.MultiInBlock());
+            mod.CreateInstruction<Loop>(b.Block(), nullptr, b.MultiInBlock());
         },
         "internal compiler error");
 }
@@ -72,7 +72,7 @@
         {
             Module mod;
             Builder b{mod};
-            Loop loop(mod.NextInstructionId(), b.Block(), b.MultiInBlock(), nullptr);
+            mod.CreateInstruction<Loop>(b.Block(), b.MultiInBlock(), nullptr);
         },
         "internal compiler error");
 }
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index 69f30b6..c77cc36 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -30,6 +30,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "src/tint/lang/core/constant/manager.h"
 #include "src/tint/lang/core/ir/block.h"
@@ -77,6 +78,25 @@
     /// @returns a reference to this module
     Module& operator=(Module&& o);
 
+    /// Creates a new `TYPE` instruction owned by the module
+    /// When the Module is destructed the object will be destructed and freed.
+    /// @param args the arguments to pass to the constructor
+    /// @returns the pointer to the instruction
+    template <typename TYPE, typename... ARGS>
+    TYPE* CreateInstruction(ARGS&&... args) {
+        return allocators_.instructions.Create<TYPE>(NextInstructionId(),
+                                                     std::forward<ARGS>(args)...);
+    }
+
+    /// Creates a new `TYPE` value owned by the module
+    /// When the Module is destructed the object will be destructed and freed.
+    /// @param args the arguments to pass to the constructor
+    /// @returns the pointer to the value
+    template <typename TYPE, typename... ARGS>
+    TYPE* CreateValue(ARGS&&... args) {
+        return allocators_.values.Create<TYPE>(std::forward<ARGS>(args)...);
+    }
+
     /// @param inst the instruction
     /// @return the name of the given instruction, or an invalid symbol if the instruction is not
     /// named or does not have a single return value.
@@ -111,22 +131,22 @@
 
     /// @returns a iterable of all the alive instructions
     FilteredIterable<IsAlive, BlockAllocator<Instruction>::View> Instructions() {
-        return {allocators.instructions.Objects()};
+        return {allocators_.instructions.Objects()};
     }
 
     /// @returns a iterable of all the alive instructions
     FilteredIterable<IsAlive, BlockAllocator<Instruction>::ConstView> Instructions() const {
-        return {allocators.instructions.Objects()};
+        return {allocators_.instructions.Objects()};
     }
 
     /// @returns a iterable of all the alive values
     FilteredIterable<IsAlive, BlockAllocator<Value>::View> Values() {
-        return {allocators.values.Objects()};
+        return {allocators_.values.Objects()};
     }
 
     /// @returns a iterable of all the alive values
     FilteredIterable<IsAlive, BlockAllocator<Value>::ConstView> Values() const {
-        return {allocators.values.Objects()};
+        return {allocators_.values.Objects()};
     }
 
     /// @returns the functions in the module, in dependency order
@@ -134,24 +154,12 @@
     /// @returns the functions in the module, in dependency order
     Vector<const Function*, 16> DependencyOrderedFunctions() const;
 
-    /// @returns the next instruction id for this module
-    Instruction::Id NextInstructionId() { return next_instruction_id_++; }
-
     /// The block allocator
     BlockAllocator<Block> blocks;
 
     /// The constant value manager
     core::constant::Manager constant_values;
 
-    /// The various BlockAllocators for the module
-    struct {
-        /// The instruction allocator
-        BlockAllocator<Instruction> instructions;
-
-        /// The value allocator
-        BlockAllocator<Value> values;
-    } allocators;
-
     /// List of functions in the module.
     Vector<ConstPropagatingPtr<Function>, 8> functions;
 
@@ -165,6 +173,18 @@
     Hashmap<const core::constant::Value*, ir::Constant*, 16> constants;
 
   private:
+    /// @returns the next instruction id for this module
+    Instruction::Id NextInstructionId() { return next_instruction_id_++; }
+
+    /// The various BlockAllocators for the module
+    struct {
+        /// The instruction allocator
+        BlockAllocator<Instruction> instructions;
+
+        /// The value allocator
+        BlockAllocator<Value> values;
+    } allocators_;
+
     Instruction::Id next_instruction_id_ = 0;
 };
 
diff --git a/src/tint/lang/core/ir/next_iteration.cc b/src/tint/lang/core/ir/next_iteration.cc
index b81bdf3..bba8de8 100644
--- a/src/tint/lang/core/ir/next_iteration.cc
+++ b/src/tint/lang/core/ir/next_iteration.cc
@@ -57,8 +57,7 @@
 NextIteration* NextIteration::Clone(CloneContext& ctx) {
     auto* new_loop = ctx.Clone(loop_);
     auto args = ctx.Remap<NextIteration::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<NextIteration>(ctx.ir.NextInstructionId(),
-                                                                new_loop, args);
+    return ctx.ir.CreateInstruction<NextIteration>(new_loop, args);
 }
 
 void NextIteration::SetLoop(ir::Loop* loop) {
diff --git a/src/tint/lang/core/ir/return.cc b/src/tint/lang/core/ir/return.cc
index fa543af..cc3aef2 100644
--- a/src/tint/lang/core/ir/return.cc
+++ b/src/tint/lang/core/ir/return.cc
@@ -53,10 +53,9 @@
 Return* Return::Clone(CloneContext& ctx) {
     auto* fn = ctx.Remap(Func());
     if (auto* val = Value()) {
-        return ctx.ir.allocators.instructions.Create<Return>(ctx.ir.NextInstructionId(), fn,
-                                                             ctx.Remap(val));
+        return ctx.ir.CreateInstruction<Return>(fn, ctx.Remap(val));
     }
-    return ctx.ir.allocators.instructions.Create<Return>(ctx.ir.NextInstructionId(), fn);
+    return ctx.ir.CreateInstruction<Return>(fn);
 }
 
 Function* Return::Func() {
diff --git a/src/tint/lang/core/ir/store.cc b/src/tint/lang/core/ir/store.cc
index 5f65d61..0acd27b 100644
--- a/src/tint/lang/core/ir/store.cc
+++ b/src/tint/lang/core/ir/store.cc
@@ -50,7 +50,7 @@
 Store* Store::Clone(CloneContext& ctx) {
     auto* to = ctx.Remap(To());
     auto* from = ctx.Remap(From());
-    return ctx.ir.allocators.instructions.Create<Store>(ctx.ir.NextInstructionId(), to, from);
+    return ctx.ir.CreateInstruction<Store>(to, from);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store_vector_element.cc b/src/tint/lang/core/ir/store_vector_element.cc
index 4077a23..8495003 100644
--- a/src/tint/lang/core/ir/store_vector_element.cc
+++ b/src/tint/lang/core/ir/store_vector_element.cc
@@ -53,8 +53,7 @@
     auto* to = ctx.Remap(To());
     auto* idx = ctx.Remap(Index());
     auto* val = ctx.Remap(Value());
-    return ctx.ir.allocators.instructions.Create<StoreVectorElement>(ctx.ir.NextInstructionId(), to,
-                                                                     idx, val);
+    return ctx.ir.CreateInstruction<StoreVectorElement>(to, idx, val);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/switch.cc b/src/tint/lang/core/ir/switch.cc
index 8717c84..bf7e9ed 100644
--- a/src/tint/lang/core/ir/switch.cc
+++ b/src/tint/lang/core/ir/switch.cc
@@ -61,8 +61,7 @@
 
 Switch* Switch::Clone(CloneContext& ctx) {
     auto* cond = ctx.Remap(Condition());
-    auto* new_switch =
-        ctx.ir.allocators.instructions.Create<Switch>(ctx.ir.NextInstructionId(), cond);
+    auto* new_switch = ctx.ir.CreateInstruction<Switch>(cond);
     ctx.Replace(this, new_switch);
 
     new_switch->cases_.Reserve(cases_.Length());
diff --git a/src/tint/lang/core/ir/swizzle.cc b/src/tint/lang/core/ir/swizzle.cc
index 5bf32f4..ddcf447 100644
--- a/src/tint/lang/core/ir/swizzle.cc
+++ b/src/tint/lang/core/ir/swizzle.cc
@@ -57,8 +57,7 @@
 Swizzle* Swizzle::Clone(CloneContext& ctx) {
     auto* result = ctx.Clone(Result(0));
     auto* obj = ctx.Remap(Object());
-    return ctx.ir.allocators.instructions.Create<Swizzle>(ctx.ir.NextInstructionId(), result, obj,
-                                                          indices_);
+    return ctx.ir.CreateInstruction<Swizzle>(result, obj, indices_);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/terminate_invocation.cc b/src/tint/lang/core/ir/terminate_invocation.cc
index 8f1cd66..e007988 100644
--- a/src/tint/lang/core/ir/terminate_invocation.cc
+++ b/src/tint/lang/core/ir/terminate_invocation.cc
@@ -39,7 +39,7 @@
 TerminateInvocation::~TerminateInvocation() = default;
 
 TerminateInvocation* TerminateInvocation::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.instructions.Create<TerminateInvocation>(ctx.ir.NextInstructionId());
+    return ctx.ir.CreateInstruction<TerminateInvocation>();
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unreachable.cc b/src/tint/lang/core/ir/unreachable.cc
index 24a0343..1fafa6c 100644
--- a/src/tint/lang/core/ir/unreachable.cc
+++ b/src/tint/lang/core/ir/unreachable.cc
@@ -39,7 +39,7 @@
 Unreachable::~Unreachable() = default;
 
 Unreachable* Unreachable::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.instructions.Create<Unreachable>(ctx.ir.NextInstructionId());
+    return ctx.ir.CreateInstruction<Unreachable>();
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unused.cc b/src/tint/lang/core/ir/unused.cc
index 167510e..37f8b24 100644
--- a/src/tint/lang/core/ir/unused.cc
+++ b/src/tint/lang/core/ir/unused.cc
@@ -39,7 +39,7 @@
 Unused::~Unused() = default;
 
 Unused* Unused::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.values.Create<Unused>();
+    return ctx.ir.CreateValue<Unused>();
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/user_call.cc b/src/tint/lang/core/ir/user_call.cc
index 6696977..c709946 100644
--- a/src/tint/lang/core/ir/user_call.cc
+++ b/src/tint/lang/core/ir/user_call.cc
@@ -54,8 +54,7 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* target = ctx.Remap(Target());
     auto args = ctx.Remap<UserCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<UserCall>(ctx.ir.NextInstructionId(), new_result,
-                                                           target, args);
+    return ctx.ir.CreateInstruction<UserCall>(new_result, target, args);
 }
 
 void UserCall::SetArgs(VectorRef<Value*> arguments) {
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 4115311..5c7235f 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -2652,7 +2652,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Var_RootBlock_NullResult) {
-    auto* v = mod.allocators.instructions.Create<ir::Var>(mod.NextInstructionId(), nullptr);
+    auto* v = mod.CreateInstruction<ir::Var>(nullptr);
     v->SetInitializer(b.Constant(0_i));
     mod.root_block->Append(v);
 
@@ -2683,7 +2683,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_NullResult) {
-    auto* v = mod.allocators.instructions.Create<ir::Var>(mod.NextInstructionId(), nullptr);
+    auto* v = mod.CreateInstruction<ir::Var>(nullptr);
     v->SetInitializer(b.Constant(0_i));
 
     auto* f = b.Function("my_func", ty.void_());
@@ -3108,8 +3108,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Let_NullResult) {
-    auto* v = mod.allocators.instructions.Create<ir::Let>(mod.NextInstructionId(), nullptr,
-                                                          b.Constant(1_i));
+    auto* v = mod.CreateInstruction<ir::Let>(nullptr, b.Constant(1_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -3138,8 +3137,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Let_NullValue) {
-    auto* v = mod.allocators.instructions.Create<ir::Let>(mod.NextInstructionId(),
-                                                          b.InstructionResult(ty.f32()), nullptr);
+    auto* v = mod.CreateInstruction<ir::Let>(b.InstructionResult(ty.f32()), nullptr);
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -3168,8 +3166,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Let_WrongType) {
-    auto* v = mod.allocators.instructions.Create<ir::Let>(
-        mod.NextInstructionId(), b.InstructionResult(ty.f32()), b.Constant(1_i));
+    auto* v = mod.CreateInstruction<ir::Let>(b.InstructionResult(ty.f32()), b.Constant(1_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -3406,8 +3403,8 @@
 }
 
 TEST_F(IR_ValidatorTest, Binary_Result_Nullptr) {
-    auto* bin = mod.allocators.instructions.Create<ir::CoreBinary>(
-        mod.NextInstructionId(), nullptr, BinaryOp::kAdd, b.Constant(3_i), b.Constant(2_i));
+    auto* bin = mod.CreateInstruction<ir::CoreBinary>(nullptr, BinaryOp::kAdd, b.Constant(3_i),
+                                                      b.Constant(2_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -3527,8 +3524,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Unary_Result_Nullptr) {
-    auto* bin = mod.allocators.instructions.Create<ir::CoreUnary>(
-        mod.NextInstructionId(), nullptr, UnaryOp::kNegation, b.Constant(2_i));
+    auto* bin = mod.CreateInstruction<ir::CoreUnary>(nullptr, UnaryOp::kNegation, b.Constant(2_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -3669,8 +3665,7 @@
 
 TEST_F(IR_ValidatorTest, ExitIf_NullIf) {
     auto* if_ = b.If(true);
-    if_->True()->Append(
-        mod.allocators.instructions.Create<ExitIf>(mod.NextInstructionId(), nullptr));
+    if_->True()->Append(mod.CreateInstruction<ExitIf>(nullptr));
 
     auto* f = b.Function("my_func", ty.void_());
     auto sb = b.Append(f->Block());
@@ -4057,7 +4052,7 @@
     auto* switch_ = b.Switch(1_i);
 
     auto* def = b.DefaultCase(switch_);
-    def->Append(mod.allocators.instructions.Create<ExitSwitch>(mod.NextInstructionId(), nullptr));
+    def->Append(mod.CreateInstruction<ExitSwitch>(nullptr));
 
     auto* f = b.Function("my_func", ty.void_());
     auto sb = b.Append(f->Block());
@@ -5392,8 +5387,7 @@
 TEST_F(IR_ValidatorTest, ExitLoop_NullLoop) {
     auto* loop = b.Loop();
     loop->Continuing()->Append(b.NextIteration(loop));
-    loop->Body()->Append(
-        mod.allocators.instructions.Create<ExitLoop>(mod.NextInstructionId(), nullptr));
+    loop->Body()->Append(mod.CreateInstruction<ExitLoop>(nullptr));
 
     auto* f = b.Function("my_func", ty.void_());
     auto sb = b.Append(f->Block());
@@ -6155,8 +6149,7 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::Load>(
-            mod.NextInstructionId(), b.InstructionResult(ty.i32()), nullptr));
+        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.i32()), nullptr));
         b.Return(f);
     });
 
@@ -6185,8 +6178,7 @@
 
     b.Append(f->Block(), [&] {
         auto* let = b.Let("l", 1_i);
-        b.Append(mod.allocators.instructions.Create<ir::Load>(
-            mod.NextInstructionId(), b.InstructionResult(ty.f32()), let->Result(0)));
+        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.f32()), let->Result(0)));
         b.Return(f);
     });
 
@@ -6217,8 +6209,7 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        b.Append(mod.allocators.instructions.Create<ir::Load>(
-            mod.NextInstructionId(), b.InstructionResult(ty.f32()), var->Result(0)));
+        b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.f32()), var->Result(0)));
         b.Return(f);
     });
 
@@ -6249,8 +6240,7 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        auto* load = mod.allocators.instructions.Create<ir::Load>(mod.NextInstructionId(), nullptr,
-                                                                  var->Result(0));
+        auto* load = mod.CreateInstruction<ir::Load>(nullptr, var->Result(0));
         load->ClearResults();
         b.Append(load);
         b.Return(f);
@@ -6282,8 +6272,7 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(), nullptr,
-                                                               b.Constant(42_i)));
+        b.Append(mod.CreateInstruction<ir::Store>(nullptr, b.Constant(42_i)));
         b.Return(f);
     });
 
@@ -6312,8 +6301,7 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
-                                                               var->Result(0), nullptr));
+        b.Append(mod.CreateInstruction<ir::Store>(var->Result(0), nullptr));
         b.Return(f);
     });
 
@@ -6342,8 +6330,7 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(), nullptr,
-                                                               nullptr));
+        b.Append(mod.CreateInstruction<ir::Store>(nullptr, nullptr));
         b.Return(f);
     });
 
@@ -6380,8 +6367,7 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        auto* store = mod.allocators.instructions.Create<ir::Store>(
-            mod.NextInstructionId(), var->Result(0), b.Constant(42_i));
+        auto* store = mod.CreateInstruction<ir::Store>(var->Result(0), b.Constant(42_i));
         store->SetResults(Vector{b.InstructionResult(ty.i32())});
         b.Append(store);
         b.Return(f);
@@ -6413,8 +6399,7 @@
 
     b.Append(f->Block(), [&] {
         auto* let = b.Let("l", 1_i);
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
-                                                               let->Result(0), b.Constant(42_u)));
+        b.Append(mod.CreateInstruction<ir::Store>(let->Result(0), b.Constant(42_u)));
         b.Return(f);
     });
 
@@ -6445,8 +6430,7 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
-                                                               var->Result(0), b.Constant(42_u)));
+        b.Append(mod.CreateInstruction<ir::Store>(var->Result(0), b.Constant(42_u)));
         b.Return(f);
     });
 
@@ -6478,8 +6462,7 @@
     b.Append(f->Block(), [&] {
         auto* result = b.InstructionResult(ty.u32());
         result->SetType(nullptr);
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(), result,
-                                                               b.Constant(42_u)));
+        b.Append(mod.CreateInstruction<ir::Store>(result, b.Constant(42_u)));
         b.Return(f);
     });
 
@@ -6520,8 +6503,7 @@
         auto* val = b.Construct(ty.u32(), 42_u);
         val->Result(0)->SetType(nullptr);
 
-        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
-                                                               var->Result(0), val->Result(0)));
+        b.Append(mod.CreateInstruction<ir::Store>(var->Result(0), val->Result(0)));
         b.Return(f);
     });
 
@@ -6561,8 +6543,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
-        b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(
-            mod.NextInstructionId(), nullptr, var->Result(0), b.Constant(1_i)));
+        b.Append(
+            mod.CreateInstruction<ir::LoadVectorElement>(nullptr, var->Result(0), b.Constant(1_i)));
         b.Return(f);
     });
 
@@ -6600,8 +6582,8 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(
-            mod.NextInstructionId(), b.InstructionResult(ty.f32()), nullptr, b.Constant(1_i)));
+        b.Append(mod.CreateInstruction<ir::LoadVectorElement>(b.InstructionResult(ty.f32()),
+                                                              nullptr, b.Constant(1_i)));
         b.Return(f);
     });
 
@@ -6630,8 +6612,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
-        b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(
-            mod.NextInstructionId(), b.InstructionResult(ty.f32()), var->Result(0), nullptr));
+        b.Append(mod.CreateInstruction<ir::LoadVectorElement>(b.InstructionResult(ty.f32()),
+                                                              var->Result(0), nullptr));
         b.Return(f);
     });
 
@@ -6724,8 +6706,8 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(
-            mod.NextInstructionId(), nullptr, b.Constant(1_i), b.Constant(2_f)));
+        b.Append(mod.CreateInstruction<ir::StoreVectorElement>(nullptr, b.Constant(1_i),
+                                                               b.Constant(2_f)));
         b.Return(f);
     });
 
@@ -6754,8 +6736,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
-        b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(
-            mod.NextInstructionId(), var->Result(0), nullptr, b.Constant(2_f)));
+        b.Append(mod.CreateInstruction<ir::StoreVectorElement>(var->Result(0), nullptr,
+                                                               b.Constant(2_f)));
         b.Return(f);
     });
 
@@ -6785,8 +6767,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
-        b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(
-            mod.NextInstructionId(), var->Result(0), b.Constant(1_i), nullptr));
+        b.Append(mod.CreateInstruction<ir::StoreVectorElement>(var->Result(0), b.Constant(1_i),
+                                                               nullptr));
         b.Return(f);
     });
 
@@ -7267,7 +7249,7 @@
 TEST_F(IR_ValidatorTest, Switch_NoCondition) {
     auto* f = b.Function("my_func", ty.void_());
 
-    auto* s = b.ir.allocators.instructions.Create<ir::Switch>(mod.NextInstructionId());
+    auto* s = b.ir.CreateInstruction<ir::Switch>();
     f->Block()->Append(s);
     b.Append(b.DefaultCase(s), [&] { b.ExitSwitch(s); });
     f->Block()->Append(b.Return(f));
diff --git a/src/tint/lang/core/ir/var.cc b/src/tint/lang/core/ir/var.cc
index 3fca232..54912b6 100644
--- a/src/tint/lang/core/ir/var.cc
+++ b/src/tint/lang/core/ir/var.cc
@@ -53,8 +53,7 @@
 
 Var* Var::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
-    auto* new_var =
-        ctx.ir.allocators.instructions.Create<Var>(ctx.ir.NextInstructionId(), new_result);
+    auto* new_var = ctx.ir.CreateInstruction<Var>(new_result);
 
     new_var->binding_point_ = binding_point_;
     new_var->attributes_ = attributes_;
diff --git a/src/tint/lang/hlsl/ir/builtin_call.cc b/src/tint/lang/hlsl/ir/builtin_call.cc
index b1efb5e..39f7c05 100644
--- a/src/tint/lang/hlsl/ir/builtin_call.cc
+++ b/src/tint/lang/hlsl/ir/builtin_call.cc
@@ -51,8 +51,7 @@
 BuiltinCall* BuiltinCall::Clone(core::ir::CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto new_args = ctx.Clone<BuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
-                                                              new_result, func_, new_args);
+    return ctx.ir.CreateInstruction<BuiltinCall>(new_result, func_, new_args);
 }
 
 }  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/member_builtin_call.cc b/src/tint/lang/hlsl/ir/member_builtin_call.cc
index 7e3ba72..d595917 100644
--- a/src/tint/lang/hlsl/ir/member_builtin_call.cc
+++ b/src/tint/lang/hlsl/ir/member_builtin_call.cc
@@ -57,8 +57,8 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* new_object = ctx.Clone(Object());
     auto new_args = ctx.Clone<MemberBuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<MemberBuiltinCall>(
-        ctx.ir.NextInstructionId(), new_result, func_, new_object, std::move(new_args));
+    return ctx.ir.CreateInstruction<MemberBuiltinCall>(new_result, func_, new_object,
+                                                       std::move(new_args));
 }
 
 }  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/ternary.cc b/src/tint/lang/hlsl/ir/ternary.cc
index ef092ce..db292bc 100644
--- a/src/tint/lang/hlsl/ir/ternary.cc
+++ b/src/tint/lang/hlsl/ir/ternary.cc
@@ -44,8 +44,7 @@
 Ternary* Ternary::Clone(core::ir::CloneContext& ctx) {
     auto new_result = ctx.Clone(Result(0));
     auto new_args = ctx.Remap<Ternary::kDefaultNumOperands>(operands_);
-    return ctx.ir.allocators.instructions.Create<Ternary>(ctx.ir.NextInstructionId(), new_result,
-                                                          new_args);
+    return ctx.ir.CreateInstruction<Ternary>(new_result, new_args);
 }
 
 }  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/ternary_test.cc b/src/tint/lang/hlsl/ir/ternary_test.cc
index 4c3baac..eae38f3 100644
--- a/src/tint/lang/hlsl/ir/ternary_test.cc
+++ b/src/tint/lang/hlsl/ir/ternary_test.cc
@@ -45,8 +45,7 @@
     auto* cmp = b.Constant(true);
 
     Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
-    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.ir.NextInstructionId(),
-                                                           b.InstructionResult(ty.u32()), args);
+    auto* t = b.ir.CreateInstruction<Ternary>(b.InstructionResult(ty.u32()), args);
 
     EXPECT_THAT(false_->UsagesUnsorted(), testing::UnorderedElementsAre(core::ir::Usage{t, 0u}));
     EXPECT_THAT(true_->UsagesUnsorted(), testing::UnorderedElementsAre(core::ir::Usage{t, 1u}));
@@ -59,8 +58,7 @@
     auto* cmp = b.Constant(true);
 
     Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
-    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.ir.NextInstructionId(),
-                                                           b.InstructionResult(ty.u32()), args);
+    auto* t = b.ir.CreateInstruction<Ternary>(b.InstructionResult(ty.u32()), args);
 
     EXPECT_EQ(t->Results().Length(), 1u);
 
@@ -74,8 +72,7 @@
     auto* cmp = b.Constant(true);
 
     Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
-    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.ir.NextInstructionId(),
-                                                           b.InstructionResult(ty.u32()), args);
+    auto* t = b.ir.CreateInstruction<Ternary>(b.InstructionResult(ty.u32()), args);
 
     auto* new_t = clone_ctx.Clone(t);
 
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
index 10adb3e..7253143 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
@@ -507,8 +507,7 @@
 
     void Select(core::ir::CoreBuiltinCall* call) {
         Vector<core::ir::Value*, 4> args = call->Args();
-        auto* ternary = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-            b.ir.NextInstructionId(), call->DetachResult(), args);
+        auto* ternary = b.ir.CreateInstruction<hlsl::ir::Ternary>(call->DetachResult(), args);
         ternary->InsertBefore(call);
         call->Destroy();
     }
@@ -528,8 +527,7 @@
             args.Push(b.Call(type, core::BuiltinFn::kCeil, val)->Result(0));
             args.Push(b.LessThan(ty.match_width(ty.bool_(), type), val, b.Zero(type))->Result(0));
         });
-        auto* trunc = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-            b.ir.NextInstructionId(), call->DetachResult(), args);
+        auto* trunc = b.ir.CreateInstruction<hlsl::ir::Ternary>(call->DetachResult(), args);
         trunc->InsertBefore(call);
 
         call->Destroy();
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
index d6b0d36..92537ea 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
@@ -347,8 +347,8 @@
             auto* cond = b.Equal(ty.bool_(), b.Modulo(ty.u32(), byte_idx, 4_u), 0_u);
 
             Vector<core::ir::Value*, 3> args{false_, true_, cond->Result(0)};
-            auto* shift_amt = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-                b.ir.NextInstructionId(), b.InstructionResult(ty.u32()), args);
+            auto* shift_amt =
+                b.ir.CreateInstruction<hlsl::ir::Ternary>(b.InstructionResult(ty.u32()), args);
             b.Append(shift_amt);
 
             load = b.ShiftRight(ty.u32(), load, shift_amt);
@@ -408,8 +408,8 @@
                 Vector<core::ir::Value*, 3> args{sw_rhs->Result(0), sw_lhs->Result(0),
                                                  cond->Result(0)};
 
-                load = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-                    b.ir.NextInstructionId(), b.InstructionResult(ty.vec2<u32>()), args);
+                load = b.ir.CreateInstruction<hlsl::ir::Ternary>(
+                    b.InstructionResult(ty.vec2<u32>()), args);
                 b.Append(load);
             }
         } else {
@@ -454,8 +454,8 @@
                 Vector<core::ir::Value*, 3> args{sw_rhs->Result(0), sw_lhs->Result(0),
                                                  cond->Result(0)};
 
-                load = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-                    b.ir.NextInstructionId(), b.InstructionResult(ty.u32()), args);
+                load =
+                    b.ir.CreateInstruction<hlsl::ir::Ternary>(b.InstructionResult(ty.u32()), args);
                 b.Append(load);
             }
             return b.Bitcast(result_ty, load);
diff --git a/src/tint/lang/msl/ir/binary.cc b/src/tint/lang/msl/ir/binary.cc
index 04ade0d..34a88ac 100644
--- a/src/tint/lang/msl/ir/binary.cc
+++ b/src/tint/lang/msl/ir/binary.cc
@@ -50,8 +50,7 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* new_rhs = ctx.Remap(RHS());
     auto* new_lhs = ctx.Remap(LHS());
-    return ctx.ir.allocators.instructions.Create<Binary>(ctx.ir.NextInstructionId(), new_result,
-                                                         Op(), new_lhs, new_rhs);
+    return ctx.ir.CreateInstruction<Binary>(new_result, Op(), new_lhs, new_rhs);
 }
 
 }  // namespace tint::msl::ir
diff --git a/src/tint/lang/msl/ir/builtin_call.cc b/src/tint/lang/msl/ir/builtin_call.cc
index db3c2ed..06519c4 100644
--- a/src/tint/lang/msl/ir/builtin_call.cc
+++ b/src/tint/lang/msl/ir/builtin_call.cc
@@ -51,8 +51,7 @@
 BuiltinCall* BuiltinCall::Clone(core::ir::CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto new_args = ctx.Clone<BuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
-                                                              new_result, func_, new_args);
+    return ctx.ir.CreateInstruction<BuiltinCall>(new_result, func_, new_args);
 }
 
 }  // namespace tint::msl::ir
diff --git a/src/tint/lang/msl/ir/member_builtin_call.cc b/src/tint/lang/msl/ir/member_builtin_call.cc
index 5bea168..0857f37 100644
--- a/src/tint/lang/msl/ir/member_builtin_call.cc
+++ b/src/tint/lang/msl/ir/member_builtin_call.cc
@@ -57,8 +57,8 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* new_object = ctx.Clone(Object());
     auto new_args = ctx.Clone<MemberBuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<MemberBuiltinCall>(
-        ctx.ir.NextInstructionId(), new_result, func_, new_object, std::move(new_args));
+    return ctx.ir.CreateInstruction<MemberBuiltinCall>(new_result, func_, new_object,
+                                                       std::move(new_args));
 }
 
 }  // namespace tint::msl::ir
diff --git a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
index d67ce0c..e024197 100644
--- a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
@@ -288,8 +288,8 @@
     /// @param builtin the builtin call instruction
     void AtomicCall(core::ir::CoreBuiltinCall* builtin, msl::BuiltinFn intrinsic) {
         auto args = Vector<core::ir::Value*, 4>{builtin->Args()};
-        args.Push(ir.allocators.values.Create<msl::ir::MemoryOrder>(
-            b.ConstantValue(u32(std::memory_order_relaxed))));
+        args.Push(
+            ir.CreateValue<msl::ir::MemoryOrder>(b.ConstantValue(u32(std::memory_order_relaxed))));
         auto* call = b.CallWithResult<msl::ir::BuiltinCall>(builtin->DetachResult(), intrinsic,
                                                             std::move(args));
         call->InsertBefore(builtin);
@@ -315,7 +315,7 @@
             func->SetParams({ptr, cmp, val});
             b.Append(func->Block(), [&] {
                 auto* old_value = b.Var<function>("old_value", cmp)->Result(0);
-                auto* order = ir.allocators.values.Create<msl::ir::MemoryOrder>(
+                auto* order = ir.CreateValue<msl::ir::MemoryOrder>(
                     b.ConstantValue(u32(std::memory_order_relaxed)));
                 auto* call = b.Call<msl::ir::BuiltinCall>(
                     ty.bool_(), BuiltinFn::kAtomicCompareExchangeWeakExplicit,
@@ -637,7 +637,7 @@
             if (component->Type()->Is<core::type::I32>()) {
                 component = b.Constant(component->Value()->ValueAs<u32>());
             }
-            args.Push(ir.allocators.values.Create<msl::ir::Component>(component->Value()));
+            args.Push(ir.CreateValue<msl::ir::Component>(component->Value()));
         }
 
         // Call the `gather()` member function.
diff --git a/src/tint/lang/spirv/ir/builtin_call.cc b/src/tint/lang/spirv/ir/builtin_call.cc
index 1b7c8fa..3359fb0 100644
--- a/src/tint/lang/spirv/ir/builtin_call.cc
+++ b/src/tint/lang/spirv/ir/builtin_call.cc
@@ -51,8 +51,7 @@
 BuiltinCall* BuiltinCall::Clone(core::ir::CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto new_args = ctx.Clone<BuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
-                                                              new_result, func_, new_args);
+    return ctx.ir.CreateInstruction<BuiltinCall>(new_result, func_, new_args);
 }
 
 }  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
index aee71d2..a414f7a 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -198,7 +198,7 @@
     /// @param value the literal value
     /// @returns the literal operand
     spirv::ir::LiteralOperand* Literal(u32 value) {
-        return ir.allocators.values.Create<spirv::ir::LiteralOperand>(b.ConstantValue(value));
+        return ir.CreateValue<spirv::ir::LiteralOperand>(b.ConstantValue(value));
     }
 
     /// Handle an `arrayLength()` builtin.
diff --git a/src/tint/lang/wgsl/ir/builtin_call.cc b/src/tint/lang/wgsl/ir/builtin_call.cc
index f4777d1..064fd87 100644
--- a/src/tint/lang/wgsl/ir/builtin_call.cc
+++ b/src/tint/lang/wgsl/ir/builtin_call.cc
@@ -51,8 +51,7 @@
 BuiltinCall* BuiltinCall::Clone(core::ir::CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto new_args = ctx.Clone<BuiltinCall::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
-                                                              new_result, fn_, new_args);
+    return ctx.ir.CreateInstruction<BuiltinCall>(new_result, fn_, new_args);
 }
 
 }  // namespace tint::wgsl::ir
diff --git a/src/tint/lang/wgsl/ir/unary.cc b/src/tint/lang/wgsl/ir/unary.cc
index ab14e91..cc4648b 100644
--- a/src/tint/lang/wgsl/ir/unary.cc
+++ b/src/tint/lang/wgsl/ir/unary.cc
@@ -45,8 +45,7 @@
 Unary* Unary::Clone(core::ir::CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Val());
-    return ctx.ir.allocators.instructions.Create<Unary>(ctx.ir.NextInstructionId(), new_result,
-                                                        Op(), val);
+    return ctx.ir.CreateInstruction<Unary>(new_result, Op(), val);
 }
 
 const core::intrinsic::TableData& Unary::TableData() const {
diff --git a/src/tint/lang/wgsl/reader/lower/lower_test.cc b/src/tint/lang/wgsl/reader/lower/lower_test.cc
index 2486d17..079eb05 100644
--- a/src/tint/lang/wgsl/reader/lower/lower_test.cc
+++ b/src/tint/lang/wgsl/reader/lower/lower_test.cc
@@ -45,12 +45,11 @@
     auto* f = b.Function("f", ty.void_());
     b.Append(f->Block(), [&] {  //
         auto* result = b.InstructionResult(ty.i32());
-        b.Append(b.ir.allocators.instructions.Create<wgsl::ir::BuiltinCall>(
-            b.ir.NextInstructionId(), result, wgsl::BuiltinFn::kMax,
-            Vector{
-                b.Value(i32(1)),
-                b.Value(i32(2)),
-            }));
+        b.Append(b.ir.CreateInstruction<wgsl::ir::BuiltinCall>(result, wgsl::BuiltinFn::kMax,
+                                                               Vector{
+                                                                   b.Value(i32(1)),
+                                                                   b.Value(i32(2)),
+                                                               }));
         b.Return(f);
     });
 
@@ -85,9 +84,8 @@
     auto* f = b.Function("f", ty.i32());
     b.Append(f->Block(), [&] {  //
         auto* result = b.InstructionResult(ty.i32());
-        b.Append(b.ir.allocators.instructions.Create<wgsl::ir::BuiltinCall>(
-            b.ir.NextInstructionId(), result, wgsl::BuiltinFn::kWorkgroupUniformLoad,
-            Vector{wgvar->Result(0)}));
+        b.Append(b.ir.CreateInstruction<wgsl::ir::BuiltinCall>(
+            result, wgsl::BuiltinFn::kWorkgroupUniformLoad, Vector{wgvar->Result(0)}));
         b.Return(f, result);
     });
 
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index ac630c6..e8f12e6 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -977,10 +977,8 @@
                         inst = impl.builder_.Bitcast(ty, args[0]);
                     } else {
                         auto* res = impl.builder_.InstructionResult(ty);
-                        inst =
-                            impl.builder_.ir.allocators.instructions.Create<wgsl::ir::BuiltinCall>(
-                                impl.builder_.ir.NextInstructionId(), res, b->Fn(),
-                                std::move(args));
+                        inst = impl.builder_.ir.CreateInstruction<wgsl::ir::BuiltinCall>(
+                            res, b->Fn(), std::move(args));
                     }
                 } else if (sem->Target()->As<sem::ValueConstructor>()) {
                     inst = impl.builder_.Construct(ty, std::move(args));
diff --git a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
index 6a7b9ee..6143817 100644
--- a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
+++ b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
@@ -100,9 +100,7 @@
         TINT_ASSERT(operand);
         if (auto* ref_ty = As<core::type::Reference>(operand->Type())) {
             auto* as_ptr = b.InstructionResult(RefToPtr(ref_ty));
-            mod.allocators.instructions
-                .Create<wgsl::ir::Unary>(mod.NextInstructionId(), as_ptr, core::UnaryOp::kAddressOf,
-                                         operand)
+            mod.CreateInstruction<wgsl::ir::Unary>(as_ptr, core::UnaryOp::kAddressOf, operand)
                 ->InsertBefore(use.instruction);
             use.instruction->SetOperand(use.operand_index, as_ptr);
         }
@@ -117,9 +115,7 @@
         auto* operand = use.instruction->Operand(use.operand_index);
         if (auto* ptr_ty = As<core::type::Pointer>(operand->Type())) {
             auto* as_ptr = b.InstructionResult(PtrToRef(ptr_ty));
-            mod.allocators.instructions
-                .Create<wgsl::ir::Unary>(mod.NextInstructionId(), as_ptr,
-                                         core::UnaryOp::kIndirection, operand)
+            mod.CreateInstruction<wgsl::ir::Unary>(as_ptr, core::UnaryOp::kIndirection, operand)
                 ->InsertBefore(use.instruction);
             use.instruction->SetOperand(use.operand_index, as_ptr);
         }