Add ID to Instruction.

This CL adds an `id` into each Instruction which is retrieved from the
module at creation.

Bug: 354711610
Change-Id: I0b34739134a743cfc430162ce50e888611e56c04
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/201157
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/access.cc b/src/tint/lang/core/ir/access.cc
index b9f750e..b57ba8f 100644
--- a/src/tint/lang/core/ir/access.cc
+++ b/src/tint/lang/core/ir/access.cc
@@ -37,9 +37,10 @@
 namespace tint::core::ir {
 
 //! @cond Doxygen_Suppress
-Access::Access() = default;
+Access::Access(Id id) : Base(id) {}
 
-Access::Access(InstructionResult* result, Value* object, VectorRef<Value*> indices) {
+Access::Access(Id id, InstructionResult* result, Value* object, VectorRef<Value*> indices)
+    : Base(id) {
     AddOperand(Access::kObjectOperandOffset, object);
     AddOperands(Access::kIndicesOperandOffset, std::move(indices));
     AddResult(result);
@@ -51,7 +52,8 @@
     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>(new_result, obj, indices);
+    return ctx.ir.allocators.instructions.Create<Access>(ctx.ir.NextInstructionId(), new_result,
+                                                         obj, indices);
 }
 //! @endcond
 
diff --git a/src/tint/lang/core/ir/access.h b/src/tint/lang/core/ir/access.h
index 0df6ee4..8522878 100644
--- a/src/tint/lang/core/ir/access.h
+++ b/src/tint/lang/core/ir/access.h
@@ -51,13 +51,16 @@
     static constexpr size_t kMinNumOperands = 1;
 
     /// Constructor (no results, no operands)
-    Access();
+    /// @param id the instruction id
+    explicit Access(Instruction::Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param object the accessor object
     /// @param indices the indices to access
-    Access(InstructionResult* result, Value* object, VectorRef<Value*> indices);
+    Access(Instruction::Id id, InstructionResult* result, Value* object, VectorRef<Value*> indices);
+
     ~Access() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/binary.cc b/src/tint/lang/core/ir/binary.cc
index 2738b01..081e33f 100644
--- a/src/tint/lang/core/ir/binary.cc
+++ b/src/tint/lang/core/ir/binary.cc
@@ -34,9 +34,10 @@
 
 namespace tint::core::ir {
 
-Binary::Binary() = default;
+Binary::Binary(Id id) : Base(id) {}
 
-Binary::Binary(InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs) : op_(op) {
+Binary::Binary(Id id, InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs)
+    : Base(id), op_(op) {
     AddOperand(Binary::kLhsOperandOffset, lhs);
     AddOperand(Binary::kRhsOperandOffset, rhs);
     AddResult(result);
diff --git a/src/tint/lang/core/ir/binary.h b/src/tint/lang/core/ir/binary.h
index 1a6600d..cefae72 100644
--- a/src/tint/lang/core/ir/binary.h
+++ b/src/tint/lang/core/ir/binary.h
@@ -56,14 +56,16 @@
     static constexpr size_t kNumOperands = 2;
 
     /// Constructor (no results, no operands)
-    Binary();
+    /// @param id the instruction id
+    explicit Binary(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param op the binary operator
     /// @param lhs the lhs of the instruction
     /// @param rhs the rhs of the instruction
-    Binary(InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs);
+    Binary(Id id, InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs);
     ~Binary() override;
 
     /// @returns the binary operator
diff --git a/src/tint/lang/core/ir/binary/decode.cc b/src/tint/lang/core/ir/binary/decode.cc
index 96e4154..336bfdd 100644
--- a/src/tint/lang/core/ir/binary/decode.cc
+++ b/src/tint/lang/core/ir/binary/decode.cc
@@ -466,89 +466,99 @@
     }
 
     ir::Access* CreateInstructionAccess(const pb::InstructionAccess&) {
-        return mod_out_.allocators.instructions.Create<ir::Access>();
+        return mod_out_.allocators.instructions.Create<ir::Access>(mod_out_.NextInstructionId());
     }
 
     ir::CoreBinary* CreateInstructionBinary(const pb::InstructionBinary& binary_in) {
-        auto* binary_out = mod_out_.allocators.instructions.Create<ir::CoreBinary>();
+        auto* binary_out =
+            mod_out_.allocators.instructions.Create<ir::CoreBinary>(mod_out_.NextInstructionId());
         binary_out->SetOp(BinaryOp(binary_in.op()));
         return binary_out;
     }
 
     ir::Bitcast* CreateInstructionBitcast(const pb::InstructionBitcast&) {
-        return mod_out_.allocators.instructions.Create<ir::Bitcast>();
+        return mod_out_.allocators.instructions.Create<ir::Bitcast>(mod_out_.NextInstructionId());
     }
 
     ir::BreakIf* CreateInstructionBreakIf(const pb::InstructionBreakIf&) {
-        auto* break_if_out = mod_out_.allocators.instructions.Create<ir::BreakIf>();
+        auto* break_if_out =
+            mod_out_.allocators.instructions.Create<ir::BreakIf>(mod_out_.NextInstructionId());
         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>();
+        auto* call_out = mod_out_.allocators.instructions.Create<ir::CoreBuiltinCall>(
+            mod_out_.NextInstructionId());
         call_out->SetFunc(BuiltinFn(call_in.builtin()));
         return call_out;
     }
 
     ir::Construct* CreateInstructionConstruct(const pb::InstructionConstruct&) {
-        return mod_out_.allocators.instructions.Create<ir::Construct>();
+        return mod_out_.allocators.instructions.Create<ir::Construct>(mod_out_.NextInstructionId());
     }
 
     ir::Continue* CreateInstructionContinue(const pb::InstructionContinue&) {
-        auto* continue_ = mod_out_.allocators.instructions.Create<ir::Continue>();
+        auto* continue_ =
+            mod_out_.allocators.instructions.Create<ir::Continue>(mod_out_.NextInstructionId());
         continues_.Push(continue_);
         return continue_;
     }
 
     ir::Convert* CreateInstructionConvert(const pb::InstructionConvert&) {
-        return mod_out_.allocators.instructions.Create<ir::Convert>();
+        return mod_out_.allocators.instructions.Create<ir::Convert>(mod_out_.NextInstructionId());
     }
 
     ir::ExitIf* CreateInstructionExitIf(const pb::InstructionExitIf&) {
-        auto* exit_out = mod_out_.allocators.instructions.Create<ir::ExitIf>();
+        auto* exit_out =
+            mod_out_.allocators.instructions.Create<ir::ExitIf>(mod_out_.NextInstructionId());
         exit_ifs_.Push(exit_out);
         return exit_out;
     }
 
     ir::ExitLoop* CreateInstructionExitLoop(const pb::InstructionExitLoop&) {
-        auto* exit_out = mod_out_.allocators.instructions.Create<ir::ExitLoop>();
+        auto* exit_out =
+            mod_out_.allocators.instructions.Create<ir::ExitLoop>(mod_out_.NextInstructionId());
         exit_loops_.Push(exit_out);
         return exit_out;
     }
 
     ir::ExitSwitch* CreateInstructionExitSwitch(const pb::InstructionExitSwitch&) {
-        auto* exit_out = mod_out_.allocators.instructions.Create<ir::ExitSwitch>();
+        auto* exit_out =
+            mod_out_.allocators.instructions.Create<ir::ExitSwitch>(mod_out_.NextInstructionId());
         exit_switches_.Push(exit_out);
         return exit_out;
     }
 
     ir::Discard* CreateInstructionDiscard(const pb::InstructionDiscard&) {
-        return mod_out_.allocators.instructions.Create<ir::Discard>();
+        return mod_out_.allocators.instructions.Create<ir::Discard>(mod_out_.NextInstructionId());
     }
 
     ir::If* CreateInstructionIf(const pb::InstructionIf& if_in) {
-        auto* if_out = mod_out_.allocators.instructions.Create<ir::If>();
+        auto* if_out =
+            mod_out_.allocators.instructions.Create<ir::If>(mod_out_.NextInstructionId());
         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>();
+        return mod_out_.allocators.instructions.Create<ir::Let>(mod_out_.NextInstructionId());
     }
 
     ir::Load* CreateInstructionLoad(const pb::InstructionLoad&) {
-        return mod_out_.allocators.instructions.Create<ir::Load>();
+        return mod_out_.allocators.instructions.Create<ir::Load>(mod_out_.NextInstructionId());
     }
 
     ir::LoadVectorElement* CreateInstructionLoadVectorElement(
         const pb::InstructionLoadVectorElement&) {
-        return mod_out_.allocators.instructions.Create<ir::LoadVectorElement>();
+        return mod_out_.allocators.instructions.Create<ir::LoadVectorElement>(
+            mod_out_.NextInstructionId());
     }
 
     ir::Loop* CreateInstructionLoop(const pb::InstructionLoop& loop_in) {
-        auto* loop_out = mod_out_.allocators.instructions.Create<ir::Loop>();
+        auto* loop_out =
+            mod_out_.allocators.instructions.Create<ir::Loop>(mod_out_.NextInstructionId());
         if (loop_in.has_initializer()) {
             loop_out->SetInitializer(Block(loop_in.initializer()));
         } else {
@@ -564,26 +574,29 @@
     }
 
     ir::NextIteration* CreateInstructionNextIteration(const pb::InstructionNextIteration&) {
-        auto* next_it_out = mod_out_.allocators.instructions.Create<ir::NextIteration>();
+        auto* next_it_out = mod_out_.allocators.instructions.Create<ir::NextIteration>(
+            mod_out_.NextInstructionId());
         next_iterations_.Push(next_it_out);
         return next_it_out;
     }
 
     ir::Return* CreateInstructionReturn(const pb::InstructionReturn&) {
-        return mod_out_.allocators.instructions.Create<ir::Return>();
+        return mod_out_.allocators.instructions.Create<ir::Return>(mod_out_.NextInstructionId());
     }
 
     ir::Store* CreateInstructionStore(const pb::InstructionStore&) {
-        return mod_out_.allocators.instructions.Create<ir::Store>();
+        return mod_out_.allocators.instructions.Create<ir::Store>(mod_out_.NextInstructionId());
     }
 
     ir::StoreVectorElement* CreateInstructionStoreVectorElement(
         const pb::InstructionStoreVectorElement&) {
-        return mod_out_.allocators.instructions.Create<ir::StoreVectorElement>();
+        return mod_out_.allocators.instructions.Create<ir::StoreVectorElement>(
+            mod_out_.NextInstructionId());
     }
 
     ir::Swizzle* CreateInstructionSwizzle(const pb::InstructionSwizzle& swizzle_in) {
-        auto* swizzle_out = mod_out_.allocators.instructions.Create<ir::Swizzle>();
+        auto* swizzle_out =
+            mod_out_.allocators.instructions.Create<ir::Swizzle>(mod_out_.NextInstructionId());
         Vector<uint32_t, 4> indices;
         for (auto idx : swizzle_in.indices()) {
             indices.Push(idx);
@@ -593,7 +606,8 @@
     }
 
     ir::Switch* CreateInstructionSwitch(const pb::InstructionSwitch& switch_in) {
-        auto* switch_out = mod_out_.allocators.instructions.Create<ir::Switch>();
+        auto* switch_out =
+            mod_out_.allocators.instructions.Create<ir::Switch>(mod_out_.NextInstructionId());
         for (auto& case_in : switch_in.cases()) {
             ir::Switch::Case case_out{};
             case_out.block = Block(case_in.block());
@@ -613,17 +627,19 @@
     }
 
     ir::CoreUnary* CreateInstructionUnary(const pb::InstructionUnary& unary_in) {
-        auto* unary_out = mod_out_.allocators.instructions.Create<ir::CoreUnary>();
+        auto* unary_out =
+            mod_out_.allocators.instructions.Create<ir::CoreUnary>(mod_out_.NextInstructionId());
         unary_out->SetOp(UnaryOp(unary_in.op()));
         return unary_out;
     }
 
     ir::UserCall* CreateInstructionUserCall(const pb::InstructionUserCall&) {
-        return mod_out_.allocators.instructions.Create<ir::UserCall>();
+        return mod_out_.allocators.instructions.Create<ir::UserCall>(mod_out_.NextInstructionId());
     }
 
     ir::Var* CreateInstructionVar(const pb::InstructionVar& var_in) {
-        auto* var_out = mod_out_.allocators.instructions.Create<ir::Var>();
+        auto* var_out =
+            mod_out_.allocators.instructions.Create<ir::Var>(mod_out_.NextInstructionId());
         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 01c06b6..0045833 100644
--- a/src/tint/lang/core/ir/bitcast.cc
+++ b/src/tint/lang/core/ir/bitcast.cc
@@ -34,9 +34,9 @@
 
 namespace tint::core::ir {
 
-Bitcast::Bitcast() = default;
+Bitcast::Bitcast(Id id) : Base(id) {}
 
-Bitcast::Bitcast(InstructionResult* result, Value* val) {
+Bitcast::Bitcast(Id id, InstructionResult* result, Value* val) : Base(id) {
     AddOperand(Bitcast::kValueOperandOffset, val);
     AddResult(result);
 }
@@ -46,7 +46,8 @@
 Bitcast* Bitcast::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Val());
-    return ctx.ir.allocators.instructions.Create<Bitcast>(new_result, val);
+    return ctx.ir.allocators.instructions.Create<Bitcast>(ctx.ir.NextInstructionId(), new_result,
+                                                          val);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/bitcast.h b/src/tint/lang/core/ir/bitcast.h
index 37bc7c0..fa28388 100644
--- a/src/tint/lang/core/ir/bitcast.h
+++ b/src/tint/lang/core/ir/bitcast.h
@@ -42,12 +42,15 @@
     static constexpr size_t kValueOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    Bitcast();
+    /// @param id the instruction id
+    explicit Bitcast(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param val the value being bitcast
-    Bitcast(InstructionResult* result, Value* val);
+    Bitcast(Id id, InstructionResult* result, Value* val);
+
     ~Bitcast() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/break_if.cc b/src/tint/lang/core/ir/break_if.cc
index 72ad55b..7a1b96f 100644
--- a/src/tint/lang/core/ir/break_if.cc
+++ b/src/tint/lang/core/ir/break_if.cc
@@ -40,13 +40,14 @@
 
 namespace tint::core::ir {
 
-BreakIf::BreakIf() = default;
+BreakIf::BreakIf(Id id) : Base(id) {}
 
-BreakIf::BreakIf(Value* condition,
+BreakIf::BreakIf(Id id,
+                 Value* condition,
                  ir::Loop* loop,
                  VectorRef<Value*> next_iter_values /* = tint::Empty */,
                  VectorRef<Value*> exit_values /* = tint::Empty */)
-    : loop_(loop), num_next_iter_values_(next_iter_values.Length()) {
+    : Base(id), loop_(loop), num_next_iter_values_(next_iter_values.Length()) {
     TINT_ASSERT(loop_);
 
     AddOperand(BreakIf::kConditionOperandOffset, condition);
@@ -65,7 +66,8 @@
     auto* loop = ctx.Remap(loop_);
     auto* cond = ctx.Remap(Condition());
     auto args = ctx.Remap<BreakIf::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<BreakIf>(cond, loop, args);
+    return ctx.ir.allocators.instructions.Create<BreakIf>(ctx.ir.NextInstructionId(), cond, loop,
+                                                          args);
 }
 
 void BreakIf::SetLoop(ir::Loop* loop) {
diff --git a/src/tint/lang/core/ir/break_if.h b/src/tint/lang/core/ir/break_if.h
index 47533ec..132ea20 100644
--- a/src/tint/lang/core/ir/break_if.h
+++ b/src/tint/lang/core/ir/break_if.h
@@ -52,19 +52,23 @@
     static constexpr size_t kArgsOperandOffset = 1;
 
     /// Constructor (no operands, no loop)
-    BreakIf();
+    /// @param id the instruction id
+    explicit BreakIf(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param condition the break condition
     /// @param loop the loop containing the break-if
     /// @param next_iter_values the arguments passed to the loop body MultiInBlock, if the break
     /// condition evaluates to `false`.
     /// @param exit_values the values returned by the loop, if the break condition evaluates to
     /// `true`.
-    BreakIf(Value* condition,
+    BreakIf(Id id,
+            Value* condition,
             ir::Loop* loop,
             VectorRef<Value*> next_iter_values = tint::Empty,
             VectorRef<Value*> exit_values = tint::Empty);
+
     ~BreakIf() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/builder.cc b/src/tint/lang/core/ir/builder.cc
index 16d0ed0..a1fb84a 100644
--- a/src/tint/lang/core/ir/builder.cc
+++ b/src/tint/lang/core/ir/builder.cc
@@ -70,8 +70,8 @@
 }
 
 ir::Loop* Builder::Loop() {
-    return Append(
-        ir.allocators.instructions.Create<ir::Loop>(Block(), MultiInBlock(), MultiInBlock()));
+    return Append(ir.allocators.instructions.Create<ir::Loop>(ir.NextInstructionId(), Block(),
+                                                              MultiInBlock(), MultiInBlock()));
 }
 
 Block* Builder::Case(ir::Switch* s, VectorRef<ir::Constant*> values) {
@@ -96,11 +96,12 @@
 }
 
 ir::Discard* Builder::Discard() {
-    return Append(ir.allocators.instructions.Create<ir::Discard>());
+    return Append(ir.allocators.instructions.Create<ir::Discard>(ir.NextInstructionId()));
 }
 
 ir::Var* Builder::Var(const core::type::MemoryView* type) {
-    return Append(ir.allocators.instructions.Create<ir::Var>(InstructionResult(type)));
+    return Append(ir.allocators.instructions.Create<ir::Var>(ir.NextInstructionId(),
+                                                             InstructionResult(type)));
 }
 
 ir::Var* Builder::Var(std::string_view name, const core::type::MemoryView* type) {
@@ -130,11 +131,12 @@
 }
 
 ir::TerminateInvocation* Builder::TerminateInvocation() {
-    return Append(ir.allocators.instructions.Create<ir::TerminateInvocation>());
+    return Append(
+        ir.allocators.instructions.Create<ir::TerminateInvocation>(ir.NextInstructionId()));
 }
 
 ir::Unreachable* Builder::Unreachable() {
-    return Append(ir.allocators.instructions.Create<ir::Unreachable>());
+    return Append(ir.allocators.instructions.Create<ir::Unreachable>(ir.NextInstructionId()));
 }
 
 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 8d080d6..0787547 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -296,7 +296,8 @@
     template <typename T>
     ir::If* If(T&& condition) {
         auto* cond_val = Value(std::forward<T>(condition));
-        return Append(ir.allocators.instructions.Create<ir::If>(cond_val, Block(), Block()));
+        return Append(ir.allocators.instructions.Create<ir::If>(ir.NextInstructionId(), cond_val,
+                                                                Block(), Block()));
     }
 
     /// Creates a loop instruction
@@ -309,7 +310,8 @@
     template <typename T>
     ir::Switch* Switch(T&& condition) {
         auto* cond_val = Value(std::forward<T>(condition));
-        return Append(ir.allocators.instructions.Create<ir::Switch>(cond_val));
+        return Append(
+            ir.allocators.instructions.Create<ir::Switch>(ir.NextInstructionId(), cond_val));
     }
 
     /// Creates a default case for the switch @p s
@@ -555,8 +557,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>(InstructionResult(type), op,
-                                                                        lhs_val, rhs_val));
+        return Append(ir.allocators.instructions.Create<ir::CoreBinary>(
+            ir.NextInstructionId(), InstructionResult(type), op, lhs_val, rhs_val));
     }
 
     /// Creates an op for `lhs kind rhs`
@@ -571,8 +573,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<KLASS>(InstructionResult(type), op, lhs_val,
-                                                               rhs_val));
+        return Append(ir.allocators.instructions.Create<KLASS>(
+            ir.NextInstructionId(), InstructionResult(type), op, lhs_val, rhs_val));
     }
 
     /// Creates an And operation
@@ -921,8 +923,8 @@
     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>(InstructionResult(type), op, value));
+        return Append(ir.allocators.instructions.Create<ir::CoreUnary>(
+            ir.NextInstructionId(), InstructionResult(type), op, value));
     }
 
     /// Creates an op for `op val`
@@ -1000,8 +1002,8 @@
     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>(InstructionResult(type), value));
+        return Append(ir.allocators.instructions.Create<ir::Bitcast>(
+            ir.NextInstructionId(), InstructionResult(type), value));
     }
 
     /// Creates a bitcast instruction
@@ -1029,7 +1031,7 @@
                                  ir::Function* func,
                                  ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::UserCall>(
-            result, func, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), result, func, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a user function call instruction
@@ -1072,7 +1074,7 @@
                                         core::BuiltinFn func,
                                         ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::CoreBuiltinCall>(
-            result, func, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), result, func, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a core builtin call instruction
@@ -1105,7 +1107,7 @@
     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>(
-            result, func, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), result, func, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a builtin call instruction
@@ -1130,7 +1132,8 @@
     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>(
-            result, func, Value(std::forward<OBJ>(obj)), Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), result, func, Value(std::forward<OBJ>(obj)),
+            Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a member builtin call instruction.
@@ -1153,8 +1156,8 @@
     /// @returns the instruction
     template <typename VAL>
     ir::Convert* ConvertWithResult(ir::InstructionResult* result, VAL&& val) {
-        return Append(
-            ir.allocators.instructions.Create<ir::Convert>(result, Value(std::forward<VAL>(val))));
+        return Append(ir.allocators.instructions.Create<ir::Convert>(
+            ir.NextInstructionId(), result, Value(std::forward<VAL>(val))));
     }
 
     /// Creates a value conversion instruction to the template type T
@@ -1182,7 +1185,7 @@
     template <typename... ARGS>
     ir::Construct* ConstructWithResult(ir::InstructionResult* result, ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::Construct>(
-            result, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), result, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a value constructor instruction to the template type T
@@ -1210,7 +1213,8 @@
     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>(result, value));
+        return Append(
+            ir.allocators.instructions.Create<ir::Load>(ir.NextInstructionId(), result, value));
     }
 
     /// Creates a load instruction
@@ -1231,7 +1235,8 @@
         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>(to_val, from_val));
+        return Append(
+            ir.allocators.instructions.Create<ir::Store>(ir.NextInstructionId(), to_val, from_val));
     }
 
     /// Creates a store vector element instruction
@@ -1245,8 +1250,8 @@
         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>(to_val, index_val,
-                                                                                value_val));
+        return Append(ir.allocators.instructions.Create<ir::StoreVectorElement>(
+            ir.NextInstructionId(), to_val, index_val, value_val));
     }
 
     /// Creates a load vector element instruction with an existing instruction result
@@ -1261,8 +1266,8 @@
         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>(result, from_val, index_val));
+        return Append(ir.allocators.instructions.Create<ir::LoadVectorElement>(
+            ir.NextInstructionId(), result, from_val, index_val));
     }
 
     /// Creates a load vector element instruction
@@ -1362,8 +1367,8 @@
             TINT_ASSERT(val);
             return nullptr;
         }
-        auto* let =
-            Append(ir.allocators.instructions.Create<ir::Let>(InstructionResult(val->Type()), val));
+        auto* let = Append(ir.allocators.instructions.Create<ir::Let>(
+            ir.NextInstructionId(), InstructionResult(val->Type()), val));
         ir.SetName(let->Result(0), name);
         return let;
     }
@@ -1372,7 +1377,8 @@
     /// @param type the let type
     /// @returns the instruction
     ir::Let* Let(const type::Type* type) {
-        auto* let = ir.allocators.instructions.Create<ir::Let>(InstructionResult(type), nullptr);
+        auto* let = ir.allocators.instructions.Create<ir::Let>(ir.NextInstructionId(),
+                                                               InstructionResult(type), nullptr);
         Append(let);
         return let;
     }
@@ -1381,7 +1387,7 @@
     /// @param func the function being returned
     /// @returns the instruction
     ir::Return* Return(ir::Function* func) {
-        return Append(ir.allocators.instructions.Create<ir::Return>(func));
+        return Append(ir.allocators.instructions.Create<ir::Return>(ir.NextInstructionId(), func));
     }
 
     /// Creates a return instruction
@@ -1392,11 +1398,13 @@
     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>(func));
+                return Append(
+                    ir.allocators.instructions.Create<ir::Return>(ir.NextInstructionId(), func));
             }
         }
         auto* val = Value(std::forward<ARG>(value));
-        return Append(ir.allocators.instructions.Create<ir::Return>(func, val));
+        return Append(
+            ir.allocators.instructions.Create<ir::Return>(ir.NextInstructionId(), func, val));
     }
 
     /// Creates a loop next iteration instruction
@@ -1406,7 +1414,7 @@
     template <typename... ARGS>
     ir::NextIteration* NextIteration(ir::Loop* loop, ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::NextIteration>(
-            loop, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), loop, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates a loop break-if instruction
@@ -1417,7 +1425,8 @@
     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>(cond_val, loop));
+        return Append(
+            ir.allocators.instructions.Create<ir::BreakIf>(ir.NextInstructionId(), cond_val, loop));
     }
 
     /// Creates a loop break-if instruction
@@ -1436,7 +1445,8 @@
         CheckForNonDeterministicEvaluation<CONDITION, NEXT_ITER_VALUES, EXIT_VALUES>();
         auto* cond_val = Value(std::forward<CONDITION>(condition));
         return Append(ir.allocators.instructions.Create<ir::BreakIf>(
-            cond_val, loop, Values(std::forward<NEXT_ITER_VALUES>(next_iter_values)),
+            ir.NextInstructionId(), cond_val, loop,
+            Values(std::forward<NEXT_ITER_VALUES>(next_iter_values)),
             Values(std::forward<EXIT_VALUES>(exit_values))));
     }
 
@@ -1447,7 +1457,7 @@
     template <typename... ARGS>
     ir::Continue* Continue(ir::Loop* loop, ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::Continue>(
-            loop, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), loop, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit switch instruction
@@ -1457,7 +1467,7 @@
     template <typename... ARGS>
     ir::ExitSwitch* ExitSwitch(ir::Switch* sw, ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::ExitSwitch>(
-            sw, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), sw, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit loop instruction
@@ -1467,7 +1477,7 @@
     template <typename... ARGS>
     ir::ExitLoop* ExitLoop(ir::Loop* loop, ARGS&&... args) {
         return Append(ir.allocators.instructions.Create<ir::ExitLoop>(
-            loop, Values(std::forward<ARGS>(args)...)));
+            ir.NextInstructionId(), loop, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit if instruction
@@ -1476,8 +1486,8 @@
     /// @returns the instruction
     template <typename... ARGS>
     ir::ExitIf* ExitIf(ir::If* i, ARGS&&... args) {
-        return Append(
-            ir.allocators.instructions.Create<ir::ExitIf>(i, Values(std::forward<ARGS>(args)...)));
+        return Append(ir.allocators.instructions.Create<ir::ExitIf>(
+            ir.NextInstructionId(), i, Values(std::forward<ARGS>(args)...)));
     }
 
     /// Creates an exit instruction for the given control instruction
@@ -1563,7 +1573,7 @@
         CheckForNonDeterministicEvaluation<OBJ, ARGS...>();
         auto* obj_val = Value(std::forward<OBJ>(object));
         return Append(ir.allocators.instructions.Create<ir::Access>(
-            result, obj_val, Values(std::forward<ARGS>(indices)...)));
+            ir.NextInstructionId(), result, obj_val, Values(std::forward<ARGS>(indices)...)));
     }
 
     /// Creates a new `Access`
@@ -1596,8 +1606,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>(InstructionResult(type),
-                                                                     obj_val, std::move(indices)));
+        return Append(ir.allocators.instructions.Create<ir::Swizzle>(
+            ir.NextInstructionId(), InstructionResult(type), obj_val, std::move(indices)));
     }
 
     /// Creates a new `Swizzle`
@@ -1622,7 +1632,8 @@
                          std::initializer_list<uint32_t> indices) {
         auto* obj_val = Value(std::forward<OBJ>(object));
         return Append(ir.allocators.instructions.Create<ir::Swizzle>(
-            InstructionResult(type), obj_val, Vector<uint32_t, 4>(indices)));
+            ir.NextInstructionId(), InstructionResult(type), obj_val,
+            Vector<uint32_t, 4>(indices)));
     }
 
     /// Name names the value or instruction with @p name
diff --git a/src/tint/lang/core/ir/builtin_call.cc b/src/tint/lang/core/ir/builtin_call.cc
index 5c38785..92e0d17 100644
--- a/src/tint/lang/core/ir/builtin_call.cc
+++ b/src/tint/lang/core/ir/builtin_call.cc
@@ -33,11 +33,11 @@
 
 namespace tint::core::ir {
 
-BuiltinCall::BuiltinCall() {
+BuiltinCall::BuiltinCall(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-BuiltinCall::BuiltinCall(InstructionResult* result, VectorRef<Value*> arguments) {
+BuiltinCall::BuiltinCall(Id id, InstructionResult* result, VectorRef<Value*> arguments) : Base(id) {
     flags_.Add(Flag::kSequenced);
     AddOperands(BuiltinCall::kArgsOperandOffset, std::move(arguments));
     AddResult(result);
diff --git a/src/tint/lang/core/ir/builtin_call.h b/src/tint/lang/core/ir/builtin_call.h
index 1b752ec..648c2ac 100644
--- a/src/tint/lang/core/ir/builtin_call.h
+++ b/src/tint/lang/core/ir/builtin_call.h
@@ -41,12 +41,15 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    BuiltinCall();
+    /// @param id the instruction id
+    explicit BuiltinCall(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param args the conversion arguments
-    explicit BuiltinCall(InstructionResult* result, VectorRef<Value*> args = tint::Empty);
+    BuiltinCall(Id id, InstructionResult* result, VectorRef<Value*> args = tint::Empty);
+
     ~BuiltinCall() override;
 
     /// @returns the identifier for the function
diff --git a/src/tint/lang/core/ir/call.cc b/src/tint/lang/core/ir/call.cc
index 19c186a..a04b408 100644
--- a/src/tint/lang/core/ir/call.cc
+++ b/src/tint/lang/core/ir/call.cc
@@ -33,7 +33,7 @@
 
 namespace tint::core::ir {
 
-Call::Call() = default;
+Call::Call(Id id) : Base(id) {}
 
 Call::~Call() = default;
 
diff --git a/src/tint/lang/core/ir/call.h b/src/tint/lang/core/ir/call.h
index 0b88fc2..932ee53 100644
--- a/src/tint/lang/core/ir/call.h
+++ b/src/tint/lang/core/ir/call.h
@@ -59,7 +59,7 @@
 
   protected:
     /// Constructor
-    Call();
+    explicit Call(Id id);
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/construct.cc b/src/tint/lang/core/ir/construct.cc
index d92ea6f..697458c 100644
--- a/src/tint/lang/core/ir/construct.cc
+++ b/src/tint/lang/core/ir/construct.cc
@@ -36,9 +36,9 @@
 
 namespace tint::core::ir {
 
-Construct::Construct() = default;
+Construct::Construct(Id id) : Base(id) {}
 
-Construct::Construct(InstructionResult* result, VectorRef<Value*> arguments) {
+Construct::Construct(Id id, InstructionResult* result, VectorRef<Value*> arguments) : Base(id) {
     AddOperands(Construct::kArgsOperandOffset, std::move(arguments));
     AddResult(result);
 }
@@ -48,7 +48,8 @@
 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>(new_result, args);
+    return ctx.ir.allocators.instructions.Create<Construct>(ctx.ir.NextInstructionId(), new_result,
+                                                            args);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/construct.h b/src/tint/lang/core/ir/construct.h
index d0bbeb8..dd429d2 100644
--- a/src/tint/lang/core/ir/construct.h
+++ b/src/tint/lang/core/ir/construct.h
@@ -42,12 +42,15 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no result, no operands)
-    Construct();
+    /// @param id the instruction id
+    explicit Construct(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param args the constructor arguments
-    explicit Construct(InstructionResult* result, VectorRef<Value*> args = tint::Empty);
+    Construct(Id id, InstructionResult* result, VectorRef<Value*> args = tint::Empty);
+
     ~Construct() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/continue.cc b/src/tint/lang/core/ir/continue.cc
index 3b58584..9e0bb1f 100644
--- a/src/tint/lang/core/ir/continue.cc
+++ b/src/tint/lang/core/ir/continue.cc
@@ -40,9 +40,9 @@
 
 namespace tint::core::ir {
 
-Continue::Continue() = default;
+Continue::Continue(Id id) : Base(id) {}
 
-Continue::Continue(ir::Loop* loop, VectorRef<Value*> args) : loop_(loop) {
+Continue::Continue(Id id, ir::Loop* loop, VectorRef<Value*> args) : Base(id), loop_(loop) {
     TINT_ASSERT(loop_);
 
     AddOperands(Continue::kArgsOperandOffset, std::move(args));
@@ -58,7 +58,7 @@
     auto* loop = ctx.Remap(Loop());
     auto args = ctx.Remap<Continue::kDefaultNumOperands>(Args());
 
-    return ctx.ir.allocators.instructions.Create<Continue>(loop, args);
+    return ctx.ir.allocators.instructions.Create<Continue>(ctx.ir.NextInstructionId(), loop, args);
 }
 
 void Continue::SetLoop(ir::Loop* loop) {
diff --git a/src/tint/lang/core/ir/continue.h b/src/tint/lang/core/ir/continue.h
index 803ae8f..dab2080 100644
--- a/src/tint/lang/core/ir/continue.h
+++ b/src/tint/lang/core/ir/continue.h
@@ -48,12 +48,14 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no operands, no loop)
-    Continue();
+    /// @param id the instruction id
+    explicit Continue(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param loop the loop owning the continue block
     /// @param args the arguments for the MultiInBlock
-    explicit Continue(ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
+    Continue(Id id, ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
     ~Continue() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/control_instruction.cc b/src/tint/lang/core/ir/control_instruction.cc
index 7cdda8d..91b7c56 100644
--- a/src/tint/lang/core/ir/control_instruction.cc
+++ b/src/tint/lang/core/ir/control_instruction.cc
@@ -33,7 +33,7 @@
 
 namespace tint::core::ir {
 
-ControlInstruction::ControlInstruction() {
+ControlInstruction::ControlInstruction(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
diff --git a/src/tint/lang/core/ir/control_instruction.h b/src/tint/lang/core/ir/control_instruction.h
index 60f29e2..1d5ed62 100644
--- a/src/tint/lang/core/ir/control_instruction.h
+++ b/src/tint/lang/core/ir/control_instruction.h
@@ -45,7 +45,8 @@
 class ControlInstruction : public Castable<ControlInstruction, OperandInstruction<1, 1>> {
   public:
     /// Constructor
-    ControlInstruction();
+    /// @param id the instruction id
+    explicit ControlInstruction(Id id);
 
     /// Destructor
     ~ControlInstruction() override;
diff --git a/src/tint/lang/core/ir/convert.cc b/src/tint/lang/core/ir/convert.cc
index 985c47d..767f810 100644
--- a/src/tint/lang/core/ir/convert.cc
+++ b/src/tint/lang/core/ir/convert.cc
@@ -36,9 +36,9 @@
 
 namespace tint::core::ir {
 
-Convert::Convert() = default;
+Convert::Convert(Id id) : Base(id) {}
 
-Convert::Convert(InstructionResult* result, Value* value) {
+Convert::Convert(Id id, InstructionResult* result, Value* value) : Base(id) {
     AddOperand(Convert::kValueOperandOffset, value);
     AddResult(result);
 }
@@ -48,7 +48,8 @@
 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>(new_result, val);
+    return ctx.ir.allocators.instructions.Create<Convert>(ctx.ir.NextInstructionId(), new_result,
+                                                          val);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/convert.h b/src/tint/lang/core/ir/convert.h
index f0aed80..56173d6 100644
--- a/src/tint/lang/core/ir/convert.h
+++ b/src/tint/lang/core/ir/convert.h
@@ -43,12 +43,15 @@
     static constexpr size_t kValueOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    Convert();
+    /// @param id the instruction id
+    explicit Convert(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param value the value to convert
-    Convert(InstructionResult* result, Value* value);
+    Convert(Id id, InstructionResult* result, Value* value);
+
     ~Convert() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/core_binary.cc b/src/tint/lang/core/ir/core_binary.cc
index ef3cac4..82517f6 100644
--- a/src/tint/lang/core/ir/core_binary.cc
+++ b/src/tint/lang/core/ir/core_binary.cc
@@ -35,10 +35,10 @@
 
 namespace tint::core::ir {
 
-CoreBinary::CoreBinary() = default;
+CoreBinary::CoreBinary(Id id) : Base(id) {}
 
-CoreBinary::CoreBinary(InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs)
-    : Base(result, op, lhs, rhs) {}
+CoreBinary::CoreBinary(Id id, InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs)
+    : Base(id, result, op, lhs, rhs) {}
 
 CoreBinary::~CoreBinary() = default;
 
@@ -46,7 +46,8 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* lhs = ctx.Remap(LHS());
     auto* rhs = ctx.Remap(RHS());
-    return ctx.ir.allocators.instructions.Create<CoreBinary>(new_result, Op(), lhs, rhs);
+    return ctx.ir.allocators.instructions.Create<CoreBinary>(ctx.ir.NextInstructionId(), new_result,
+                                                             Op(), lhs, rhs);
 }
 
 const core::intrinsic::TableData& CoreBinary::TableData() const {
diff --git a/src/tint/lang/core/ir/core_binary.h b/src/tint/lang/core/ir/core_binary.h
index 7329ce6..3cfbbca 100644
--- a/src/tint/lang/core/ir/core_binary.h
+++ b/src/tint/lang/core/ir/core_binary.h
@@ -39,14 +39,17 @@
     static constexpr size_t kValueOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    CoreBinary();
+    /// @param id the instruction id
+    explicit CoreBinary(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param op the Binary operator
     /// @param lhs the lhs of the instruction
     /// @param rhs the rhs of the instruction
-    CoreBinary(InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs);
+    CoreBinary(Id id, InstructionResult* result, BinaryOp op, Value* lhs, Value* rhs);
+
     ~CoreBinary() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/core_builtin_call.cc b/src/tint/lang/core/ir/core_builtin_call.cc
index e095f1a..10ed0eb3 100644
--- a/src/tint/lang/core/ir/core_builtin_call.cc
+++ b/src/tint/lang/core/ir/core_builtin_call.cc
@@ -37,12 +37,13 @@
 
 namespace tint::core::ir {
 
-CoreBuiltinCall::CoreBuiltinCall() = default;
+CoreBuiltinCall::CoreBuiltinCall(Id id) : Base(id) {}
 
-CoreBuiltinCall::CoreBuiltinCall(InstructionResult* result,
+CoreBuiltinCall::CoreBuiltinCall(Id id,
+                                 InstructionResult* result,
                                  core::BuiltinFn func,
                                  VectorRef<Value*> arguments)
-    : Base(result, arguments), func_(func) {
+    : Base(id, result, arguments), func_(func) {
     TINT_ASSERT(func != core::BuiltinFn::kNone);
 }
 
@@ -51,7 +52,8 @@
 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>(new_result, func_, args);
+    return ctx.ir.allocators.instructions.Create<CoreBuiltinCall>(ctx.ir.NextInstructionId(),
+                                                                  new_result, func_, args);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/core_builtin_call.h b/src/tint/lang/core/ir/core_builtin_call.h
index f153c7c..4ef792e 100644
--- a/src/tint/lang/core/ir/core_builtin_call.h
+++ b/src/tint/lang/core/ir/core_builtin_call.h
@@ -42,15 +42,19 @@
 class CoreBuiltinCall final : public Castable<CoreBuiltinCall, BuiltinCall> {
   public:
     /// Constructor (no results, no operands)
-    CoreBuiltinCall();
+    /// @param id the instruction id
+    explicit CoreBuiltinCall(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the builtin function
     /// @param args the conversion arguments
-    CoreBuiltinCall(InstructionResult* result,
+    CoreBuiltinCall(Id id,
+                    InstructionResult* result,
                     core::BuiltinFn func,
                     VectorRef<Value*> args = tint::Empty);
+
     ~CoreBuiltinCall() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/core_unary.cc b/src/tint/lang/core/ir/core_unary.cc
index e01a261..239a7ff 100644
--- a/src/tint/lang/core/ir/core_unary.cc
+++ b/src/tint/lang/core/ir/core_unary.cc
@@ -35,16 +35,18 @@
 
 namespace tint::core::ir {
 
-CoreUnary::CoreUnary() = default;
+CoreUnary::CoreUnary(Id id) : Base(id) {}
 
-CoreUnary::CoreUnary(InstructionResult* result, UnaryOp op, Value* val) : Base(result, op, val) {}
+CoreUnary::CoreUnary(Id id, InstructionResult* result, UnaryOp op, Value* val)
+    : Base(id, result, op, val) {}
 
 CoreUnary::~CoreUnary() = default;
 
 CoreUnary* CoreUnary::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* val = ctx.Remap(Val());
-    return ctx.ir.allocators.instructions.Create<CoreUnary>(new_result, Op(), val);
+    return ctx.ir.allocators.instructions.Create<CoreUnary>(ctx.ir.NextInstructionId(), new_result,
+                                                            Op(), val);
 }
 
 const core::intrinsic::TableData& CoreUnary::TableData() const {
diff --git a/src/tint/lang/core/ir/core_unary.h b/src/tint/lang/core/ir/core_unary.h
index 04a02f4..a0eb9f8 100644
--- a/src/tint/lang/core/ir/core_unary.h
+++ b/src/tint/lang/core/ir/core_unary.h
@@ -39,13 +39,16 @@
     static constexpr size_t kValueOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    CoreUnary();
+    /// @param id the instruction id
+    explicit CoreUnary(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param op the unary operator
     /// @param val the input value for the instruction
-    CoreUnary(InstructionResult* result, UnaryOp op, Value* val);
+    CoreUnary(Id id, InstructionResult* result, UnaryOp op, Value* val);
+
     ~CoreUnary() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembler.cc
index d7fa6da..ed4fb2f 100644
--- a/src/tint/lang/core/ir/disassembler.cc
+++ b/src/tint/lang/core/ir/disassembler.cc
@@ -481,8 +481,10 @@
         },
         [&](const StoreVectorElement* s) {
             EmitInstructionName(s);
-            out_ << " ";
-            EmitOperandList(s);
+            if (s->Operands().Length() > 0) {
+                out_ << " ";
+                EmitOperandList(s);
+            }
         },
         [&](const UserCall* uc) {
             EmitValueWithType(uc);
@@ -869,8 +871,12 @@
 void Disassembler::EmitBinary(const Binary* b) {
     SourceMarker sm(this);
     EmitValueWithType(b);
-    out_ << " = " << NameOf(b->Op()) << " ";
-    EmitOperandList(b);
+    out_ << " = " << NameOf(b->Op());
+
+    if (b->Operands().Length() > 0) {
+        out_ << " ";
+        EmitOperandList(b);
+    }
 
     sm.Store(b);
 }
@@ -878,8 +884,12 @@
 void Disassembler::EmitUnary(const Unary* u) {
     SourceMarker sm(this);
     EmitValueWithType(u);
-    out_ << " = " << NameOf(u->Op()) << " ";
-    EmitOperandList(u);
+    out_ << " = " << NameOf(u->Op());
+
+    if (u->Operands().Length() > 0) {
+        out_ << " ";
+        EmitOperandList(u);
+    }
 
     sm.Store(u);
 }
diff --git a/src/tint/lang/core/ir/discard.cc b/src/tint/lang/core/ir/discard.cc
index 53cc77f..1625018 100644
--- a/src/tint/lang/core/ir/discard.cc
+++ b/src/tint/lang/core/ir/discard.cc
@@ -34,14 +34,14 @@
 
 namespace tint::core::ir {
 
-Discard::Discard() {
+Discard::Discard(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
 Discard::~Discard() = default;
 
 Discard* Discard::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.instructions.Create<Discard>();
+    return ctx.ir.allocators.instructions.Create<Discard>(ctx.ir.NextInstructionId());
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/discard.h b/src/tint/lang/core/ir/discard.h
index 62e20f7..7e0c824 100644
--- a/src/tint/lang/core/ir/discard.h
+++ b/src/tint/lang/core/ir/discard.h
@@ -39,7 +39,9 @@
 class Discard final : public Castable<Discard, Call> {
   public:
     /// Constructor
-    Discard();
+    /// @param id the instruction id
+    explicit Discard(Id id);
+
     ~Discard() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/exit.cc b/src/tint/lang/core/ir/exit.cc
index 773daa7..c8ace87 100644
--- a/src/tint/lang/core/ir/exit.cc
+++ b/src/tint/lang/core/ir/exit.cc
@@ -33,6 +33,8 @@
 
 namespace tint::core::ir {
 
+Exit::Exit(Instruction::Id id) : Base(id) {}
+
 Exit::~Exit() = default;
 
 void Exit::Destroy() {
diff --git a/src/tint/lang/core/ir/exit.h b/src/tint/lang/core/ir/exit.h
index f75da6f..d220a8e 100644
--- a/src/tint/lang/core/ir/exit.h
+++ b/src/tint/lang/core/ir/exit.h
@@ -41,6 +41,10 @@
 /// The base class for all exit terminators.
 class Exit : public Castable<Exit, Terminator> {
   public:
+    /// Constructor
+    /// @param id the instruction id
+    explicit Exit(Instruction::Id id);
+
     ~Exit() override;
 
     /// @copydoc Value::Destroy
diff --git a/src/tint/lang/core/ir/exit_if.cc b/src/tint/lang/core/ir/exit_if.cc
index b848a86..2fc5e5a 100644
--- a/src/tint/lang/core/ir/exit_if.cc
+++ b/src/tint/lang/core/ir/exit_if.cc
@@ -38,9 +38,9 @@
 
 namespace tint::core::ir {
 
-ExitIf::ExitIf() = default;
+ExitIf::ExitIf(Id id) : Base(id) {}
 
-ExitIf::ExitIf(ir::If* i, VectorRef<Value*> args) {
+ExitIf::ExitIf(Id id, ir::If* i, VectorRef<Value*> args) : Base(id) {
     SetIf(i);
     AddOperands(ExitIf::kArgsOperandOffset, std::move(args));
 }
@@ -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>(if_, args);
+    return ctx.ir.allocators.instructions.Create<ExitIf>(ctx.ir.NextInstructionId(), if_, args);
 }
 
 void ExitIf::SetIf(ir::If* i) {
diff --git a/src/tint/lang/core/ir/exit_if.h b/src/tint/lang/core/ir/exit_if.h
index b561466..1b21176 100644
--- a/src/tint/lang/core/ir/exit_if.h
+++ b/src/tint/lang/core/ir/exit_if.h
@@ -47,12 +47,15 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no operands, no if)
-    ExitIf();
+    /// @param id the instruction id
+    explicit ExitIf(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param i the if being exited
     /// @param args the target MultiInBlock arguments
-    explicit ExitIf(ir::If* i, VectorRef<Value*> args = tint::Empty);
+    ExitIf(Id id, ir::If* i, VectorRef<Value*> args = tint::Empty);
+
     ~ExitIf() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/exit_loop.cc b/src/tint/lang/core/ir/exit_loop.cc
index 2222659..ddbe6e1 100644
--- a/src/tint/lang/core/ir/exit_loop.cc
+++ b/src/tint/lang/core/ir/exit_loop.cc
@@ -39,9 +39,9 @@
 
 namespace tint::core::ir {
 
-ExitLoop::ExitLoop() = default;
+ExitLoop::ExitLoop(Id id) : Base(id) {}
 
-ExitLoop::ExitLoop(ir::Loop* loop, VectorRef<Value*> args /* = tint::Empty */) {
+ExitLoop::ExitLoop(Id id, ir::Loop* loop, VectorRef<Value*> args /* = tint::Empty */) : Base(id) {
     SetLoop(loop);
     AddOperands(ExitLoop::kArgsOperandOffset, std::move(args));
 }
@@ -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>(loop, args);
+    return ctx.ir.allocators.instructions.Create<ExitLoop>(ctx.ir.NextInstructionId(), loop, args);
 }
 
 void ExitLoop::SetLoop(ir::Loop* l) {
diff --git a/src/tint/lang/core/ir/exit_loop.h b/src/tint/lang/core/ir/exit_loop.h
index d6cde81..eb0bf1c 100644
--- a/src/tint/lang/core/ir/exit_loop.h
+++ b/src/tint/lang/core/ir/exit_loop.h
@@ -47,12 +47,15 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no operands, no loop)
-    ExitLoop();
+    /// @param id the instruction id
+    explicit ExitLoop(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param loop the loop being exited
     /// @param args the target MultiInBlock arguments
-    explicit ExitLoop(ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
+    ExitLoop(Id id, ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
+
     ~ExitLoop() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/exit_switch.cc b/src/tint/lang/core/ir/exit_switch.cc
index 07363e7..ade9843 100644
--- a/src/tint/lang/core/ir/exit_switch.cc
+++ b/src/tint/lang/core/ir/exit_switch.cc
@@ -38,9 +38,10 @@
 
 namespace tint::core::ir {
 
-ExitSwitch::ExitSwitch() = default;
+ExitSwitch::ExitSwitch(Id id) : Base(id) {}
 
-ExitSwitch::ExitSwitch(ir::Switch* sw, VectorRef<Value*> args /* = tint::Empty */) {
+ExitSwitch::ExitSwitch(Id id, ir::Switch* sw, VectorRef<Value*> args /* = tint::Empty */)
+    : Base(id) {
     SetSwitch(sw);
     AddOperands(ExitSwitch::kArgsOperandOffset, std::move(args));
 }
@@ -50,7 +51,8 @@
 ExitSwitch* ExitSwitch::Clone(CloneContext& ctx) {
     auto* switch_ = ctx.Remap(Switch());
     auto args = ctx.Remap<ExitSwitch::kDefaultNumOperands>(Args());
-    return ctx.ir.allocators.instructions.Create<ExitSwitch>(switch_, args);
+    return ctx.ir.allocators.instructions.Create<ExitSwitch>(ctx.ir.NextInstructionId(), switch_,
+                                                             args);
 }
 
 void ExitSwitch::SetSwitch(ir::Switch* s) {
diff --git a/src/tint/lang/core/ir/exit_switch.h b/src/tint/lang/core/ir/exit_switch.h
index d7d3032..93d5ce2 100644
--- a/src/tint/lang/core/ir/exit_switch.h
+++ b/src/tint/lang/core/ir/exit_switch.h
@@ -47,12 +47,15 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no operands, no switch)
-    ExitSwitch();
+    /// @param id the instruction id
+    explicit ExitSwitch(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param sw the switch being exited
     /// @param args the target MultiInBlock arguments
-    explicit ExitSwitch(ir::Switch* sw, VectorRef<Value*> args = tint::Empty);
+    ExitSwitch(Id id, ir::Switch* sw, VectorRef<Value*> args = tint::Empty);
+
     ~ExitSwitch() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/if.cc b/src/tint/lang/core/ir/if.cc
index c7a2aad..e4ebd0c 100644
--- a/src/tint/lang/core/ir/if.cc
+++ b/src/tint/lang/core/ir/if.cc
@@ -36,9 +36,9 @@
 
 namespace tint::core::ir {
 
-If::If() = default;
+If::If(Id id) : Base(id) {}
 
-If::If(Value* cond, ir::Block* t, ir::Block* f) : true_(t), false_(f) {
+If::If(Id id, Value* cond, ir::Block* t, ir::Block* f) : Base(id), true_(t), false_(f) {
     TINT_ASSERT(true_);
     TINT_ASSERT(false_);
 
@@ -77,7 +77,8 @@
     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>(cond, new_true, new_false);
+    auto* new_if = ctx.ir.allocators.instructions.Create<If>(ctx.ir.NextInstructionId(), cond,
+                                                             new_true, new_false);
     ctx.Replace(this, new_if);
 
     true_->CloneInto(ctx, new_true);
diff --git a/src/tint/lang/core/ir/if.h b/src/tint/lang/core/ir/if.h
index 328168b..128100e 100644
--- a/src/tint/lang/core/ir/if.h
+++ b/src/tint/lang/core/ir/if.h
@@ -62,13 +62,15 @@
     static constexpr size_t kConditionOperandOffset = 0;
 
     /// Constructor (no results, no operands, no blocks)
-    If();
+    /// @param id the instruction id
+    explicit If(Id id);
 
     /// Constructor
     /// @param cond the if condition
     /// @param t the true block
     /// @param f the false block
-    If(Value* cond, ir::Block* t, ir::Block* f);
+    If(Id id, Value* cond, ir::Block* t, ir::Block* f);
+
     ~If() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/if_test.cc b/src/tint/lang/core/ir/if_test.cc
index 9697fb0..30f193f 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_(b.Constant(false), nullptr, b.Block());
+            If if_(mod.NextInstructionId(), b.Constant(false), nullptr, b.Block());
         },
         "internal compiler error");
 }
@@ -70,7 +70,7 @@
         {
             Module mod;
             Builder b{mod};
-            If if_(b.Constant(false), b.Block(), nullptr);
+            If if_(mod.NextInstructionId(), b.Constant(false), b.Block(), nullptr);
         },
         "internal compiler error");
 }
diff --git a/src/tint/lang/core/ir/instruction.cc b/src/tint/lang/core/ir/instruction.cc
index 3f99d68..1468fa5 100644
--- a/src/tint/lang/core/ir/instruction.cc
+++ b/src/tint/lang/core/ir/instruction.cc
@@ -34,7 +34,7 @@
 
 namespace tint::core::ir {
 
-Instruction::Instruction() = default;
+Instruction::Instruction(Id id) : id_(id) {}
 
 Instruction::~Instruction() = default;
 
diff --git a/src/tint/lang/core/ir/instruction.h b/src/tint/lang/core/ir/instruction.h
index cff4ab0a..00a96ac 100644
--- a/src/tint/lang/core/ir/instruction.h
+++ b/src/tint/lang/core/ir/instruction.h
@@ -47,6 +47,8 @@
 /// An instruction in the IR.
 class Instruction : public Castable<Instruction> {
   public:
+    using Id = uint32_t;
+
     /// Destructor
     ~Instruction() override;
 
@@ -166,7 +168,10 @@
     };
 
     /// Constructor
-    Instruction();
+    explicit Instruction(Id id);
+
+    /// The instruction id
+    Id id_;
 
     /// The block that owns this instruction
     ConstPropagatingPtr<ir::Block> block_;
diff --git a/src/tint/lang/core/ir/let.cc b/src/tint/lang/core/ir/let.cc
index f0317e1..de01f25 100644
--- a/src/tint/lang/core/ir/let.cc
+++ b/src/tint/lang/core/ir/let.cc
@@ -35,9 +35,9 @@
 
 namespace tint::core::ir {
 
-Let::Let() = default;
+Let::Let(Id id) : Base(id) {}
 
-Let::Let(InstructionResult* result, ir::Value* value) {
+Let::Let(Id id, InstructionResult* result, ir::Value* value) : Base(id) {
     AddOperand(Let::kValueOperandOffset, value);
     AddResult(result);
 }
@@ -47,7 +47,8 @@
 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>(new_result, val);
+    auto* new_let =
+        ctx.ir.allocators.instructions.Create<Let>(ctx.ir.NextInstructionId(), new_result, val);
 
     auto name = ctx.ir.NameOf(this);
     ctx.ir.SetName(new_let, name.Name());
diff --git a/src/tint/lang/core/ir/let.h b/src/tint/lang/core/ir/let.h
index b02de3a..d80a9b8 100644
--- a/src/tint/lang/core/ir/let.h
+++ b/src/tint/lang/core/ir/let.h
@@ -41,12 +41,14 @@
     static constexpr size_t kValueOperandOffset = 0;
 
     /// Constructor (no result, no operands)
-    Let();
+    /// @param id the instruction id
+    explicit Let(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param value the let's value
-    Let(InstructionResult* result, Value* value);
+    Let(Id id, InstructionResult* result, Value* value);
 
     ~Let() override;
 
diff --git a/src/tint/lang/core/ir/load.cc b/src/tint/lang/core/ir/load.cc
index 77007cc..4ac424a 100644
--- a/src/tint/lang/core/ir/load.cc
+++ b/src/tint/lang/core/ir/load.cc
@@ -36,11 +36,11 @@
 
 namespace tint::core::ir {
 
-Load::Load() {
+Load::Load(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-Load::Load(InstructionResult* result, Value* from) {
+Load::Load(Id id, InstructionResult* result, Value* from) : Base(id) {
     flags_.Add(Flag::kSequenced);
 
     AddOperand(Load::kFromOperandOffset, from);
@@ -52,7 +52,8 @@
 Load* Load::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
     auto* from = ctx.Remap(From());
-    return ctx.ir.allocators.instructions.Create<Load>(new_result, from);
+    return ctx.ir.allocators.instructions.Create<Load>(ctx.ir.NextInstructionId(), new_result,
+                                                       from);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load.h b/src/tint/lang/core/ir/load.h
index 7c51aa8..96e97ad 100644
--- a/src/tint/lang/core/ir/load.h
+++ b/src/tint/lang/core/ir/load.h
@@ -48,12 +48,14 @@
     static constexpr size_t kNumOperands = 1;
 
     /// Constructor (no results, no operands)
-    Load();
+    /// @param id the instruction id
+    explicit Load(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param from the value being loaded from
-    Load(InstructionResult* result, Value* from);
+    Load(Id id, InstructionResult* result, Value* from);
 
     ~Load() override;
 
diff --git a/src/tint/lang/core/ir/load_vector_element.cc b/src/tint/lang/core/ir/load_vector_element.cc
index 29889e7..876da47 100644
--- a/src/tint/lang/core/ir/load_vector_element.cc
+++ b/src/tint/lang/core/ir/load_vector_element.cc
@@ -34,11 +34,15 @@
 
 namespace tint::core::ir {
 
-LoadVectorElement::LoadVectorElement() {
+LoadVectorElement::LoadVectorElement(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-LoadVectorElement::LoadVectorElement(InstructionResult* result, ir::Value* from, ir::Value* index) {
+LoadVectorElement::LoadVectorElement(Id id,
+                                     InstructionResult* result,
+                                     ir::Value* from,
+                                     ir::Value* index)
+    : Base(id) {
     flags_.Add(Flag::kSequenced);
 
     AddOperand(LoadVectorElement::kFromOperandOffset, from);
@@ -52,7 +56,8 @@
     auto* new_result = ctx.Clone(Result(0));
     auto* from = ctx.Remap(From());
     auto* index = ctx.Remap(Index());
-    return ctx.ir.allocators.instructions.Create<LoadVectorElement>(new_result, from, index);
+    return ctx.ir.allocators.instructions.Create<LoadVectorElement>(ctx.ir.NextInstructionId(),
+                                                                    new_result, from, index);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/load_vector_element.h b/src/tint/lang/core/ir/load_vector_element.h
index 20dd52b..44e3a8c 100644
--- a/src/tint/lang/core/ir/load_vector_element.h
+++ b/src/tint/lang/core/ir/load_vector_element.h
@@ -51,13 +51,16 @@
     static constexpr size_t kNumOperands = 2;
 
     /// Constructor (no results, no operands)
-    LoadVectorElement();
+    /// @param id the instruction id
+    explicit LoadVectorElement(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param from the vector pointer
     /// @param index the new vector element index
-    LoadVectorElement(InstructionResult* result, ir::Value* from, ir::Value* index);
+    LoadVectorElement(Id id, InstructionResult* result, ir::Value* from, ir::Value* index);
+
     ~LoadVectorElement() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/loop.cc b/src/tint/lang/core/ir/loop.cc
index fca440f..cb8ff03 100644
--- a/src/tint/lang/core/ir/loop.cc
+++ b/src/tint/lang/core/ir/loop.cc
@@ -36,10 +36,10 @@
 
 namespace tint::core::ir {
 
-Loop::Loop() = default;
+Loop::Loop(Id id) : Base(id) {}
 
-Loop::Loop(ir::Block* i, ir::MultiInBlock* b, ir::MultiInBlock* c)
-    : initializer_(i), body_(b), continuing_(c) {
+Loop::Loop(Id id, ir::Block* i, ir::MultiInBlock* b, ir::MultiInBlock* c)
+    : Base(id), initializer_(i), body_(b), continuing_(c) {
     TINT_ASSERT(initializer_);
     TINT_ASSERT(body_);
     TINT_ASSERT(continuing_);
@@ -62,8 +62,8 @@
     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>(new_init, new_body, new_continuing);
+    auto* new_loop = ctx.ir.allocators.instructions.Create<Loop>(
+        ctx.ir.NextInstructionId(), 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.h b/src/tint/lang/core/ir/loop.h
index edfca17..d29eef7 100644
--- a/src/tint/lang/core/ir/loop.h
+++ b/src/tint/lang/core/ir/loop.h
@@ -73,13 +73,15 @@
 class Loop final : public Castable<Loop, ControlInstruction> {
   public:
     /// Constructor (no results, no operands, no blocks)
-    Loop();
+    /// @param id the instruction id
+    explicit Loop(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param i the initializer block
     /// @param b the body block
     /// @param c the continuing block
-    Loop(ir::Block* i, ir::MultiInBlock* b, ir::MultiInBlock* c);
+    Loop(Id id, ir::Block* i, ir::MultiInBlock* b, ir::MultiInBlock* c);
     ~Loop() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/loop_test.cc b/src/tint/lang/core/ir/loop_test.cc
index 537b6cd..b0bc859 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(nullptr, b.MultiInBlock(), b.MultiInBlock());
+            Loop loop(mod.NextInstructionId(), nullptr, b.MultiInBlock(), b.MultiInBlock());
         },
         "internal compiler error");
 }
@@ -62,7 +62,7 @@
         {
             Module mod;
             Builder b{mod};
-            Loop loop(b.Block(), nullptr, b.MultiInBlock());
+            Loop loop(mod.NextInstructionId(), b.Block(), nullptr, b.MultiInBlock());
         },
         "internal compiler error");
 }
@@ -72,7 +72,7 @@
         {
             Module mod;
             Builder b{mod};
-            Loop loop(b.Block(), b.MultiInBlock(), nullptr);
+            Loop loop(mod.NextInstructionId(), b.Block(), b.MultiInBlock(), nullptr);
         },
         "internal compiler error");
 }
diff --git a/src/tint/lang/core/ir/member_builtin_call.cc b/src/tint/lang/core/ir/member_builtin_call.cc
index 418e8a6..a340471 100644
--- a/src/tint/lang/core/ir/member_builtin_call.cc
+++ b/src/tint/lang/core/ir/member_builtin_call.cc
@@ -38,13 +38,15 @@
 
 namespace tint::core::ir {
 
-MemberBuiltinCall::MemberBuiltinCall() {
+MemberBuiltinCall::MemberBuiltinCall(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-MemberBuiltinCall::MemberBuiltinCall(InstructionResult* result,
+MemberBuiltinCall::MemberBuiltinCall(Id id,
+                                     InstructionResult* result,
                                      Value* object,
-                                     VectorRef<Value*> arguments) {
+                                     VectorRef<Value*> arguments)
+    : Base(id) {
     flags_.Add(Flag::kSequenced);
     AddOperand(MemberBuiltinCall::kObjectOperandOffset, object);
     AddOperands(MemberBuiltinCall::kArgsOperandOffset, std::move(arguments));
diff --git a/src/tint/lang/core/ir/member_builtin_call.h b/src/tint/lang/core/ir/member_builtin_call.h
index 718bad7..96ec128 100644
--- a/src/tint/lang/core/ir/member_builtin_call.h
+++ b/src/tint/lang/core/ir/member_builtin_call.h
@@ -44,15 +44,19 @@
     static constexpr size_t kArgsOperandOffset = 1;
 
     /// Constructor (no results, no operands)
-    MemberBuiltinCall();
+    /// @param id the instruction id
+    explicit MemberBuiltinCall(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param object the object
     /// @param args the call arguments
-    explicit MemberBuiltinCall(InstructionResult* result,
-                               Value* object,
-                               VectorRef<Value*> args = tint::Empty);
+    MemberBuiltinCall(Id id,
+                      InstructionResult* result,
+                      Value* object,
+                      VectorRef<Value*> args = tint::Empty);
+
     ~MemberBuiltinCall() override;
 
     /// @returns the offset of the arguments in Operands()
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index c510de0..69f30b6 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -134,6 +134,9 @@
     /// @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;
 
@@ -160,6 +163,9 @@
 
     /// The map of core::constant::Value to their ir::Constant.
     Hashmap<const core::constant::Value*, ir::Constant*, 16> constants;
+
+  private:
+    Instruction::Id next_instruction_id_ = 0;
 };
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/next_iteration.cc b/src/tint/lang/core/ir/next_iteration.cc
index 5fc404f..b81bdf3 100644
--- a/src/tint/lang/core/ir/next_iteration.cc
+++ b/src/tint/lang/core/ir/next_iteration.cc
@@ -39,10 +39,10 @@
 
 namespace tint::core::ir {
 
-NextIteration::NextIteration() = default;
+NextIteration::NextIteration(Id id) : Base(id) {}
 
-NextIteration::NextIteration(ir::Loop* loop, VectorRef<Value*> args /* = tint::Empty */)
-    : loop_(loop) {
+NextIteration::NextIteration(Id id, ir::Loop* loop, VectorRef<Value*> args /* = tint::Empty */)
+    : Base(id), loop_(loop) {
     TINT_ASSERT(loop_);
 
     AddOperands(NextIteration::kArgsOperandOffset, std::move(args));
@@ -57,7 +57,8 @@
 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>(new_loop, args);
+    return ctx.ir.allocators.instructions.Create<NextIteration>(ctx.ir.NextInstructionId(),
+                                                                new_loop, args);
 }
 
 void NextIteration::SetLoop(ir::Loop* loop) {
diff --git a/src/tint/lang/core/ir/next_iteration.h b/src/tint/lang/core/ir/next_iteration.h
index c80c10f..825f8c0 100644
--- a/src/tint/lang/core/ir/next_iteration.h
+++ b/src/tint/lang/core/ir/next_iteration.h
@@ -48,12 +48,15 @@
     static constexpr size_t kArgsOperandOffset = 0;
 
     /// Constructor (no operands, no loop)
-    NextIteration();
+    /// @param id the instruction id
+    explicit NextIteration(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param loop the loop being iterated
     /// @param args the arguments for the MultiInBlock
-    explicit NextIteration(ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
+    NextIteration(Id id, ir::Loop* loop, VectorRef<Value*> args = tint::Empty);
+
     ~NextIteration() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/operand_instruction.h b/src/tint/lang/core/ir/operand_instruction.h
index cab7667..bafa3ed 100644
--- a/src/tint/lang/core/ir/operand_instruction.h
+++ b/src/tint/lang/core/ir/operand_instruction.h
@@ -42,6 +42,10 @@
 template <unsigned N, unsigned R>
 class OperandInstruction : public Castable<OperandInstruction<N, R>, Instruction> {
   public:
+    /// Constructor
+    /// @param id the instruction id
+    explicit OperandInstruction(Instruction::Id id) : OperandInstruction::Base(id) {}
+
     /// Destructor
     ~OperandInstruction() override = default;
 
diff --git a/src/tint/lang/core/ir/return.cc b/src/tint/lang/core/ir/return.cc
index 4f7608b..fa543af 100644
--- a/src/tint/lang/core/ir/return.cc
+++ b/src/tint/lang/core/ir/return.cc
@@ -37,13 +37,13 @@
 
 namespace tint::core::ir {
 
-Return::Return() = default;
+Return::Return(Id id) : Base(id) {}
 
-Return::Return(Function* func) {
+Return::Return(Id id, Function* func) : Base(id) {
     AddOperand(Return::kFunctionOperandOffset, func);
 }
 
-Return::Return(Function* func, ir::Value* arg) {
+Return::Return(Id id, Function* func, ir::Value* arg) : Base(id) {
     AddOperand(Return::kFunctionOperandOffset, func);
     AddOperand(Return::kArgsOperandOffset, arg);
 }
@@ -53,9 +53,10 @@
 Return* Return::Clone(CloneContext& ctx) {
     auto* fn = ctx.Remap(Func());
     if (auto* val = Value()) {
-        return ctx.ir.allocators.instructions.Create<Return>(fn, ctx.Remap(val));
+        return ctx.ir.allocators.instructions.Create<Return>(ctx.ir.NextInstructionId(), fn,
+                                                             ctx.Remap(val));
     }
-    return ctx.ir.allocators.instructions.Create<Return>(fn);
+    return ctx.ir.allocators.instructions.Create<Return>(ctx.ir.NextInstructionId(), fn);
 }
 
 Function* Return::Func() {
diff --git a/src/tint/lang/core/ir/return.h b/src/tint/lang/core/ir/return.h
index 4e8827b..9faa020 100644
--- a/src/tint/lang/core/ir/return.h
+++ b/src/tint/lang/core/ir/return.h
@@ -50,16 +50,19 @@
     static constexpr size_t kArgsOperandOffset = 1;
 
     /// Constructor (no operands)
-    Return();
+    /// @param id the instruction id
+    explicit Return(Id id);
 
     /// Constructor (no return value)
+    /// @param id the instruction id
     /// @param func the function being returned
-    explicit Return(Function* func);
+    Return(Id id, Function* func);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param func the function being returned
     /// @param arg the return value
-    Return(Function* func, ir::Value* arg);
+    Return(Id id, Function* func, ir::Value* arg);
 
     ~Return() override;
 
diff --git a/src/tint/lang/core/ir/store.cc b/src/tint/lang/core/ir/store.cc
index 388646b..5f65d61 100644
--- a/src/tint/lang/core/ir/store.cc
+++ b/src/tint/lang/core/ir/store.cc
@@ -34,11 +34,11 @@
 
 namespace tint::core::ir {
 
-Store::Store() {
+Store::Store(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-Store::Store(Value* to, Value* from) {
+Store::Store(Id id, Value* to, Value* from) : Base(id) {
     flags_.Add(Flag::kSequenced);
 
     AddOperand(Store::kToOperandOffset, to);
@@ -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>(to, from);
+    return ctx.ir.allocators.instructions.Create<Store>(ctx.ir.NextInstructionId(), to, from);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store.h b/src/tint/lang/core/ir/store.h
index 1e51149..aecd476 100644
--- a/src/tint/lang/core/ir/store.h
+++ b/src/tint/lang/core/ir/store.h
@@ -51,12 +51,15 @@
     static constexpr size_t kNumOperands = 2;
 
     /// Constructor (no results, no operands)
-    Store();
+    /// @param id the instruction id
+    explicit Store(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param to the value to store too
     /// @param from the value being stored from
-    Store(Value* to, Value* from);
+    Store(Id id, Value* to, Value* from);
+
     ~Store() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/store_vector_element.cc b/src/tint/lang/core/ir/store_vector_element.cc
index 16b6f87..4077a23 100644
--- a/src/tint/lang/core/ir/store_vector_element.cc
+++ b/src/tint/lang/core/ir/store_vector_element.cc
@@ -34,11 +34,12 @@
 
 namespace tint::core::ir {
 
-StoreVectorElement::StoreVectorElement() {
+StoreVectorElement::StoreVectorElement(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-StoreVectorElement::StoreVectorElement(ir::Value* to, ir::Value* index, ir::Value* value) {
+StoreVectorElement::StoreVectorElement(Id id, ir::Value* to, ir::Value* index, ir::Value* value)
+    : Base(id) {
     flags_.Add(Flag::kSequenced);
 
     AddOperand(StoreVectorElement::kToOperandOffset, to);
@@ -52,7 +53,8 @@
     auto* to = ctx.Remap(To());
     auto* idx = ctx.Remap(Index());
     auto* val = ctx.Remap(Value());
-    return ctx.ir.allocators.instructions.Create<StoreVectorElement>(to, idx, val);
+    return ctx.ir.allocators.instructions.Create<StoreVectorElement>(ctx.ir.NextInstructionId(), to,
+                                                                     idx, val);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/store_vector_element.h b/src/tint/lang/core/ir/store_vector_element.h
index 22c29ed..4a97f2f 100644
--- a/src/tint/lang/core/ir/store_vector_element.h
+++ b/src/tint/lang/core/ir/store_vector_element.h
@@ -54,13 +54,16 @@
     static constexpr size_t kNumOperands = 3;
 
     /// Constructor (no operands)
-    StoreVectorElement();
+    /// @param id the instruction id
+    explicit StoreVectorElement(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param to the vector pointer
     /// @param index the new vector element index
     /// @param value the new vector element value
-    StoreVectorElement(ir::Value* to, ir::Value* index, ir::Value* value);
+    StoreVectorElement(Id id, ir::Value* to, ir::Value* index, ir::Value* value);
+
     ~StoreVectorElement() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/switch.cc b/src/tint/lang/core/ir/switch.cc
index f94f200..8717c84 100644
--- a/src/tint/lang/core/ir/switch.cc
+++ b/src/tint/lang/core/ir/switch.cc
@@ -37,9 +37,9 @@
 
 namespace tint::core::ir {
 
-Switch::Switch() = default;
+Switch::Switch(Id id) : Base(id) {}
 
-Switch::Switch(Value* cond) {
+Switch::Switch(Id id, Value* cond) : Base(id) {
     TINT_ASSERT(cond);
 
     AddOperand(Switch::kConditionOperandOffset, cond);
@@ -61,7 +61,8 @@
 
 Switch* Switch::Clone(CloneContext& ctx) {
     auto* cond = ctx.Remap(Condition());
-    auto* new_switch = ctx.ir.allocators.instructions.Create<Switch>(cond);
+    auto* new_switch =
+        ctx.ir.allocators.instructions.Create<Switch>(ctx.ir.NextInstructionId(), cond);
     ctx.Replace(this, new_switch);
 
     new_switch->cases_.Reserve(cases_.Length());
diff --git a/src/tint/lang/core/ir/switch.h b/src/tint/lang/core/ir/switch.h
index 151ecf3..207ce02 100644
--- a/src/tint/lang/core/ir/switch.h
+++ b/src/tint/lang/core/ir/switch.h
@@ -82,11 +82,14 @@
     };
 
     /// Constructor (no results, no operands, no cases)
-    Switch();
+    /// @param id the instruction id
+    explicit Switch(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param cond the condition
-    explicit Switch(Value* cond);
+    Switch(Id id, Value* cond);
+
     ~Switch() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/swizzle.cc b/src/tint/lang/core/ir/swizzle.cc
index 4fd3c0c..9cec926 100644
--- a/src/tint/lang/core/ir/swizzle.cc
+++ b/src/tint/lang/core/ir/swizzle.cc
@@ -37,10 +37,10 @@
 
 namespace tint::core::ir {
 
-Swizzle::Swizzle() = default;
+Swizzle::Swizzle(Id id) : Base(id) {}
 
-Swizzle::Swizzle(InstructionResult* result, Value* object, VectorRef<uint32_t> indices)
-    : indices_(std::move(indices)) {
+Swizzle::Swizzle(Id id, InstructionResult* result, Value* object, VectorRef<uint32_t> indices)
+    : Base(id), indices_(std::move(indices)) {
     TINT_ASSERT(!indices.IsEmpty());
     TINT_ASSERT(indices.Length() <= 4);
 
@@ -57,7 +57,8 @@
 Swizzle* Swizzle::Clone(CloneContext& ctx) {
     auto* result = ctx.Clone(Result(0));
     auto* obj = ctx.Remap(Object());
-    return ctx.ir.allocators.instructions.Create<Swizzle>(result, obj, indices_);
+    return ctx.ir.allocators.instructions.Create<Swizzle>(ctx.ir.NextInstructionId(), result, obj,
+                                                          indices_);
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/swizzle.h b/src/tint/lang/core/ir/swizzle.h
index efe85d0..9a2f7a7 100644
--- a/src/tint/lang/core/ir/swizzle.h
+++ b/src/tint/lang/core/ir/swizzle.h
@@ -43,13 +43,16 @@
     static constexpr size_t kObjectOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    Swizzle();
+    /// @param id the instruction id
+    explicit Swizzle(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param object the object being swizzled
     /// @param indices the indices to swizzle
-    Swizzle(InstructionResult* result, Value* object, VectorRef<uint32_t> indices);
+    Swizzle(Id id, InstructionResult* result, Value* object, VectorRef<uint32_t> indices);
+
     ~Swizzle() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/terminate_invocation.cc b/src/tint/lang/core/ir/terminate_invocation.cc
index 97bf084..8f1cd66 100644
--- a/src/tint/lang/core/ir/terminate_invocation.cc
+++ b/src/tint/lang/core/ir/terminate_invocation.cc
@@ -34,10 +34,12 @@
 
 namespace tint::core::ir {
 
+TerminateInvocation::TerminateInvocation(Id id) : Base(id) {}
+
 TerminateInvocation::~TerminateInvocation() = default;
 
 TerminateInvocation* TerminateInvocation::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.instructions.Create<TerminateInvocation>();
+    return ctx.ir.allocators.instructions.Create<TerminateInvocation>(ctx.ir.NextInstructionId());
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/terminate_invocation.h b/src/tint/lang/core/ir/terminate_invocation.h
index b0308bb..5a12625 100644
--- a/src/tint/lang/core/ir/terminate_invocation.h
+++ b/src/tint/lang/core/ir/terminate_invocation.h
@@ -37,6 +37,10 @@
 /// An terminate invocation instruction in the IR.
 class TerminateInvocation final : public Castable<TerminateInvocation, Terminator> {
   public:
+    /// Constructor
+    /// @param id the instruction id
+    explicit TerminateInvocation(Id id);
+
     ~TerminateInvocation() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/terminator.cc b/src/tint/lang/core/ir/terminator.cc
index 36212d3..c511eab 100644
--- a/src/tint/lang/core/ir/terminator.cc
+++ b/src/tint/lang/core/ir/terminator.cc
@@ -33,6 +33,8 @@
 
 namespace tint::core::ir {
 
+Terminator::Terminator(Id id) : Base(id) {}
+
 Terminator::~Terminator() = default;
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/terminator.h b/src/tint/lang/core/ir/terminator.h
index f02c7b2..f1ff399 100644
--- a/src/tint/lang/core/ir/terminator.h
+++ b/src/tint/lang/core/ir/terminator.h
@@ -42,6 +42,10 @@
 /// The base class of all instructions that terminate a block.
 class Terminator : public Castable<Terminator, OperandInstruction<1, 0>> {
   public:
+    /// Constructor
+    /// @param id the instruction id
+    explicit Terminator(Id id);
+
     ~Terminator() override;
 
     /// @returns the offset of the arguments in Operands()
diff --git a/src/tint/lang/core/ir/unary.cc b/src/tint/lang/core/ir/unary.cc
index 9fdfe0f..e3a7bb0 100644
--- a/src/tint/lang/core/ir/unary.cc
+++ b/src/tint/lang/core/ir/unary.cc
@@ -34,9 +34,9 @@
 
 namespace tint::core::ir {
 
-Unary::Unary() = default;
+Unary::Unary(Id id) : Base(id) {}
 
-Unary::Unary(InstructionResult* result, UnaryOp op, Value* val) : op_(op) {
+Unary::Unary(Id id, InstructionResult* result, UnaryOp op, Value* val) : Base(id), op_(op) {
     AddOperand(Unary::kValueOperandOffset, val);
     AddResult(result);
 }
diff --git a/src/tint/lang/core/ir/unary.h b/src/tint/lang/core/ir/unary.h
index f148892..f48e0f6 100644
--- a/src/tint/lang/core/ir/unary.h
+++ b/src/tint/lang/core/ir/unary.h
@@ -53,13 +53,16 @@
     static constexpr size_t kNumOperands = 1;
 
     /// Constructor (no results, no operands)
-    Unary();
+    /// @param id the instruction id
+    explicit Unary(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param op the unary operator
     /// @param val the input value for the instruction
-    Unary(InstructionResult* result, UnaryOp op, Value* val);
+    Unary(Id id, InstructionResult* result, UnaryOp op, Value* val);
+
     ~Unary() override;
 
     /// @returns the value for the instruction
diff --git a/src/tint/lang/core/ir/unreachable.cc b/src/tint/lang/core/ir/unreachable.cc
index b4146ad..24a0343 100644
--- a/src/tint/lang/core/ir/unreachable.cc
+++ b/src/tint/lang/core/ir/unreachable.cc
@@ -34,10 +34,12 @@
 
 namespace tint::core::ir {
 
+Unreachable::Unreachable(Instruction::Id id) : Base(id) {}
+
 Unreachable::~Unreachable() = default;
 
 Unreachable* Unreachable::Clone(CloneContext& ctx) {
-    return ctx.ir.allocators.instructions.Create<Unreachable>();
+    return ctx.ir.allocators.instructions.Create<Unreachable>(ctx.ir.NextInstructionId());
 }
 
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/unreachable.h b/src/tint/lang/core/ir/unreachable.h
index 43cddc9..e353436 100644
--- a/src/tint/lang/core/ir/unreachable.h
+++ b/src/tint/lang/core/ir/unreachable.h
@@ -37,6 +37,9 @@
 /// An unreachable instruction in the IR.
 class Unreachable final : public Castable<Unreachable, Terminator> {
   public:
+    /// @param id the instruction id
+    explicit Unreachable(Instruction::Id id);
+
     ~Unreachable() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/user_call.cc b/src/tint/lang/core/ir/user_call.cc
index 4da83b6..6696977 100644
--- a/src/tint/lang/core/ir/user_call.cc
+++ b/src/tint/lang/core/ir/user_call.cc
@@ -36,11 +36,12 @@
 
 namespace tint::core::ir {
 
-UserCall::UserCall() {
+UserCall::UserCall(Id id) : Base(id) {
     flags_.Add(Flag::kSequenced);
 }
 
-UserCall::UserCall(InstructionResult* result, Function* func, VectorRef<Value*> arguments) {
+UserCall::UserCall(Id id, InstructionResult* result, Function* func, VectorRef<Value*> arguments)
+    : Base(id) {
     flags_.Add(Flag::kSequenced);
     AddOperand(UserCall::kFunctionOperandOffset, func);
     AddOperands(UserCall::kArgsOperandOffset, std::move(arguments));
@@ -53,7 +54,8 @@
     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>(new_result, target, args);
+    return ctx.ir.allocators.instructions.Create<UserCall>(ctx.ir.NextInstructionId(), new_result,
+                                                           target, args);
 }
 
 void UserCall::SetArgs(VectorRef<Value*> arguments) {
diff --git a/src/tint/lang/core/ir/user_call.h b/src/tint/lang/core/ir/user_call.h
index 77ea90f..4f21e35 100644
--- a/src/tint/lang/core/ir/user_call.h
+++ b/src/tint/lang/core/ir/user_call.h
@@ -52,13 +52,16 @@
     static constexpr size_t kMinOperands = 1;
 
     /// Constructor (no results, no operands)
-    UserCall();
+    /// @param id the instruction id
+    explicit UserCall(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the function being called
     /// @param args the function arguments
-    UserCall(InstructionResult* result, Function* func, VectorRef<Value*> args);
+    UserCall(Id id, InstructionResult* result, Function* func, VectorRef<Value*> args);
+
     ~UserCall() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 3566f3f..5fb3314 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -2124,7 +2124,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Var_RootBlock_NullResult) {
-    auto* v = mod.allocators.instructions.Create<ir::Var>(nullptr);
+    auto* v = mod.allocators.instructions.Create<ir::Var>(mod.NextInstructionId(), nullptr);
     v->SetInitializer(b.Constant(0_i));
     mod.root_block->Append(v);
 
@@ -2155,7 +2155,7 @@
 }
 
 TEST_F(IR_ValidatorTest, Var_Function_NullResult) {
-    auto* v = mod.allocators.instructions.Create<ir::Var>(nullptr);
+    auto* v = mod.allocators.instructions.Create<ir::Var>(mod.NextInstructionId(), nullptr);
     v->SetInitializer(b.Constant(0_i));
 
     auto* f = b.Function("my_func", ty.void_());
@@ -2550,7 +2550,8 @@
 }
 
 TEST_F(IR_ValidatorTest, Let_NullResult) {
-    auto* v = mod.allocators.instructions.Create<ir::Let>(nullptr, b.Constant(1_i));
+    auto* v = mod.allocators.instructions.Create<ir::Let>(mod.NextInstructionId(), nullptr,
+                                                          b.Constant(1_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -2579,7 +2580,8 @@
 }
 
 TEST_F(IR_ValidatorTest, Let_NullValue) {
-    auto* v = mod.allocators.instructions.Create<ir::Let>(b.InstructionResult(ty.f32()), nullptr);
+    auto* v = mod.allocators.instructions.Create<ir::Let>(mod.NextInstructionId(),
+                                                          b.InstructionResult(ty.f32()), nullptr);
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -2608,8 +2610,8 @@
 }
 
 TEST_F(IR_ValidatorTest, Let_WrongType) {
-    auto* v =
-        mod.allocators.instructions.Create<ir::Let>(b.InstructionResult(ty.f32()), b.Constant(1_i));
+    auto* v = mod.allocators.instructions.Create<ir::Let>(
+        mod.NextInstructionId(), b.InstructionResult(ty.f32()), b.Constant(1_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -2847,7 +2849,7 @@
 
 TEST_F(IR_ValidatorTest, Binary_Result_Nullptr) {
     auto* bin = mod.allocators.instructions.Create<ir::CoreBinary>(
-        nullptr, BinaryOp::kAdd, b.Constant(3_i), b.Constant(2_i));
+        mod.NextInstructionId(), nullptr, BinaryOp::kAdd, b.Constant(3_i), b.Constant(2_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -2894,8 +2896,8 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(), R"(:3:5 error: binary: expected at least 2 operands, got 0
-    %2:i32 = add 
-    ^^^^^^^^^^^^^
+    %2:i32 = add
+    ^^^^^^^^^^^^
 
 :2:3 note: in block
   $B1: {
@@ -2904,7 +2906,7 @@
 note: # Disassembly
 %my_func = func():void {
   $B1: {
-    %2:i32 = add 
+    %2:i32 = add
     ret
   }
 }
@@ -2967,8 +2969,8 @@
 }
 
 TEST_F(IR_ValidatorTest, Unary_Result_Nullptr) {
-    auto* bin = mod.allocators.instructions.Create<ir::CoreUnary>(nullptr, UnaryOp::kNegation,
-                                                                  b.Constant(2_i));
+    auto* bin = mod.allocators.instructions.Create<ir::CoreUnary>(
+        mod.NextInstructionId(), nullptr, UnaryOp::kNegation, b.Constant(2_i));
 
     auto* f = b.Function("my_func", ty.void_());
 
@@ -3048,8 +3050,8 @@
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
               R"(:3:5 error: unary: expected at least 1 operands, got 0
-    %2:f32 = negation 
-    ^^^^^^^^^^^^^^^^^^
+    %2:f32 = negation
+    ^^^^^^^^^^^^^^^^^
 
 :2:3 note: in block
   $B1: {
@@ -3058,7 +3060,7 @@
 note: # Disassembly
 %my_func = func():void {
   $B1: {
-    %2:f32 = negation 
+    %2:f32 = negation
     ret
   }
 }
@@ -3109,7 +3111,8 @@
 
 TEST_F(IR_ValidatorTest, ExitIf_NullIf) {
     auto* if_ = b.If(true);
-    if_->True()->Append(mod.allocators.instructions.Create<ExitIf>(nullptr));
+    if_->True()->Append(
+        mod.allocators.instructions.Create<ExitIf>(mod.NextInstructionId(), nullptr));
 
     auto* f = b.Function("my_func", ty.void_());
     auto sb = b.Append(f->Block());
@@ -3496,7 +3499,7 @@
     auto* switch_ = b.Switch(1_i);
 
     auto* def = b.DefaultCase(switch_);
-    def->Append(mod.allocators.instructions.Create<ExitSwitch>(nullptr));
+    def->Append(mod.allocators.instructions.Create<ExitSwitch>(mod.NextInstructionId(), nullptr));
 
     auto* f = b.Function("my_func", ty.void_());
     auto sb = b.Append(f->Block());
@@ -4831,7 +4834,8 @@
 TEST_F(IR_ValidatorTest, ExitLoop_NullLoop) {
     auto* loop = b.Loop();
     loop->Continuing()->Append(b.NextIteration(loop));
-    loop->Body()->Append(mod.allocators.instructions.Create<ExitLoop>(nullptr));
+    loop->Body()->Append(
+        mod.allocators.instructions.Create<ExitLoop>(mod.NextInstructionId(), nullptr));
 
     auto* f = b.Function("my_func", ty.void_());
     auto sb = b.Append(f->Block());
@@ -5537,8 +5541,8 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(
-            mod.allocators.instructions.Create<ir::Load>(b.InstructionResult(ty.i32()), nullptr));
+        b.Append(mod.allocators.instructions.Create<ir::Load>(
+            mod.NextInstructionId(), b.InstructionResult(ty.i32()), nullptr));
         b.Return(f);
     });
 
@@ -5567,8 +5571,8 @@
 
     b.Append(f->Block(), [&] {
         auto* let = b.Let("l", 1_i);
-        b.Append(mod.allocators.instructions.Create<ir::Load>(b.InstructionResult(ty.f32()),
-                                                              let->Result(0)));
+        b.Append(mod.allocators.instructions.Create<ir::Load>(
+            mod.NextInstructionId(), b.InstructionResult(ty.f32()), let->Result(0)));
         b.Return(f);
     });
 
@@ -5599,8 +5603,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        b.Append(mod.allocators.instructions.Create<ir::Load>(b.InstructionResult(ty.f32()),
-                                                              var->Result(0)));
+        b.Append(mod.allocators.instructions.Create<ir::Load>(
+            mod.NextInstructionId(), b.InstructionResult(ty.f32()), var->Result(0)));
         b.Return(f);
     });
 
@@ -5631,7 +5635,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        auto* load = mod.allocators.instructions.Create<ir::Load>(nullptr, var->Result(0));
+        auto* load = mod.allocators.instructions.Create<ir::Load>(mod.NextInstructionId(), nullptr,
+                                                                  var->Result(0));
         load->ClearResults();
         b.Append(load);
         b.Return(f);
@@ -5663,7 +5668,8 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::Store>(nullptr, b.Constant(42_i)));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(), nullptr,
+                                                               b.Constant(42_i)));
         b.Return(f);
     });
 
@@ -5692,7 +5698,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        b.Append(mod.allocators.instructions.Create<ir::Store>(var->Result(0), nullptr));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
+                                                               var->Result(0), nullptr));
         b.Return(f);
     });
 
@@ -5721,7 +5728,8 @@
     auto* f = b.Function("my_func", ty.void_());
 
     b.Append(f->Block(), [&] {
-        b.Append(mod.allocators.instructions.Create<ir::Store>(nullptr, nullptr));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(), nullptr,
+                                                               nullptr));
         b.Return(f);
     });
 
@@ -5758,8 +5766,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        auto* store =
-            mod.allocators.instructions.Create<ir::Store>(var->Result(0), b.Constant(42_i));
+        auto* store = mod.allocators.instructions.Create<ir::Store>(
+            mod.NextInstructionId(), var->Result(0), b.Constant(42_i));
         store->SetResults(Vector{b.InstructionResult(ty.i32())});
         b.Append(store);
         b.Return(f);
@@ -5791,7 +5799,8 @@
 
     b.Append(f->Block(), [&] {
         auto* let = b.Let("l", 1_i);
-        b.Append(mod.allocators.instructions.Create<ir::Store>(let->Result(0), b.Constant(42_u)));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
+                                                               let->Result(0), b.Constant(42_u)));
         b.Return(f);
     });
 
@@ -5822,7 +5831,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, i32>());
-        b.Append(mod.allocators.instructions.Create<ir::Store>(var->Result(0), b.Constant(42_u)));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
+                                                               var->Result(0), b.Constant(42_u)));
         b.Return(f);
     });
 
@@ -5854,7 +5864,8 @@
     b.Append(f->Block(), [&] {
         auto* result = b.InstructionResult(ty.u32());
         result->SetType(nullptr);
-        b.Append(mod.allocators.instructions.Create<ir::Store>(result, b.Constant(42_u)));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(), result,
+                                                               b.Constant(42_u)));
         b.Return(f);
     });
 
@@ -5895,7 +5906,8 @@
         auto* val = b.Construct(ty.u32(), 42_u);
         val->Result(0)->SetType(nullptr);
 
-        b.Append(mod.allocators.instructions.Create<ir::Store>(var->Result(0), val->Result(0)));
+        b.Append(mod.allocators.instructions.Create<ir::Store>(mod.NextInstructionId(),
+                                                               var->Result(0), val->Result(0)));
         b.Return(f);
     });
 
@@ -5927,8 +5939,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
-        b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(nullptr, var->Result(0),
-                                                                           b.Constant(1_i)));
+        b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(
+            mod.NextInstructionId(), nullptr, var->Result(0), b.Constant(1_i)));
         b.Return(f);
     });
 
@@ -5967,7 +5979,7 @@
 
     b.Append(f->Block(), [&] {
         b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(
-            b.InstructionResult(ty.f32()), nullptr, b.Constant(1_i)));
+            mod.NextInstructionId(), b.InstructionResult(ty.f32()), nullptr, b.Constant(1_i)));
         b.Return(f);
     });
 
@@ -5997,7 +6009,7 @@
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
         b.Append(mod.allocators.instructions.Create<ir::LoadVectorElement>(
-            b.InstructionResult(ty.f32()), var->Result(0), nullptr));
+            mod.NextInstructionId(), b.InstructionResult(ty.f32()), var->Result(0), nullptr));
         b.Return(f);
     });
 
@@ -6091,7 +6103,7 @@
 
     b.Append(f->Block(), [&] {
         b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(
-            nullptr, b.Constant(1_i), b.Constant(2_f)));
+            mod.NextInstructionId(), nullptr, b.Constant(1_i), b.Constant(2_f)));
         b.Return(f);
     });
 
@@ -6120,8 +6132,8 @@
 
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
-        b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(var->Result(0), nullptr,
-                                                                            b.Constant(2_f)));
+        b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(
+            mod.NextInstructionId(), var->Result(0), nullptr, b.Constant(2_f)));
         b.Return(f);
     });
 
@@ -6152,7 +6164,7 @@
     b.Append(f->Block(), [&] {
         auto* var = b.Var(ty.ptr<function, vec3<f32>>());
         b.Append(mod.allocators.instructions.Create<ir::StoreVectorElement>(
-            var->Result(0), b.Constant(1_i), nullptr));
+            mod.NextInstructionId(), var->Result(0), b.Constant(1_i), nullptr));
         b.Return(f);
     });
 
@@ -6191,7 +6203,7 @@
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
               R"(:4:5 error: store_vector_element: expected exactly 3 operands, got 0
-    store_vector_element 
+    store_vector_element
     ^^^^^^^^^^^^^^^^^^^^
 
 :2:3 note: in block
@@ -6202,7 +6214,7 @@
 %my_func = func():void {
   $B1: {
     %2:ptr<function, vec3<f32>, read_write> = var
-    store_vector_element 
+    store_vector_element
     ret
   }
 }
@@ -6633,7 +6645,7 @@
 TEST_F(IR_ValidatorTest, Switch_NoCondition) {
     auto* f = b.Function("my_func", ty.void_());
 
-    auto* s = b.ir.allocators.instructions.Create<ir::Switch>();
+    auto* s = b.ir.allocators.instructions.Create<ir::Switch>(mod.NextInstructionId());
     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 e39cbd9..3fca232 100644
--- a/src/tint/lang/core/ir/var.cc
+++ b/src/tint/lang/core/ir/var.cc
@@ -37,9 +37,9 @@
 
 namespace tint::core::ir {
 
-Var::Var() = default;
+Var::Var(Id id) : Base(id) {}
 
-Var::Var(InstructionResult* result) {
+Var::Var(Id id, InstructionResult* result) : Base(id) {
     if (result && result->Type()) {
         TINT_ASSERT(result->Type()->Is<core::type::MemoryView>());
     }
@@ -53,7 +53,8 @@
 
 Var* Var::Clone(CloneContext& ctx) {
     auto* new_result = ctx.Clone(Result(0));
-    auto* new_var = ctx.ir.allocators.instructions.Create<Var>(new_result);
+    auto* new_var =
+        ctx.ir.allocators.instructions.Create<Var>(ctx.ir.NextInstructionId(), new_result);
 
     new_var->binding_point_ = binding_point_;
     new_var->attributes_ = attributes_;
diff --git a/src/tint/lang/core/ir/var.h b/src/tint/lang/core/ir/var.h
index 32c3201..b274514 100644
--- a/src/tint/lang/core/ir/var.h
+++ b/src/tint/lang/core/ir/var.h
@@ -49,11 +49,14 @@
     static constexpr size_t kNumResults = 1;
 
     /// Constructor (no results, no operands)
-    Var();
+    /// @param id the instruction id
+    explicit Var(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
-    explicit Var(InstructionResult* result);
+    explicit Var(Id id, InstructionResult* result);
+
     ~Var() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/hlsl/ir/builtin_call.cc b/src/tint/lang/hlsl/ir/builtin_call.cc
index 2101f71..b1efb5e 100644
--- a/src/tint/lang/hlsl/ir/builtin_call.cc
+++ b/src/tint/lang/hlsl/ir/builtin_call.cc
@@ -37,10 +37,11 @@
 
 namespace tint::hlsl::ir {
 
-BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
+BuiltinCall::BuiltinCall(Id id,
+                         core::ir::InstructionResult* result,
                          BuiltinFn func,
                          VectorRef<core::ir::Value*> arguments)
-    : Base(result, arguments), func_(func) {
+    : Base(id, result, arguments), func_(func) {
     flags_.Add(Flag::kSequenced);
     TINT_ASSERT(func != BuiltinFn::kNone);
 }
@@ -50,7 +51,8 @@
 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>(new_result, func_, new_args);
+    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
+                                                              new_result, func_, new_args);
 }
 
 }  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/builtin_call.h b/src/tint/lang/hlsl/ir/builtin_call.h
index 9416c5b..6fff0ee 100644
--- a/src/tint/lang/hlsl/ir/builtin_call.h
+++ b/src/tint/lang/hlsl/ir/builtin_call.h
@@ -42,12 +42,15 @@
 class BuiltinCall final : public Castable<BuiltinCall, core::ir::BuiltinCall> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the builtin function
     /// @param args the conversion arguments
-    BuiltinCall(core::ir::InstructionResult* result,
+    BuiltinCall(Id id,
+                core::ir::InstructionResult* result,
                 BuiltinFn func,
                 VectorRef<core::ir::Value*> args = tint::Empty);
+
     ~BuiltinCall() override;
 
     /// @copydoc core::ir::Instruction::Clone()
diff --git a/src/tint/lang/hlsl/ir/member_builtin_call.cc b/src/tint/lang/hlsl/ir/member_builtin_call.cc
index 81707ca..7e3ba72 100644
--- a/src/tint/lang/hlsl/ir/member_builtin_call.cc
+++ b/src/tint/lang/hlsl/ir/member_builtin_call.cc
@@ -42,11 +42,12 @@
 
 namespace tint::hlsl::ir {
 
-MemberBuiltinCall::MemberBuiltinCall(core::ir::InstructionResult* result,
+MemberBuiltinCall::MemberBuiltinCall(Id id,
+                                     core::ir::InstructionResult* result,
                                      BuiltinFn func,
                                      core::ir::Value* object,
                                      VectorRef<core::ir::Value*> arguments)
-    : Base(result, object, arguments), func_(func) {
+    : Base(id, result, object, arguments), func_(func) {
     TINT_ASSERT(func != BuiltinFn::kNone);
 }
 
@@ -56,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>(new_result, func_, new_object,
-                                                                    std::move(new_args));
+    return ctx.ir.allocators.instructions.Create<MemberBuiltinCall>(
+        ctx.ir.NextInstructionId(), new_result, func_, new_object, std::move(new_args));
 }
 
 }  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/member_builtin_call.h b/src/tint/lang/hlsl/ir/member_builtin_call.h
index 068ff97..d7e2110 100644
--- a/src/tint/lang/hlsl/ir/member_builtin_call.h
+++ b/src/tint/lang/hlsl/ir/member_builtin_call.h
@@ -42,14 +42,17 @@
 class MemberBuiltinCall final : public Castable<MemberBuiltinCall, core::ir::MemberBuiltinCall> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the builtin function
     /// @param object the object
     /// @param args the call arguments
-    MemberBuiltinCall(core::ir::InstructionResult* result,
+    MemberBuiltinCall(Id id,
+                      core::ir::InstructionResult* result,
                       BuiltinFn func,
                       core::ir::Value* object,
                       VectorRef<core::ir::Value*> args = tint::Empty);
+
     ~MemberBuiltinCall() override;
 
     /// @copydoc core::ir::Instruction::Clone()
diff --git a/src/tint/lang/hlsl/ir/ternary.cc b/src/tint/lang/hlsl/ir/ternary.cc
index 64af60a10..ef092ce 100644
--- a/src/tint/lang/hlsl/ir/ternary.cc
+++ b/src/tint/lang/hlsl/ir/ternary.cc
@@ -33,7 +33,8 @@
 
 namespace tint::hlsl::ir {
 
-Ternary::Ternary(core::ir::InstructionResult* result, VectorRef<core::ir::Value*> args) {
+Ternary::Ternary(Id id, core::ir::InstructionResult* result, VectorRef<core::ir::Value*> args)
+    : Base(id) {
     AddResult(result);
     AddOperands(ArgsOperandOffset(), args);
 }
@@ -43,7 +44,8 @@
 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>(new_result, new_args);
+    return ctx.ir.allocators.instructions.Create<Ternary>(ctx.ir.NextInstructionId(), new_result,
+                                                          new_args);
 }
 
 }  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/ternary.h b/src/tint/lang/hlsl/ir/ternary.h
index 46c42aa..ebf519b 100644
--- a/src/tint/lang/hlsl/ir/ternary.h
+++ b/src/tint/lang/hlsl/ir/ternary.h
@@ -45,7 +45,8 @@
     ///
     /// Note, the args are in the order of (`false`, `true`, `compare`) to match select.
     /// Note, the ternary evaluates all branches, not just the selected branch.
-    Ternary(core::ir::InstructionResult* result, VectorRef<core::ir::Value*> args);
+    Ternary(Id id, core::ir::InstructionResult* result, VectorRef<core::ir::Value*> args);
+
     ~Ternary() override;
 
     /// @copydoc Instruction::Clone()
diff --git a/src/tint/lang/hlsl/ir/ternary_test.cc b/src/tint/lang/hlsl/ir/ternary_test.cc
index ac7a4f6..4c3baac 100644
--- a/src/tint/lang/hlsl/ir/ternary_test.cc
+++ b/src/tint/lang/hlsl/ir/ternary_test.cc
@@ -45,7 +45,8 @@
     auto* cmp = b.Constant(true);
 
     Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
-    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.InstructionResult(ty.u32()), args);
+    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.ir.NextInstructionId(),
+                                                           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}));
@@ -58,7 +59,8 @@
     auto* cmp = b.Constant(true);
 
     Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
-    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.InstructionResult(ty.u32()), args);
+    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.ir.NextInstructionId(),
+                                                           b.InstructionResult(ty.u32()), args);
 
     EXPECT_EQ(t->Results().Length(), 1u);
 
@@ -72,7 +74,8 @@
     auto* cmp = b.Constant(true);
 
     Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
-    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.InstructionResult(ty.u32()), args);
+    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.ir.NextInstructionId(),
+                                                           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 22eeb84..1d30657 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
@@ -469,8 +469,8 @@
 
     void Select(core::ir::CoreBuiltinCall* call) {
         Vector<core::ir::Value*, 4> args = call->Args();
-        auto* ternary =
-            b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(call->DetachResult(), args);
+        auto* ternary = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
+            b.ir.NextInstructionId(), call->DetachResult(), args);
         ternary->InsertBefore(call);
         call->Destroy();
     }
@@ -490,8 +490,8 @@
             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>(call->DetachResult(), args);
+        auto* trunc = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
+            b.ir.NextInstructionId(), 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 8c1493f..af8628b 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_uniform_access.cc
@@ -347,7 +347,7 @@
 
             Vector<core::ir::Value*, 3> args{false_, true_, cond->Result(0)};
             auto* shift_amt = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-                b.InstructionResult(ty.u32()), args);
+                b.ir.NextInstructionId(), b.InstructionResult(ty.u32()), args);
             b.Append(shift_amt);
 
             load = b.ShiftRight(ty.u32(), load, shift_amt);
@@ -408,7 +408,7 @@
                                                  cond->Result(0)};
 
                 load = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-                    b.InstructionResult(ty.vec2<u32>()), args);
+                    b.ir.NextInstructionId(), b.InstructionResult(ty.vec2<u32>()), args);
                 b.Append(load);
             }
         } else {
@@ -454,7 +454,7 @@
                                                  cond->Result(0)};
 
                 load = b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(
-                    b.InstructionResult(ty.u32()), args);
+                    b.ir.NextInstructionId(), 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 13ada6b..04ade0d 100644
--- a/src/tint/lang/msl/ir/binary.cc
+++ b/src/tint/lang/msl/ir/binary.cc
@@ -37,11 +37,12 @@
 
 namespace tint::msl::ir {
 
-Binary::Binary(core::ir::InstructionResult* result,
+Binary::Binary(Instruction::Id id,
+               core::ir::InstructionResult* result,
                core::BinaryOp op,
                core::ir::Value* lhs,
                core::ir::Value* rhs)
-    : Base(result, op, lhs, rhs) {}
+    : Base(id, result, op, lhs, rhs) {}
 
 Binary::~Binary() = default;
 
@@ -49,7 +50,8 @@
     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>(new_result, Op(), new_lhs, new_rhs);
+    return ctx.ir.allocators.instructions.Create<Binary>(ctx.ir.NextInstructionId(), new_result,
+                                                         Op(), new_lhs, new_rhs);
 }
 
 }  // namespace tint::msl::ir
diff --git a/src/tint/lang/msl/ir/binary.h b/src/tint/lang/msl/ir/binary.h
index 3df7799..5787cf8 100644
--- a/src/tint/lang/msl/ir/binary.h
+++ b/src/tint/lang/msl/ir/binary.h
@@ -39,11 +39,13 @@
 class Binary final : public Castable<Binary, core::ir::Binary> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param op the Binary operator
     /// @param lhs the lhs of the instruction
     /// @param rhs the rhs of the instruction
-    Binary(core::ir::InstructionResult* result,
+    Binary(Instruction::Id id,
+           core::ir::InstructionResult* result,
            core::BinaryOp op,
            core::ir::Value* lhs,
            core::ir::Value* rhs);
diff --git a/src/tint/lang/msl/ir/builtin_call.cc b/src/tint/lang/msl/ir/builtin_call.cc
index 65fcd1e..db3c2ed 100644
--- a/src/tint/lang/msl/ir/builtin_call.cc
+++ b/src/tint/lang/msl/ir/builtin_call.cc
@@ -37,10 +37,11 @@
 
 namespace tint::msl::ir {
 
-BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
+BuiltinCall::BuiltinCall(Id id,
+                         core::ir::InstructionResult* result,
                          BuiltinFn func,
                          VectorRef<core::ir::Value*> arguments)
-    : Base(result, arguments), func_(func) {
+    : Base(id, result, arguments), func_(func) {
     flags_.Add(Flag::kSequenced);
     TINT_ASSERT(func != BuiltinFn::kNone);
 }
@@ -50,7 +51,8 @@
 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>(new_result, func_, new_args);
+    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
+                                                              new_result, func_, new_args);
 }
 
 }  // namespace tint::msl::ir
diff --git a/src/tint/lang/msl/ir/builtin_call.h b/src/tint/lang/msl/ir/builtin_call.h
index de63d4d..8003ad4 100644
--- a/src/tint/lang/msl/ir/builtin_call.h
+++ b/src/tint/lang/msl/ir/builtin_call.h
@@ -42,10 +42,12 @@
 class BuiltinCall final : public Castable<BuiltinCall, core::ir::BuiltinCall> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the builtin function
     /// @param args the conversion arguments
-    BuiltinCall(core::ir::InstructionResult* result,
+    BuiltinCall(Id id,
+                core::ir::InstructionResult* result,
                 BuiltinFn func,
                 VectorRef<core::ir::Value*> args = tint::Empty);
     ~BuiltinCall() override;
diff --git a/src/tint/lang/msl/ir/member_builtin_call.cc b/src/tint/lang/msl/ir/member_builtin_call.cc
index 0f2ca6d..5bea168 100644
--- a/src/tint/lang/msl/ir/member_builtin_call.cc
+++ b/src/tint/lang/msl/ir/member_builtin_call.cc
@@ -42,11 +42,12 @@
 
 namespace tint::msl::ir {
 
-MemberBuiltinCall::MemberBuiltinCall(core::ir::InstructionResult* result,
+MemberBuiltinCall::MemberBuiltinCall(Id id,
+                                     core::ir::InstructionResult* result,
                                      BuiltinFn func,
                                      core::ir::Value* object,
                                      VectorRef<core::ir::Value*> arguments)
-    : Base(result, object, arguments), func_(func) {
+    : Base(id, result, object, arguments), func_(func) {
     TINT_ASSERT(func != BuiltinFn::kNone);
 }
 
@@ -56,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>(new_result, func_, new_object,
-                                                                    std::move(new_args));
+    return ctx.ir.allocators.instructions.Create<MemberBuiltinCall>(
+        ctx.ir.NextInstructionId(), new_result, func_, new_object, std::move(new_args));
 }
 
 }  // namespace tint::msl::ir
diff --git a/src/tint/lang/msl/ir/member_builtin_call.h b/src/tint/lang/msl/ir/member_builtin_call.h
index 6caa835..da47b18 100644
--- a/src/tint/lang/msl/ir/member_builtin_call.h
+++ b/src/tint/lang/msl/ir/member_builtin_call.h
@@ -42,14 +42,17 @@
 class MemberBuiltinCall final : public Castable<MemberBuiltinCall, core::ir::MemberBuiltinCall> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the builtin function
     /// @param object the object
     /// @param args the call arguments
-    MemberBuiltinCall(core::ir::InstructionResult* result,
+    MemberBuiltinCall(Id id,
+                      core::ir::InstructionResult* result,
                       BuiltinFn func,
                       core::ir::Value* object,
                       VectorRef<core::ir::Value*> args = tint::Empty);
+
     ~MemberBuiltinCall() override;
 
     /// @copydoc core::ir::Instruction::Clone()
diff --git a/src/tint/lang/spirv/ir/builtin_call.cc b/src/tint/lang/spirv/ir/builtin_call.cc
index dfdb86d..1b7c8fa 100644
--- a/src/tint/lang/spirv/ir/builtin_call.cc
+++ b/src/tint/lang/spirv/ir/builtin_call.cc
@@ -37,10 +37,11 @@
 
 namespace tint::spirv::ir {
 
-BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
+BuiltinCall::BuiltinCall(Id id,
+                         core::ir::InstructionResult* result,
                          BuiltinFn func,
                          VectorRef<core::ir::Value*> arguments)
-    : Base(result, arguments), func_(func) {
+    : Base(id, result, arguments), func_(func) {
     flags_.Add(Flag::kSequenced);
     TINT_ASSERT(func != BuiltinFn::kNone);
 }
@@ -50,7 +51,8 @@
 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>(new_result, func_, new_args);
+    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
+                                                              new_result, func_, new_args);
 }
 
 }  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/builtin_call.h b/src/tint/lang/spirv/ir/builtin_call.h
index 54e357f..1b3cf11 100644
--- a/src/tint/lang/spirv/ir/builtin_call.h
+++ b/src/tint/lang/spirv/ir/builtin_call.h
@@ -42,12 +42,15 @@
 class BuiltinCall final : public Castable<BuiltinCall, core::ir::BuiltinCall> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param func the builtin function
     /// @param args the conversion arguments
-    BuiltinCall(core::ir::InstructionResult* result,
+    BuiltinCall(Id id,
+                core::ir::InstructionResult* result,
                 BuiltinFn func,
                 VectorRef<core::ir::Value*> args = tint::Empty);
+
     ~BuiltinCall() override;
 
     /// @copydoc core::ir::Instruction::Clone()
diff --git a/src/tint/lang/wgsl/ir/builtin_call.cc b/src/tint/lang/wgsl/ir/builtin_call.cc
index 15696df..f4777d1 100644
--- a/src/tint/lang/wgsl/ir/builtin_call.cc
+++ b/src/tint/lang/wgsl/ir/builtin_call.cc
@@ -37,10 +37,11 @@
 
 namespace tint::wgsl::ir {
 
-BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
+BuiltinCall::BuiltinCall(Id id,
+                         core::ir::InstructionResult* result,
                          BuiltinFn fn,
                          VectorRef<core::ir::Value*> arguments)
-    : Base(result, arguments), fn_(fn) {
+    : Base(id, result, arguments), fn_(fn) {
     flags_.Add(Flag::kSequenced);
     TINT_ASSERT(fn != BuiltinFn::kNone);
 }
@@ -50,7 +51,8 @@
 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>(new_result, fn_, new_args);
+    return ctx.ir.allocators.instructions.Create<BuiltinCall>(ctx.ir.NextInstructionId(),
+                                                              new_result, fn_, new_args);
 }
 
 }  // namespace tint::wgsl::ir
diff --git a/src/tint/lang/wgsl/ir/builtin_call.h b/src/tint/lang/wgsl/ir/builtin_call.h
index 043129e..7711081 100644
--- a/src/tint/lang/wgsl/ir/builtin_call.h
+++ b/src/tint/lang/wgsl/ir/builtin_call.h
@@ -42,12 +42,15 @@
 class BuiltinCall : public Castable<BuiltinCall, core::ir::BuiltinCall> {
   public:
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param fn the builtin function
     /// @param args the conversion arguments
-    BuiltinCall(core::ir::InstructionResult* result,
+    BuiltinCall(Id id,
+                core::ir::InstructionResult* result,
                 BuiltinFn fn,
                 VectorRef<core::ir::Value*> args = tint::Empty);
+
     ~BuiltinCall() override;
 
     /// @copydoc core::ir::Instruction::Clone()
diff --git a/src/tint/lang/wgsl/ir/unary.cc b/src/tint/lang/wgsl/ir/unary.cc
index 7d98bef..ab14e91 100644
--- a/src/tint/lang/wgsl/ir/unary.cc
+++ b/src/tint/lang/wgsl/ir/unary.cc
@@ -35,17 +35,18 @@
 
 namespace tint::wgsl::ir {
 
-Unary::Unary() = default;
+Unary::Unary(Id id) : Base(id) {}
 
-Unary::Unary(core::ir::InstructionResult* result, core::UnaryOp op, core::ir::Value* val)
-    : Base(result, op, val) {}
+Unary::Unary(Id id, core::ir::InstructionResult* result, core::UnaryOp op, core::ir::Value* val)
+    : Base(id, result, op, val) {}
 
 Unary::~Unary() = default;
 
 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>(new_result, Op(), val);
+    return ctx.ir.allocators.instructions.Create<Unary>(ctx.ir.NextInstructionId(), new_result,
+                                                        Op(), val);
 }
 
 const core::intrinsic::TableData& Unary::TableData() const {
diff --git a/src/tint/lang/wgsl/ir/unary.h b/src/tint/lang/wgsl/ir/unary.h
index 9fbeeea..ff4cf58 100644
--- a/src/tint/lang/wgsl/ir/unary.h
+++ b/src/tint/lang/wgsl/ir/unary.h
@@ -39,13 +39,16 @@
     static constexpr size_t kValueOperandOffset = 0;
 
     /// Constructor (no results, no operands)
-    Unary();
+    /// @param id the instruction id
+    explicit Unary(Id id);
 
     /// Constructor
+    /// @param id the instruction id
     /// @param result the result value
     /// @param op the unary operator
     /// @param val the input value for the instruction
-    Unary(core::ir::InstructionResult* result, core::UnaryOp op, core::ir::Value* val);
+    Unary(Id id, core::ir::InstructionResult* result, core::UnaryOp op, core::ir::Value* val);
+
     ~Unary() override;
 
     /// @copydoc core::ir::Instruction::Clone()
diff --git a/src/tint/lang/wgsl/reader/lower/lower_test.cc b/src/tint/lang/wgsl/reader/lower/lower_test.cc
index 4a0c91c..2486d17 100644
--- a/src/tint/lang/wgsl/reader/lower/lower_test.cc
+++ b/src/tint/lang/wgsl/reader/lower/lower_test.cc
@@ -45,12 +45,12 @@
     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>(result,
-                                                                            wgsl::BuiltinFn::kMax,
-                                                                            Vector{
-                                                                                b.Value(i32(1)),
-                                                                                b.Value(i32(2)),
-                                                                            }));
+        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.Return(f);
     });
 
@@ -86,7 +86,8 @@
     b.Append(f->Block(), [&] {  //
         auto* result = b.InstructionResult(ty.i32());
         b.Append(b.ir.allocators.instructions.Create<wgsl::ir::BuiltinCall>(
-            result, wgsl::BuiltinFn::kWorkgroupUniformLoad, Vector{wgvar->Result(0)}));
+            b.ir.NextInstructionId(), 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 8622949..129bba3 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
@@ -979,7 +979,8 @@
                         auto* res = impl.builder_.InstructionResult(ty);
                         inst =
                             impl.builder_.ir.allocators.instructions.Create<wgsl::ir::BuiltinCall>(
-                                res, b->Fn(), std::move(args));
+                                impl.builder_.ir.NextInstructionId(), 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 d6765c6..6a7b9ee 100644
--- a/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
+++ b/src/tint/lang/wgsl/writer/raise/ptr_to_ref.cc
@@ -101,7 +101,8 @@
         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>(as_ptr, core::UnaryOp::kAddressOf, operand)
+                .Create<wgsl::ir::Unary>(mod.NextInstructionId(), as_ptr, core::UnaryOp::kAddressOf,
+                                         operand)
                 ->InsertBefore(use.instruction);
             use.instruction->SetOperand(use.operand_index, as_ptr);
         }
@@ -117,7 +118,8 @@
         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>(as_ptr, core::UnaryOp::kIndirection, operand)
+                .Create<wgsl::ir::Unary>(mod.NextInstructionId(), as_ptr,
+                                         core::UnaryOp::kIndirection, operand)
                 ->InsertBefore(use.instruction);
             use.instruction->SetOperand(use.operand_index, as_ptr);
         }