Преглед изворни кода

Refactor AddInst-family functions to their own file (#4941)

This in particular uses free functions because it's likely to end up
more consistent with types (versus a wrapper object for InstStore).
Note, this is unlikely to have a performance impact, but if it does, we
can look into related approaches (and we've already discussed using
LTO).

Renames `PendingBlock::AddInst` to `PendingBlock::Add` because
`MakeElementAccessInst` expects the matching name to exist.
Jon Ross-Perkins пре 1 година
родитељ
комит
311b4ff03d
44 измењених фајлова са 875 додато и 695 уклоњено
  1. 2 0
      toolchain/check/BUILD
  2. 13 12
      toolchain/check/call.cpp
  3. 16 12
      toolchain/check/check_unit.cpp
  4. 0 116
      toolchain/check/context.cpp
  5. 2 134
      toolchain/check/context.h
  6. 7 6
      toolchain/check/control_flow.cpp
  7. 102 85
      toolchain/check/convert.cpp
  8. 2 1
      toolchain/check/global_init.cpp
  9. 5 4
      toolchain/check/handle_alias.cpp
  10. 5 4
      toolchain/check/handle_array.cpp
  11. 34 25
      toolchain/check/handle_binding_pattern.cpp
  12. 24 20
      toolchain/check/handle_class.cpp
  13. 6 4
      toolchain/check/handle_export.cpp
  14. 11 10
      toolchain/check/handle_function.cpp
  15. 2 1
      toolchain/check/handle_if_statement.cpp
  16. 7 6
      toolchain/check/handle_impl.cpp
  17. 9 6
      toolchain/check/handle_index.cpp
  18. 7 6
      toolchain/check/handle_interface.cpp
  19. 11 9
      toolchain/check/handle_let_and_var.cpp
  20. 12 10
      toolchain/check/handle_literal.cpp
  21. 6 5
      toolchain/check/handle_loop_statement.cpp
  22. 15 11
      toolchain/check/handle_name.cpp
  23. 4 3
      toolchain/check/handle_namespace.cpp
  24. 29 23
      toolchain/check/handle_operator.cpp
  25. 5 4
      toolchain/check/handle_struct.cpp
  26. 3 2
      toolchain/check/handle_tuple_literal.cpp
  27. 17 16
      toolchain/check/handle_where.cpp
  28. 2 2
      toolchain/check/impl.cpp
  29. 11 9
      toolchain/check/import.cpp
  30. 3 2
      toolchain/check/import_cpp.cpp
  31. 117 61
      toolchain/check/import_ref.cpp
  32. 24 0
      toolchain/check/import_ref.h
  33. 136 0
      toolchain/check/inst.cpp
  34. 120 0
      toolchain/check/inst.h
  35. 3 2
      toolchain/check/interface.cpp
  36. 5 4
      toolchain/check/literal.cpp
  37. 39 33
      toolchain/check/member_access.cpp
  38. 25 23
      toolchain/check/pattern_match.cpp
  39. 7 6
      toolchain/check/pending_block.h
  40. 3 2
      toolchain/check/pointer_dereference.cpp
  41. 7 5
      toolchain/check/return.cpp
  42. 5 2
      toolchain/check/subpattern.cpp
  43. 8 5
      toolchain/check/type_completion.cpp
  44. 4 4
      toolchain/docs/adding_features.md

+ 2 - 0
toolchain/check/BUILD

@@ -29,6 +29,7 @@ cc_library(
         "import.cpp",
         "import_cpp.cpp",
         "import_ref.cpp",
+        "inst.cpp",
         "inst_block_stack.cpp",
         "interface.cpp",
         "literal.cpp",
@@ -62,6 +63,7 @@ cc_library(
         "import.h",
         "import_cpp.h",
         "import_ref.h",
+        "inst.h",
         "inst_block_stack.h",
         "interface.h",
         "keyword_modifier_set.h",

+ 13 - 12
toolchain/check/call.cpp

@@ -98,10 +98,11 @@ static auto PerformCallToGenericClass(Context& context, SemIR::LocId loc_id,
   if (!callee_specific_id) {
     return SemIR::ErrorInst::SingletonInstId;
   }
-  return context.GetOrAddInst<SemIR::ClassType>(
-      loc_id, {.type_id = SemIR::TypeType::SingletonTypeId,
-               .class_id = class_id,
-               .specific_id = *callee_specific_id});
+  return GetOrAddInst<SemIR::ClassType>(
+      context, loc_id,
+      {.type_id = SemIR::TypeType::SingletonTypeId,
+       .class_id = class_id,
+       .specific_id = *callee_specific_id});
 }
 
 // Performs a call where the callee is the name of a generic interface, such as
@@ -119,8 +120,8 @@ static auto PerformCallToGenericInterface(
   if (!callee_specific_id) {
     return SemIR::ErrorInst::SingletonInstId;
   }
-  return context.GetOrAddInst(
-      loc_id,
+  return GetOrAddInst(
+      context, loc_id,
       FacetTypeFromInterface(context, interface_id, *callee_specific_id));
 }
 
@@ -163,8 +164,8 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id,
     return SemIR::ErrorInst::SingletonInstId;
   }
   if (callee_specific_id->has_value()) {
-    callee_id = context.GetOrAddInst(
-        context.insts().GetLocId(callee_id),
+    callee_id = GetOrAddInst(
+        context, context.insts().GetLocId(callee_id),
         SemIR::SpecificFunction{
             .type_id = GetSingletonType(
                 context, SemIR::SpecificFunctionType::SingletonInstId),
@@ -196,8 +197,8 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id,
     case SemIR::InitRepr::InPlace:
       // Tentatively put storage for a temporary in the function's return slot.
       // This will be replaced if necessary when we perform initialization.
-      return_slot_arg_id = context.AddInst<SemIR::TemporaryStorage>(
-          loc_id, {.type_id = return_info.type_id});
+      return_slot_arg_id = AddInst<SemIR::TemporaryStorage>(
+          context, loc_id, {.type_id = return_info.type_id});
       break;
     case SemIR::InitRepr::None:
       // For functions with an implicit return type, the return type is the
@@ -220,8 +221,8 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id,
       context, loc_id, callee_function.self_id, arg_ids, return_slot_arg_id,
       context.functions().Get(callee_function.function_id),
       *callee_specific_id);
-  auto call_inst_id =
-      context.GetOrAddInst<SemIR::Call>(loc_id, {.type_id = return_info.type_id,
+  auto call_inst_id = GetOrAddInst<SemIR::Call>(context, loc_id,
+                                                {.type_id = return_info.type_id,
                                                  .callee_id = callee_id,
                                                  .args_id = converted_args_id});
 

+ 16 - 12
toolchain/check/check_unit.cpp

@@ -17,6 +17,7 @@
 #include "toolchain/check/import.h"
 #include "toolchain/check/import_cpp.h"
 #include "toolchain/check/import_ref.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/node_id_traversal.h"
 #include "toolchain/check/type.h"
 
@@ -102,18 +103,19 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
       SemIR::NameScopeId::None);
   CARBON_CHECK(package_scope_id == SemIR::NameScopeId::Package);
 
-  auto package_inst_id = context_.AddInst<SemIR::Namespace>(
-      Parse::NodeId::None, {.type_id = namespace_type_id,
-                            .name_scope_id = SemIR::NameScopeId::Package,
-                            .import_id = SemIR::InstId::None});
+  auto package_inst_id =
+      AddInst<SemIR::Namespace>(context_, Parse::NodeId::None,
+                                {.type_id = namespace_type_id,
+                                 .name_scope_id = SemIR::NameScopeId::Package,
+                                 .import_id = SemIR::InstId::None});
   CARBON_CHECK(package_inst_id == SemIR::Namespace::PackageInstId);
 
   // If there is an implicit `api` import, set it first so that it uses the
   // ImportIRId::ApiForImpl when processed for imports.
   if (unit_and_imports_->api_for_impl) {
     const auto& names = context_.parse_tree().packaging_decl()->names;
-    auto import_decl_id = context_.AddInst<SemIR::ImportDecl>(
-        names.node_id,
+    auto import_decl_id = AddInst<SemIR::ImportDecl>(
+        context_, names.node_id,
         {.package_id = SemIR::NameId::ForPackageName(names.package_id)});
     SetApiImportIR(context_,
                    {.decl_id = import_decl_id,
@@ -128,9 +130,10 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
   // are handled separately.
   for (auto& package_imports : unit_and_imports_->package_imports) {
     CARBON_CHECK(!package_imports.import_decl_id.has_value());
-    package_imports.import_decl_id = context_.AddInst<SemIR::ImportDecl>(
-        package_imports.node_id, {.package_id = SemIR::NameId::ForPackageName(
-                                      package_imports.package_id)});
+    package_imports.import_decl_id = AddInst<SemIR::ImportDecl>(
+        context_, package_imports.node_id,
+        {.package_id =
+             SemIR::NameId::ForPackageName(package_imports.package_id)});
   }
   // Process the imports.
   if (unit_and_imports_->api_for_impl) {
@@ -327,9 +330,10 @@ auto CheckUnit::ImportOtherPackages(SemIR::TypeId namespace_type_id) -> void {
             {.ir_id = SemIR::ImportIRId::ApiForImpl,
              .inst_id = api_imports->import_decl_id});
         import_decl_id =
-            context_.AddInst(context_.MakeImportedLocAndInst<SemIR::ImportDecl>(
-                import_ir_inst_id, {.package_id = SemIR::NameId::ForPackageName(
-                                        api_imports_entry.first)}));
+            AddInst(context_, MakeImportedLocIdAndInst<SemIR::ImportDecl>(
+                                  context_, import_ir_inst_id,
+                                  {.package_id = SemIR::NameId::ForPackageName(
+                                       api_imports_entry.first)}));
         package_id = api_imports_entry.first;
       }
       has_load_error |= api_imports->has_load_error;

+ 0 - 116
toolchain/check/context.cpp

@@ -98,122 +98,6 @@ auto Context::VerifyOnFinish() -> void {
   // TODO: Add verification for generic_region_stack_.
 }
 
-auto Context::GetOrAddInst(SemIR::LocIdAndInst loc_id_and_inst)
-    -> SemIR::InstId {
-  if (loc_id_and_inst.loc_id.is_implicit()) {
-    auto const_id =
-        TryEvalInst(*this, SemIR::InstId::None, loc_id_and_inst.inst);
-    if (const_id.has_value()) {
-      CARBON_VLOG("GetOrAddInst: constant: {0}\n", loc_id_and_inst.inst);
-      return constant_values().GetInstId(const_id);
-    }
-  }
-  // TODO: For an implicit instruction, this reattempts evaluation.
-  return AddInst(loc_id_and_inst);
-}
-
-// Finish producing an instruction. Set its constant value, and register it in
-// any applicable instruction lists.
-auto Context::FinishInst(SemIR::InstId inst_id, SemIR::Inst inst) -> void {
-  GenericRegionStack::DependencyKind dep_kind =
-      GenericRegionStack::DependencyKind::None;
-
-  // If the instruction has a symbolic constant type, track that we need to
-  // substitute into it.
-  if (constant_values().DependsOnGenericParameter(
-          types().GetConstantId(inst.type_id()))) {
-    dep_kind |= GenericRegionStack::DependencyKind::SymbolicType;
-  }
-
-  // If the instruction has a constant value, compute it.
-  auto const_id = TryEvalInst(*this, inst_id, inst);
-  constant_values().Set(inst_id, const_id);
-  if (const_id.is_constant()) {
-    CARBON_VLOG("Constant: {0} -> {1}\n", inst,
-                constant_values().GetInstId(const_id));
-
-    // If the constant value is symbolic, track that we need to substitute into
-    // it.
-    if (constant_values().DependsOnGenericParameter(const_id)) {
-      dep_kind |= GenericRegionStack::DependencyKind::SymbolicConstant;
-    }
-  }
-
-  // Keep track of dependent instructions.
-  if (dep_kind != GenericRegionStack::DependencyKind::None) {
-    // TODO: Also check for template-dependent instructions.
-    generic_region_stack().AddDependentInst(
-        {.inst_id = inst_id, .kind = dep_kind});
-  }
-}
-
-// Returns whether a parse node associated with an imported instruction of kind
-// `imported_kind` is usable as the location of a corresponding local
-// instruction of kind `local_kind`.
-static auto HasCompatibleImportedNodeKind(SemIR::InstKind imported_kind,
-                                          SemIR::InstKind local_kind) -> bool {
-  if (imported_kind == local_kind) {
-    return true;
-  }
-  if (imported_kind == SemIR::ImportDecl::Kind &&
-      local_kind == SemIR::Namespace::Kind) {
-    static_assert(
-        std::is_convertible_v<decltype(SemIR::ImportDecl::Kind)::TypedNodeId,
-                              decltype(SemIR::Namespace::Kind)::TypedNodeId>);
-    return true;
-  }
-  return false;
-}
-
-auto Context::CheckCompatibleImportedNodeKind(
-    SemIR::ImportIRInstId imported_loc_id, SemIR::InstKind kind) -> void {
-  auto& import_ir_inst = import_ir_insts().Get(imported_loc_id);
-  const auto* import_ir = import_irs().Get(import_ir_inst.ir_id).sem_ir;
-  auto imported_kind = import_ir->insts().Get(import_ir_inst.inst_id).kind();
-  CARBON_CHECK(
-      HasCompatibleImportedNodeKind(imported_kind, kind),
-      "Node of kind {0} created with location of imported node of kind {1}",
-      kind, imported_kind);
-}
-
-auto Context::AddPlaceholderInstInNoBlock(SemIR::LocIdAndInst loc_id_and_inst)
-    -> SemIR::InstId {
-  auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
-  CARBON_VLOG("AddPlaceholderInst: {0}\n", loc_id_and_inst.inst);
-  constant_values().Set(inst_id, SemIR::ConstantId::None);
-  return inst_id;
-}
-
-auto Context::AddPlaceholderInst(SemIR::LocIdAndInst loc_id_and_inst)
-    -> SemIR::InstId {
-  auto inst_id = AddPlaceholderInstInNoBlock(loc_id_and_inst);
-  inst_block_stack_.AddInstId(inst_id);
-  return inst_id;
-}
-
-auto Context::ReplaceLocIdAndInstBeforeConstantUse(
-    SemIR::InstId inst_id, SemIR::LocIdAndInst loc_id_and_inst) -> void {
-  sem_ir().insts().SetLocIdAndInst(inst_id, loc_id_and_inst);
-  CARBON_VLOG("ReplaceInst: {0} -> {1}\n", inst_id, loc_id_and_inst.inst);
-  FinishInst(inst_id, loc_id_and_inst.inst);
-}
-
-auto Context::ReplaceInstBeforeConstantUse(SemIR::InstId inst_id,
-                                           SemIR::Inst inst) -> void {
-  sem_ir().insts().Set(inst_id, inst);
-  CARBON_VLOG("ReplaceInst: {0} -> {1}\n", inst_id, inst);
-  FinishInst(inst_id, inst);
-}
-
-auto Context::ReplaceInstPreservingConstantValue(SemIR::InstId inst_id,
-                                                 SemIR::Inst inst) -> void {
-  auto old_const_id = sem_ir().constant_values().Get(inst_id);
-  sem_ir().insts().Set(inst_id, inst);
-  CARBON_VLOG("ReplaceInst: {0} -> {1}\n", inst_id, inst);
-  auto new_const_id = TryEvalInst(*this, inst_id, inst);
-  CARBON_CHECK(old_const_id == new_const_id);
-}
-
 auto Context::Finalize() -> void {
   // Pop information for the file-level scope.
   sem_ir().set_top_inst_block_id(inst_block_stack().Pop());

+ 2 - 134
toolchain/check/context.h

@@ -55,129 +55,6 @@ class Context {
   // Runs verification that the processing cleanly finished.
   auto VerifyOnFinish() -> void;
 
-  // Adds an instruction to the current block, returning the produced ID.
-  auto AddInst(SemIR::LocIdAndInst loc_id_and_inst) -> SemIR::InstId {
-    auto inst_id = AddInstInNoBlock(loc_id_and_inst);
-    inst_block_stack_.AddInstId(inst_id);
-    return inst_id;
-  }
-
-  // Convenience for AddInst with typed nodes.
-  template <typename InstT, typename LocT>
-  auto AddInst(LocT loc, InstT inst)
-      -> decltype(AddInst(SemIR::LocIdAndInst(loc, inst))) {
-    return AddInst(SemIR::LocIdAndInst(loc, inst));
-  }
-
-  // Returns a LocIdAndInst for an instruction with an imported location. Checks
-  // that the imported location is compatible with the kind of instruction being
-  // created.
-  template <typename InstT>
-    requires SemIR::Internal::HasNodeId<InstT>
-  auto MakeImportedLocAndInst(SemIR::ImportIRInstId imported_loc_id, InstT inst)
-      -> SemIR::LocIdAndInst {
-    if constexpr (!SemIR::Internal::HasUntypedNodeId<InstT>) {
-      CheckCompatibleImportedNodeKind(imported_loc_id, InstT::Kind);
-    }
-    return SemIR::LocIdAndInst::UncheckedLoc(imported_loc_id, inst);
-  }
-
-  // Adds an instruction in no block, returning the produced ID. Should be used
-  // rarely.
-  auto AddInstInNoBlock(SemIR::LocIdAndInst loc_id_and_inst) -> SemIR::InstId {
-    auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
-    CARBON_VLOG("AddInst: {0}\n", loc_id_and_inst.inst);
-    FinishInst(inst_id, loc_id_and_inst.inst);
-    return inst_id;
-  }
-
-  // Convenience for AddInstInNoBlock with typed nodes.
-  template <typename InstT, typename LocT>
-  auto AddInstInNoBlock(LocT loc, InstT inst)
-      -> decltype(AddInstInNoBlock(SemIR::LocIdAndInst(loc, inst))) {
-    return AddInstInNoBlock(SemIR::LocIdAndInst(loc, inst));
-  }
-
-  // If the instruction has an implicit location and a constant value, returns
-  // the constant value's instruction ID. Otherwise, same as AddInst.
-  auto GetOrAddInst(SemIR::LocIdAndInst loc_id_and_inst) -> SemIR::InstId;
-
-  // Convenience for GetOrAddInst with typed nodes.
-  template <typename InstT, typename LocT>
-  auto GetOrAddInst(LocT loc, InstT inst)
-      -> decltype(GetOrAddInst(SemIR::LocIdAndInst(loc, inst))) {
-    return GetOrAddInst(SemIR::LocIdAndInst(loc, inst));
-  }
-
-  // Adds an instruction to the current block, returning the produced ID. The
-  // instruction is a placeholder that is expected to be replaced by
-  // `ReplaceInstBeforeConstantUse`.
-  auto AddPlaceholderInst(SemIR::LocIdAndInst loc_id_and_inst) -> SemIR::InstId;
-
-  // Adds an instruction in no block, returning the produced ID. Should be used
-  // rarely. The instruction is a placeholder that is expected to be replaced by
-  // `ReplaceInstBeforeConstantUse`.
-  auto AddPlaceholderInstInNoBlock(SemIR::LocIdAndInst loc_id_and_inst)
-      -> SemIR::InstId;
-
-  // Adds an instruction to the current pattern block, returning the produced
-  // ID.
-  // TODO: Is it possible to remove this and pattern_block_stack, now that
-  // we have BeginSubpattern etc. instead?
-  auto AddPatternInst(SemIR::LocIdAndInst loc_id_and_inst) -> SemIR::InstId {
-    auto inst_id = AddInstInNoBlock(loc_id_and_inst);
-    pattern_block_stack_.AddInstId(inst_id);
-    return inst_id;
-  }
-
-  // Convenience for AddPatternInst with typed nodes.
-  template <typename InstT>
-    requires(SemIR::Internal::HasNodeId<InstT>)
-  auto AddPatternInst(decltype(InstT::Kind)::TypedNodeId node_id, InstT inst)
-      -> SemIR::InstId {
-    return AddPatternInst(SemIR::LocIdAndInst(node_id, inst));
-  }
-
-  // Pushes a parse tree node onto the stack, storing the SemIR::Inst as the
-  // result.
-  template <typename InstT>
-    requires(SemIR::Internal::HasNodeId<InstT>)
-  auto AddInstAndPush(decltype(InstT::Kind)::TypedNodeId node_id, InstT inst)
-      -> void {
-    node_stack_.Push(node_id, AddInst(node_id, inst));
-  }
-
-  // Replaces the instruction at `inst_id` with `loc_id_and_inst`. The
-  // instruction is required to not have been used in any constant evaluation,
-  // either because it's newly created and entirely unused, or because it's only
-  // used in a position that constant evaluation ignores, such as a return slot.
-  auto ReplaceLocIdAndInstBeforeConstantUse(SemIR::InstId inst_id,
-                                            SemIR::LocIdAndInst loc_id_and_inst)
-      -> void;
-
-  // Replaces the instruction at `inst_id` with `inst`, not affecting location.
-  // The instruction is required to not have been used in any constant
-  // evaluation, either because it's newly created and entirely unused, or
-  // because it's only used in a position that constant evaluation ignores, such
-  // as a return slot.
-  auto ReplaceInstBeforeConstantUse(SemIR::InstId inst_id, SemIR::Inst inst)
-      -> void;
-
-  // Replaces the instruction at `inst_id` with `inst`, not affecting location.
-  // The instruction is required to not change its constant value.
-  auto ReplaceInstPreservingConstantValue(SemIR::InstId inst_id,
-                                          SemIR::Inst inst) -> void;
-
-  // Sets only the parse node of an instruction. This is only used when setting
-  // the parse node of an imported namespace. Versus
-  // ReplaceInstBeforeConstantUse, it is safe to use after the namespace is used
-  // in constant evaluation. It's exposed this way mainly so that `insts()` can
-  // remain const.
-  auto SetNamespaceNodeId(SemIR::InstId inst_id, Parse::NodeId node_id)
-      -> void {
-    sem_ir().insts().SetLocId(inst_id, SemIR::LocId(node_id));
-  }
-
   // Adds an exported name.
   auto AddExport(SemIR::InstId inst_id) -> void { exports_.push_back(inst_id); }
 
@@ -313,8 +190,8 @@ class Context {
   auto type_blocks() -> SemIR::BlockValueStore<SemIR::TypeBlockId>& {
     return sem_ir().type_blocks();
   }
-  // Instructions should be added with `AddInst` or `AddInstInNoBlock`. This is
-  // `const` to prevent accidental misuse.
+  // Instructions should be added with `AddInst` or `AddInstInNoBlock` from
+  // `inst.h`. This is `const` to prevent accidental misuse.
   auto insts() -> const SemIR::InstStore& { return sem_ir().insts(); }
   auto constant_values() -> SemIR::ConstantValueStore& {
     return sem_ir().constant_values();
@@ -373,15 +250,6 @@ class Context {
     SemIR::TypeId type_id_;
   };
 
-  // Checks that the provided imported location has a node kind that is
-  // compatible with that of the given instruction.
-  auto CheckCompatibleImportedNodeKind(SemIR::ImportIRInstId imported_loc_id,
-                                       SemIR::InstKind kind) -> void;
-
-  // Finish producing an instruction. Set its constant value, and register it in
-  // any applicable instruction lists.
-  auto FinishInst(SemIR::InstId inst_id, SemIR::Inst inst) -> void;
-
   // Handles diagnostics.
   DiagnosticEmitter* emitter_;
 

+ 7 - 6
toolchain/check/control_flow.cpp

@@ -4,6 +4,7 @@
 
 #include "toolchain/check/control_flow.h"
 
+#include "toolchain/check/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -16,7 +17,7 @@ static auto AddDominatedBlockAndBranchImpl(Context& context,
     return SemIR::InstBlockId::Unreachable;
   }
   auto block_id = context.inst_blocks().AddDefaultValue();
-  context.AddInst<BranchNode>(node_id, {block_id, args...});
+  AddInst<BranchNode>(context, node_id, {block_id, args...});
   return block_id;
 }
 
@@ -49,7 +50,7 @@ auto AddConvergenceBlockAndPush(Context& context, Parse::NodeId node_id,
         new_block_id = context.inst_blocks().AddDefaultValue();
       }
       CARBON_CHECK(node_id.has_value());
-      context.AddInst<SemIR::Branch>(node_id, {.target_id = new_block_id});
+      AddInst<SemIR::Branch>(context, node_id, {.target_id = new_block_id});
     }
     context.inst_block_stack().Pop();
   }
@@ -68,8 +69,8 @@ auto AddConvergenceBlockWithArgAndPush(
       if (new_block_id == SemIR::InstBlockId::Unreachable) {
         new_block_id = context.inst_blocks().AddDefaultValue();
       }
-      context.AddInst<SemIR::BranchWithArg>(
-          node_id, {.target_id = new_block_id, .arg_id = arg_id});
+      AddInst<SemIR::BranchWithArg>(
+          context, node_id, {.target_id = new_block_id, .arg_id = arg_id});
     }
     context.inst_block_stack().Pop();
   }
@@ -79,8 +80,8 @@ auto AddConvergenceBlockWithArgAndPush(
   // Acquire the result value.
   SemIR::TypeId result_type_id =
       context.insts().Get(*block_args.begin()).type_id();
-  return context.AddInst<SemIR::BlockArg>(
-      node_id, {.type_id = result_type_id, .block_id = new_block_id});
+  return AddInst<SemIR::BlockArg>(
+      context, node_id, {.type_id = result_type_id, .block_id = new_block_id});
 }
 
 auto SetBlockArgResultBeforeConstantUse(Context& context,

+ 102 - 85
toolchain/check/convert.cpp

@@ -14,6 +14,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/impl_lookup.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/operator.h"
 #include "toolchain/check/pattern_match.h"
 #include "toolchain/check/type.h"
@@ -114,10 +115,10 @@ static auto FinalizeTemporary(Context& context, SemIR::InstId init_id,
                  "initialized multiple times? Have {0}",
                  sem_ir.insts().Get(return_slot_arg_id));
     auto init = sem_ir.insts().Get(init_id);
-    return context.AddInst<SemIR::Temporary>(sem_ir.insts().GetLocId(init_id),
-                                             {.type_id = init.type_id(),
-                                              .storage_id = return_slot_arg_id,
-                                              .init_id = init_id});
+    return AddInst<SemIR::Temporary>(context, sem_ir.insts().GetLocId(init_id),
+                                     {.type_id = init.type_id(),
+                                      .storage_id = return_slot_arg_id,
+                                      .init_id = init_id});
   }
 
   if (discarded) {
@@ -131,11 +132,12 @@ static auto FinalizeTemporary(Context& context, SemIR::InstId init_id,
   // initialize a temporary, rather than two separate instructions.
   auto init = sem_ir.insts().Get(init_id);
   auto loc_id = sem_ir.insts().GetLocId(init_id);
-  auto temporary_id = context.AddInst<SemIR::TemporaryStorage>(
-      loc_id, {.type_id = init.type_id()});
-  return context.AddInst<SemIR::Temporary>(loc_id, {.type_id = init.type_id(),
-                                                    .storage_id = temporary_id,
-                                                    .init_id = init_id});
+  auto temporary_id = AddInst<SemIR::TemporaryStorage>(
+      context, loc_id, {.type_id = init.type_id()});
+  return AddInst<SemIR::Temporary>(context, loc_id,
+                                   {.type_id = init.type_id(),
+                                    .storage_id = temporary_id,
+                                    .init_id = init_id});
 }
 
 // Materialize a temporary to hold the result of the given expression if it is
@@ -149,12 +151,20 @@ static auto MaterializeIfInitializing(Context& context, SemIR::InstId expr_id)
   return expr_id;
 }
 
+// Helper to allow `MakeElementAccessInst` to call `AddInst` with either a
+// `PendingBlock` or `Context` (defined in `inst.h`).
+template <typename AccessInstT>
+static auto AddInst(PendingBlock& block, SemIR::LocId loc_id, AccessInstT inst)
+    -> SemIR::InstId {
+  return block.AddInst<AccessInstT>(loc_id, inst);
+}
+
 // Creates and adds an instruction to perform element access into an aggregate.
 template <typename AccessInstT, typename InstBlockT>
 static auto MakeElementAccessInst(Context& context, SemIR::LocId loc_id,
                                   SemIR::InstId aggregate_id,
                                   SemIR::TypeId elem_type_id, InstBlockT& block,
-                                  size_t i) {
+                                  size_t i) -> SemIR::InstId {
   if constexpr (std::is_same_v<AccessInstT, SemIR::ArrayIndex>) {
     // TODO: Add a new instruction kind for indexing an array at a constant
     // index so that we don't need an integer literal instruction here, and
@@ -163,11 +173,11 @@ static auto MakeElementAccessInst(Context& context, SemIR::LocId loc_id,
         loc_id, {.type_id = GetSingletonType(
                      context, SemIR::IntLiteralType::SingletonInstId),
                  .int_id = context.ints().Add(static_cast<int64_t>(i))});
-    return block.template AddInst<AccessInstT>(
-        loc_id, {elem_type_id, aggregate_id, index_id});
+    return AddInst<AccessInstT>(block, loc_id,
+                                {elem_type_id, aggregate_id, index_id});
   } else {
-    return block.template AddInst<AccessInstT>(
-        loc_id, {elem_type_id, aggregate_id, SemIR::ElementIndex(i)});
+    return AddInst<AccessInstT>(
+        block, loc_id, {elem_type_id, aggregate_id, SemIR::ElementIndex(i)});
   }
 }
 
@@ -191,7 +201,7 @@ static auto ConvertAggregateElement(
     llvm::ArrayRef<SemIR::InstId> src_literal_elems,
     ConversionTarget::Kind kind, SemIR::InstId target_id,
     SemIR::TypeId target_elem_type, PendingBlock* target_block,
-    size_t src_field_index, size_t target_field_index) {
+    size_t src_field_index, size_t target_field_index) -> SemIR::InstId {
   // Compute the location of the source element. This goes into the current code
   // block, not into the target block.
   // TODO: Ideally we would discard this instruction if it's unused.
@@ -303,10 +313,10 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
   // Flush the temporary here if we didn't insert it earlier, so we can add a
   // reference to the return slot.
   target_block->InsertHere();
-  return context.AddInst<SemIR::ArrayInit>(
-      value_loc_id, {.type_id = target.type_id,
-                     .inits_id = sem_ir.inst_blocks().Add(inits),
-                     .dest_id = return_slot_arg_id});
+  return AddInst<SemIR::ArrayInit>(context, value_loc_id,
+                                   {.type_id = target.type_id,
+                                    .inits_id = sem_ir.inst_blocks().Add(inits),
+                                    .dest_id = return_slot_arg_id});
 }
 
 // Performs a conversion from a tuple to a tuple type. This function only
@@ -383,13 +393,13 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
 
   if (is_init) {
     target.init_block->InsertHere();
-    return context.AddInst<SemIR::TupleInit>(value_loc_id,
-                                             {.type_id = target.type_id,
-                                              .elements_id = new_block.id(),
-                                              .dest_id = target.init_id});
+    return AddInst<SemIR::TupleInit>(context, value_loc_id,
+                                     {.type_id = target.type_id,
+                                      .elements_id = new_block.id(),
+                                      .dest_id = target.init_id});
   } else {
-    return context.AddInst<SemIR::TupleValue>(
-        value_loc_id,
+    return AddInst<SemIR::TupleValue>(
+        context, value_loc_id,
         {.type_id = target.type_id, .elements_id = new_block.id()});
   }
 }
@@ -478,16 +488,18 @@ static auto ConvertStructToStructOrClass(Context& context,
   for (auto [i, dest_field] : llvm::enumerate(dest_elem_fields)) {
     if (dest_field.name_id == SemIR::NameId::Vptr) {
       // CARBON_CHECK(ToClass, "Only classes should have vptrs.");
-      auto dest_id = context.AddInst<SemIR::ClassElementAccess>(
-          value_loc_id, {.type_id = dest_field.type_id,
-                         .base_id = target.init_id,
-                         .index = SemIR::ElementIndex(i)});
-      auto vtable_ptr_id = context.AddInst<SemIR::VtablePtr>(
-          value_loc_id, {.type_id = dest_field.type_id});
-      auto init_id = context.AddInst<SemIR::InitializeFrom>(
-          value_loc_id, {.type_id = dest_field.type_id,
-                         .src_id = vtable_ptr_id,
-                         .dest_id = dest_id});
+      auto dest_id =
+          AddInst<SemIR::ClassElementAccess>(context, value_loc_id,
+                                             {.type_id = dest_field.type_id,
+                                              .base_id = target.init_id,
+                                              .index = SemIR::ElementIndex(i)});
+      auto vtable_ptr_id = AddInst<SemIR::VtablePtr>(
+          context, value_loc_id, {.type_id = dest_field.type_id});
+      auto init_id =
+          AddInst<SemIR::InitializeFrom>(context, value_loc_id,
+                                         {.type_id = dest_field.type_id,
+                                          .src_id = vtable_ptr_id,
+                                          .dest_id = dest_id});
       new_block.Set(i, init_id);
       continue;
     }
@@ -536,19 +548,19 @@ static auto ConvertStructToStructOrClass(Context& context,
     target.init_block->InsertHere();
     CARBON_CHECK(is_init,
                  "Converting directly to a class value is not supported");
-    return context.AddInst<SemIR::ClassInit>(value_loc_id,
-                                             {.type_id = target.type_id,
-                                              .elements_id = new_block.id(),
-                                              .dest_id = target.init_id});
+    return AddInst<SemIR::ClassInit>(context, value_loc_id,
+                                     {.type_id = target.type_id,
+                                      .elements_id = new_block.id(),
+                                      .dest_id = target.init_id});
   } else if (is_init) {
     target.init_block->InsertHere();
-    return context.AddInst<SemIR::StructInit>(value_loc_id,
-                                              {.type_id = target.type_id,
-                                               .elements_id = new_block.id(),
-                                               .dest_id = target.init_id});
+    return AddInst<SemIR::StructInit>(context, value_loc_id,
+                                      {.type_id = target.type_id,
+                                       .elements_id = new_block.id(),
+                                       .dest_id = target.init_id});
   } else {
-    return context.AddInst<SemIR::StructValue>(
-        value_loc_id,
+    return AddInst<SemIR::StructValue>(
+        context, value_loc_id,
         {.type_id = target.type_id, .elements_id = new_block.id()});
   }
 }
@@ -597,10 +609,11 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
 
   if (need_temporary) {
     target_block.InsertHere();
-    result_id = context.AddInst<SemIR::Temporary>(
-        context.insts().GetLocId(value_id), {.type_id = target.type_id,
-                                             .storage_id = target.init_id,
-                                             .init_id = result_id});
+    result_id =
+        AddInst<SemIR::Temporary>(context, context.insts().GetLocId(value_id),
+                                  {.type_id = target.type_id,
+                                   .storage_id = target.init_id,
+                                   .init_id = result_id});
   }
   return result_id;
 }
@@ -657,10 +670,10 @@ static auto ConvertDerivedToBase(Context& context, SemIR::LocId loc_id,
   // Add a series of `.base` accesses.
   for (auto [base_id, base_type_id] : path) {
     auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(base_id);
-    value_id = context.AddInst<SemIR::ClassElementAccess>(
-        loc_id, {.type_id = base_type_id,
-                 .base_id = value_id,
-                 .index = base_decl.index});
+    value_id = AddInst<SemIR::ClassElementAccess>(context, loc_id,
+                                                  {.type_id = base_type_id,
+                                                   .base_id = value_id,
+                                                   .index = base_decl.index});
   }
   return value_id;
 }
@@ -672,15 +685,16 @@ static auto ConvertDerivedPointerToBasePointer(
     const InheritancePath& path) -> SemIR::InstId {
   // Form `*p`.
   ptr_id = ConvertToValueExpr(context, ptr_id);
-  auto ref_id = context.AddInst<SemIR::Deref>(
-      loc_id, {.type_id = src_ptr_type.pointee_id, .pointer_id = ptr_id});
+  auto ref_id = AddInst<SemIR::Deref>(
+      context, loc_id,
+      {.type_id = src_ptr_type.pointee_id, .pointer_id = ptr_id});
 
   // Convert as a reference expression.
   ref_id = ConvertDerivedToBase(context, loc_id, ref_id, path);
 
   // Take the address.
-  return context.AddInst<SemIR::AddrOf>(
-      loc_id, {.type_id = dest_ptr_type_id, .lvalue_id = ref_id});
+  return AddInst<SemIR::AddrOf>(
+      context, loc_id, {.type_id = dest_ptr_type_id, .lvalue_id = ref_id});
 }
 
 // Returns whether `category` is a valid expression category to produce as a
@@ -801,8 +815,8 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
     // value right out of it.
     if (value_cat == SemIR::ExprCategory::Initializing &&
         CanUseValueOfInitializer(sem_ir, value_type_id, target.kind)) {
-      return context.AddInst<SemIR::ValueOfInitializer>(
-          loc_id, {.type_id = value_type_id, .init_id = value_id});
+      return AddInst<SemIR::ValueOfInitializer>(
+          context, loc_id, {.type_id = value_type_id, .init_id = value_id});
     }
 
     // PerformBuiltinConversion converts each part of a tuple or struct, even
@@ -825,8 +839,9 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
         foundation_type_id != value_type_id &&
         (context.types().Is<SemIR::TupleType>(foundation_type_id) ||
          context.types().Is<SemIR::StructType>(foundation_type_id))) {
-      auto foundation_value_id = context.AddInst<SemIR::AsCompatible>(
-          loc_id, {.type_id = foundation_type_id, .source_id = value_id});
+      auto foundation_value_id = AddInst<SemIR::AsCompatible>(
+          context, loc_id,
+          {.type_id = foundation_type_id, .source_id = value_id});
 
       auto foundation_init_id = target.init_id;
       if (foundation_init_id != SemIR::InstId::None) {
@@ -858,8 +873,8 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
         }
       }
 
-      return context.AddInst<SemIR::AsCompatible>(
-          loc_id,
+      return AddInst<SemIR::AsCompatible>(
+          context, loc_id,
           {.type_id = target.type_id, .source_id = foundation_value_id});
     }
   }
@@ -880,8 +895,8 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
             ConversionTarget{.kind = ConversionTarget::Value,
                              .type_id = value_type_id});
       }
-      return context.AddInst<SemIR::AsCompatible>(
-          loc_id, {.type_id = target.type_id, .source_id = value_id});
+      return AddInst<SemIR::AsCompatible>(
+          context, loc_id, {.type_id = target.type_id, .source_id = value_id});
     }
   }
 
@@ -986,8 +1001,9 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
     // TODO: Support converting tuple and struct values to facet types,
     // combining the above conversions and this one in a single conversion.
     if (sem_ir.types().Is<SemIR::FacetType>(value_type_id)) {
-      return context.AddInst<SemIR::FacetAccessType>(
-          loc_id, {.type_id = target.type_id, .facet_value_inst_id = value_id});
+      return AddInst<SemIR::FacetAccessType>(
+          context, loc_id,
+          {.type_id = target.type_id, .facet_value_inst_id = value_id});
     }
   }
 
@@ -1040,12 +1056,13 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
           context.constant_values().Get(lookup_inst_id),
           context.types().GetConstantId(target.type_id));
       if (witness_inst_id != SemIR::InstId::None) {
-        return context.AddInst<SemIR::FacetValue>(
-            loc_id, {
-                        .type_id = target.type_id,
-                        .type_inst_id = lookup_inst_id,
-                        .witness_inst_id = witness_inst_id,
-                    });
+        return AddInst<SemIR::FacetValue>(
+            context, loc_id,
+            {
+                .type_id = target.type_id,
+                .type_inst_id = lookup_inst_id,
+                .witness_inst_id = witness_inst_id,
+            });
       }
     }
   }
@@ -1168,17 +1185,17 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
     // Pull a value directly out of the initializer if possible and wanted.
     if (expr_id != SemIR::ErrorInst::SingletonInstId &&
         CanUseValueOfInitializer(sem_ir, target.type_id, target.kind)) {
-      expr_id = context.AddInst<SemIR::ValueOfInitializer>(
-          loc_id, {.type_id = target.type_id, .init_id = expr_id});
+      expr_id = AddInst<SemIR::ValueOfInitializer>(
+          context, loc_id, {.type_id = target.type_id, .init_id = expr_id});
     }
   }
 
   // Track that we performed a type conversion, if we did so.
   if (orig_expr_id != expr_id) {
-    expr_id =
-        context.AddInst<SemIR::Converted>(loc_id, {.type_id = target.type_id,
-                                                   .original_id = orig_expr_id,
-                                                   .result_id = expr_id});
+    expr_id = AddInst<SemIR::Converted>(context, loc_id,
+                                        {.type_id = target.type_id,
+                                         .original_id = orig_expr_id,
+                                         .result_id = expr_id});
   }
 
   // For `as`, don't perform any value category conversions. In particular, an
@@ -1228,8 +1245,8 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
 
       // If we have a reference and don't want one, form a value binding.
       // TODO: Support types with custom value representations.
-      expr_id = context.AddInst<SemIR::BindValue>(
-          context.insts().GetLocId(expr_id),
+      expr_id = AddInst<SemIR::BindValue>(
+          context, context.insts().GetLocId(expr_id),
           {.type_id = target.type_id, .value_id = expr_id});
       // We now have a value expression.
       [[fallthrough]];
@@ -1247,10 +1264,10 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
     if (auto init_rep = SemIR::InitRepr::ForType(sem_ir, target.type_id);
         init_rep.kind == SemIR::InitRepr::ByCopy) {
       target.init_block->InsertHere();
-      expr_id = context.AddInst<SemIR::InitializeFrom>(
-          loc_id, {.type_id = target.type_id,
-                   .src_id = expr_id,
-                   .dest_id = target.init_id});
+      expr_id = AddInst<SemIR::InitializeFrom>(context, loc_id,
+                                               {.type_id = target.type_id,
+                                                .src_id = expr_id,
+                                                .dest_id = target.init_id});
     }
   }
 

+ 2 - 1
toolchain/check/global_init.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/global_init.h"
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/inst.h"
 
 namespace Carbon::Check {
 
@@ -29,7 +30,7 @@ auto GlobalInit::Finalize() -> void {
   }
 
   Resume();
-  context_->AddInst<SemIR::Return>(Parse::NodeId::None, {});
+  AddInst<SemIR::Return>(*context_, Parse::NodeId::None, {});
   // Pop the GlobalInit block here to finalize it.
   context_->inst_block_stack().Pop();
 

+ 5 - 4
toolchain/check/handle_alias.cpp

@@ -4,6 +4,7 @@
 
 #include "toolchain/check/context.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/parse/node_ids.h"
@@ -58,10 +59,10 @@ auto HandleParseNode(Context& context, Parse::AliasId /*node_id*/) -> bool {
     alias_type_id = SemIR::ErrorInst::SingletonTypeId;
     alias_value_id = SemIR::ErrorInst::SingletonInstId;
   }
-  auto alias_id = context.AddInst<SemIR::BindAlias>(
-      name_context.loc_id, {.type_id = alias_type_id,
-                            .entity_name_id = entity_name_id,
-                            .value_id = alias_value_id});
+  auto alias_id = AddInst<SemIR::BindAlias>(context, name_context.loc_id,
+                                            {.type_id = alias_type_id,
+                                             .entity_name_id = entity_name_id,
+                                             .value_id = alias_value_id});
 
   // Add the name of the binding to the current scope.
   context.decl_name_stack().PopScope();

+ 5 - 4
toolchain/check/handle_array.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/type.h"
 #include "toolchain/parse/node_kind.h"
 
@@ -53,10 +54,10 @@ auto HandleParseNode(Context& context, Parse::ArrayExprId node_id) -> bool {
   bound_inst_id = ConvertToValueOfType(
       context, context.insts().GetLocId(bound_inst_id), bound_inst_id,
       GetSingletonType(context, SemIR::IntLiteralType::SingletonInstId));
-  context.AddInstAndPush<SemIR::ArrayType>(
-      node_id, {.type_id = SemIR::TypeType::SingletonTypeId,
-                .bound_id = bound_inst_id,
-                .element_type_id = element_type_id});
+  AddInstAndPush<SemIR::ArrayType>(context, node_id,
+                                   {.type_id = SemIR::TypeType::SingletonTypeId,
+                                    .bound_id = bound_inst_id,
+                                    .element_type_id = element_type_id});
   return true;
 }
 

+ 34 - 25
toolchain/check/handle_binding_pattern.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/interface.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/return.h"
@@ -54,21 +55,24 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
                    : SemIR::CompileTimeBindIndex::None,
         is_template);
     if (is_generic) {
-      bind_id = context.AddInstInNoBlock(SemIR::LocIdAndInst(
-          name_node, SemIR::BindSymbolicName{.type_id = cast_type_id,
+      bind_id = AddInstInNoBlock(
+          context,
+          SemIR::LocIdAndInst(name_node, SemIR::BindSymbolicName{
+                                             .type_id = cast_type_id,
                                              .entity_name_id = entity_name_id,
                                              .value_id = SemIR::InstId::None}));
-      binding_pattern_id =
-          context.AddPatternInst<SemIR::SymbolicBindingPattern>(
-              name_node,
-              {.type_id = cast_type_id, .entity_name_id = entity_name_id});
+      binding_pattern_id = AddPatternInst<SemIR::SymbolicBindingPattern>(
+          context, name_node,
+          {.type_id = cast_type_id, .entity_name_id = entity_name_id});
     } else {
-      bind_id = context.AddInstInNoBlock(SemIR::LocIdAndInst(
-          name_node, SemIR::BindName{.type_id = cast_type_id,
-                                     .entity_name_id = entity_name_id,
-                                     .value_id = SemIR::InstId::None}));
-      binding_pattern_id = context.AddPatternInst<SemIR::BindingPattern>(
-          name_node,
+      bind_id = AddInstInNoBlock(
+          context,
+          SemIR::LocIdAndInst(
+              name_node, SemIR::BindName{.type_id = cast_type_id,
+                                         .entity_name_id = entity_name_id,
+                                         .value_id = SemIR::InstId::None}));
+      binding_pattern_id = AddPatternInst<SemIR::BindingPattern>(
+          context, name_node,
           {.type_id = cast_type_id, .entity_name_id = entity_name_id});
     }
 
@@ -126,10 +130,11 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
     auto& class_info = context.classes().Get(parent_class_decl->class_id);
     auto field_type_id =
         GetUnboundElementType(context, class_info.self_type_id, cast_type_id);
-    auto field_id = context.AddInst<SemIR::FieldDecl>(
-        binding_id, {.type_id = field_type_id,
-                     .name_id = name_id,
-                     .index = SemIR::ElementIndex::None});
+    auto field_id =
+        AddInst<SemIR::FieldDecl>(context, binding_id,
+                                  {.type_id = field_type_id,
+                                   .name_id = name_id,
+                                   .index = SemIR::ElementIndex::None});
     context.field_decls_stack().AppendToTop(field_id);
 
     context.node_stack().Push(node_id, field_id);
@@ -163,16 +168,19 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
         .type_id = cast_type_id,
         .assoc_const_id = SemIR::AssociatedConstantId::None,
         .decl_block_id = SemIR::InstBlockId::None};
-    auto decl_id = context.AddPlaceholderInstInNoBlock(SemIR::LocIdAndInst(
-        context.parse_tree().As<Parse::CompileTimeBindingPatternId>(node_id),
-        assoc_const_decl));
+    auto decl_id = AddPlaceholderInstInNoBlock(
+        context,
+        SemIR::LocIdAndInst(
+            context.parse_tree().As<Parse::CompileTimeBindingPatternId>(
+                node_id),
+            assoc_const_decl));
     assoc_const_decl.assoc_const_id = context.associated_constants().Add(
         {.name_id = name_id,
          .parent_scope_id = context.scope_stack().PeekNameScopeId(),
          .decl_id = decl_id,
          .generic_id = SemIR::GenericId::None,
          .default_value_id = SemIR::InstId::None});
-    context.ReplaceInstBeforeConstantUse(decl_id, assoc_const_decl);
+    ReplaceInstBeforeConstantUse(context, decl_id, assoc_const_decl);
 
     context.node_stack().Push(node_id, decl_id);
     return true;
@@ -228,8 +236,8 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
         param_pattern_id = SemIR::ErrorInst::SingletonInstId;
       } else {
         auto pattern_inst_id = make_binding_pattern();
-        param_pattern_id = context.AddPatternInst<SemIR::ValueParamPattern>(
-            node_id,
+        param_pattern_id = AddPatternInst<SemIR::ValueParamPattern>(
+            context, node_id,
             {
                 .type_id = context.insts().Get(pattern_inst_id).type_id(),
                 .subpattern_id = pattern_inst_id,
@@ -332,9 +340,10 @@ auto HandleParseNode(Context& context, Parse::AddrId node_id) -> bool {
     auto pointer_type = context.types().TryGetAs<SemIR::PointerType>(
         context.insts().Get(param_pattern_id).type_id());
     if (pointer_type) {
-      auto addr_pattern_id = context.AddPatternInst<SemIR::AddrPattern>(
-          node_id, {.type_id = SemIR::AutoType::SingletonTypeId,
-                    .inner_id = param_pattern_id});
+      auto addr_pattern_id = AddPatternInst<SemIR::AddrPattern>(
+          context, node_id,
+          {.type_id = SemIR::AutoType::SingletonTypeId,
+           .inner_id = param_pattern_id});
       context.node_stack().Push(node_id, addr_pattern_id);
     } else {
       CARBON_DIAGNOSTIC(

+ 24 - 20
toolchain/check/handle_class.cpp

@@ -12,6 +12,7 @@
 #include "toolchain/check/handle.h"
 #include "toolchain/check/import.h"
 #include "toolchain/check/import_ref.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
@@ -221,7 +222,7 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
                        .class_id = SemIR::ClassId::None,
                        .decl_block_id = decl_block_id};
   auto class_decl_id =
-      context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, class_decl));
+      AddPlaceholderInst(context, SemIR::LocIdAndInst(node_id, class_decl));
 
   // TODO: Store state regarding is_extern.
   SemIR::Class class_info = {
@@ -252,7 +253,7 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
   }
 
   // Write the class ID into the ClassDecl.
-  context.ReplaceInstBeforeConstantUse(class_decl_id, class_decl);
+  ReplaceInstBeforeConstantUse(context, class_decl_id, class_decl);
 
   if (is_new_class) {
     // Build the `Self` type using the resulting type constant.
@@ -416,8 +417,8 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
   }
 
   // Build a SemIR representation for the declaration.
-  class_info.adapt_id = context.AddInst<SemIR::AdaptDecl>(
-      node_id, {.adapted_type_inst_id = adapted_inst_id});
+  class_info.adapt_id = AddInst<SemIR::AdaptDecl>(
+      context, node_id, {.adapted_type_inst_id = adapted_inst_id});
 
   // Extend the class scope with the adapted type's scope if requested.
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
@@ -546,10 +547,11 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
   // binding will be performed when it's found by name lookup into an instance.
   auto field_type_id = GetUnboundElementType(context, class_info.self_type_id,
                                              base_info.type_id);
-  class_info.base_id = context.AddInst<SemIR::BaseDecl>(
-      node_id, {.type_id = field_type_id,
-                .base_type_inst_id = base_info.inst_id,
-                .index = SemIR::ElementIndex::None});
+  class_info.base_id =
+      AddInst<SemIR::BaseDecl>(context, node_id,
+                               {.type_id = field_type_id,
+                                .base_type_inst_id = base_info.inst_id,
+                                .index = SemIR::ElementIndex::None});
 
   if (base_info.type_id != SemIR::ErrorInst::SingletonTypeId) {
     auto base_class_info = context.classes().Get(
@@ -629,10 +631,11 @@ static auto CheckCompleteAdapterClassType(Context& context,
       class_info.GetAdaptedType(context.sem_ir(), SemIR::SpecificId::None);
   auto object_repr_id = context.types().GetObjectRepr(adapted_type_id);
 
-  return context.AddInst<SemIR::CompleteTypeWitness>(
-      node_id, {.type_id = GetSingletonType(
-                    context, SemIR::WitnessType::SingletonInstId),
-                .object_repr_id = object_repr_id});
+  return AddInst<SemIR::CompleteTypeWitness>(
+      context, node_id,
+      {.type_id =
+           GetSingletonType(context, SemIR::WitnessType::SingletonInstId),
+       .object_repr_id = object_repr_id});
 }
 
 static auto AddStructTypeFields(
@@ -643,7 +646,7 @@ static auto AddStructTypeFields(
     auto field_decl = context.insts().GetAs<SemIR::FieldDecl>(field_decl_id);
     field_decl.index =
         SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
-    context.ReplaceInstPreservingConstantValue(field_decl_id, field_decl);
+    ReplaceInstPreservingConstantValue(context, field_decl_id, field_decl);
     if (field_decl.type_id == SemIR::ErrorInst::SingletonTypeId) {
       struct_type_fields.push_back(
           {.name_id = field_decl.name_id,
@@ -699,7 +702,7 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
     auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
     base_decl.index =
         SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
-    context.ReplaceInstPreservingConstantValue(class_info.base_id, base_decl);
+    ReplaceInstPreservingConstantValue(context, class_info.base_id, base_decl);
     struct_type_fields.push_back(
         {.name_id = SemIR::NameId::Base, .type_id = base_type_id});
   }
@@ -747,14 +750,15 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
         vtable.push_back(inst_id);
       }
     }
-    class_info.vtable_id = context.AddInst<SemIR::Vtable>(
-        node_id, {.type_id = GetSingletonType(
-                      context, SemIR::VtableType::SingletonInstId),
-                  .virtual_functions_id = context.inst_blocks().Add(vtable)});
+    class_info.vtable_id = AddInst<SemIR::Vtable>(
+        context, node_id,
+        {.type_id =
+             GetSingletonType(context, SemIR::VtableType::SingletonInstId),
+         .virtual_functions_id = context.inst_blocks().Add(vtable)});
   }
 
-  return context.AddInst<SemIR::CompleteTypeWitness>(
-      node_id,
+  return AddInst<SemIR::CompleteTypeWitness>(
+      context, node_id,
       {.type_id =
            GetSingletonType(context, SemIR::WitnessType::SingletonInstId),
        .object_repr_id = GetStructType(

+ 6 - 4
toolchain/check/handle_export.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/decl_name_stack.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/check/name_lookup.h"
@@ -69,10 +70,11 @@ auto HandleParseNode(Context& context, Parse::ExportDeclId node_id) -> bool {
     return true;
   }
 
-  auto export_id = context.AddInst<SemIR::ExportDecl>(
-      node_id, {.type_id = import_ref->type_id,
-                .entity_name_id = import_ref->entity_name_id,
-                .value_id = inst_id});
+  auto export_id =
+      AddInst<SemIR::ExportDecl>(context, node_id,
+                                 {.type_id = import_ref->type_id,
+                                  .entity_name_id = import_ref->entity_name_id,
+                                  .value_id = inst_id});
   context.AddExport(export_id);
 
   // Replace the ImportRef in name lookup, both for the above duplicate

+ 11 - 10
toolchain/check/handle_function.cpp

@@ -13,6 +13,7 @@
 #include "toolchain/check/handle.h"
 #include "toolchain/check/import.h"
 #include "toolchain/check/import_ref.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/interface.h"
 #include "toolchain/check/literal.h"
 #include "toolchain/check/merge.h"
@@ -62,13 +63,13 @@ auto HandleParseNode(Context& context, Parse::ReturnTypeId node_id) -> bool {
         FullPatternStack::Kind::ExplicitParamList);
   }
 
-  auto return_slot_pattern_id =
-      context.AddPatternInst<SemIR::ReturnSlotPattern>(
-          node_id, {.type_id = type_id, .type_inst_id = type_inst_id});
-  auto param_pattern_id = context.AddPatternInst<SemIR::OutParamPattern>(
-      node_id, {.type_id = type_id,
-                .subpattern_id = return_slot_pattern_id,
-                .runtime_index = SemIR::RuntimeParamIndex::Unknown});
+  auto return_slot_pattern_id = AddPatternInst<SemIR::ReturnSlotPattern>(
+      context, node_id, {.type_id = type_id, .type_inst_id = type_inst_id});
+  auto param_pattern_id = AddPatternInst<SemIR::OutParamPattern>(
+      context, node_id,
+      {.type_id = type_id,
+       .subpattern_id = return_slot_pattern_id,
+       .runtime_index = SemIR::RuntimeParamIndex::Unknown});
   context.node_stack().Push(node_id, param_pattern_id);
   return true;
 }
@@ -258,7 +259,7 @@ static auto BuildFunctionDecl(Context& context,
   auto function_decl = SemIR::FunctionDecl{
       SemIR::TypeId::None, SemIR::FunctionId::None, decl_block_id};
   auto decl_id =
-      context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, function_decl));
+      AddPlaceholderInst(context, SemIR::LocIdAndInst(node_id, function_decl));
 
   // Find self parameter pattern.
   // TODO: Do this during initial traversal of implicit params.
@@ -316,7 +317,7 @@ static auto BuildFunctionDecl(Context& context,
                       context.scope_stack().PeekSpecificId());
 
   // Write the function ID into the FunctionDecl.
-  context.ReplaceInstBeforeConstantUse(decl_id, function_decl);
+  ReplaceInstBeforeConstantUse(context, decl_id, function_decl);
 
   // Diagnose 'definition of `abstract` function' using the canonical Function's
   // modifiers.
@@ -478,7 +479,7 @@ auto HandleParseNode(Context& context, Parse::FunctionDefinitionId node_id)
           "missing `return` at end of function with declared return type");
       context.emitter().Emit(TokenOnly(node_id), MissingReturnStatement);
     } else {
-      context.AddInst<SemIR::Return>(node_id, {});
+      AddInst<SemIR::Return>(context, node_id, {});
     }
   }
 

+ 2 - 1
toolchain/check/handle_if_statement.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 
 namespace Carbon::Check {
 
@@ -54,7 +55,7 @@ auto HandleParseNode(Context& context, Parse::IfStatementId node_id) -> bool {
       // block.
       auto else_block_id =
           context.node_stack().Pop<Parse::NodeKind::IfCondition>();
-      context.AddInst<SemIR::Branch>(node_id, {.target_id = else_block_id});
+      AddInst<SemIR::Branch>(context, node_id, {.target_id = else_block_id});
       context.inst_block_stack().Pop();
       context.inst_block_stack().Push(else_block_id);
       context.region_stack().AddToRegion(else_block_id, node_id);

+ 7 - 6
toolchain/check/handle_impl.cpp

@@ -8,6 +8,7 @@
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/impl.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_lookup.h"
@@ -107,8 +108,8 @@ auto HandleParseNode(Context& context, Parse::DefaultSelfImplAsId node_id)
   // is a class and found its `Self`, so additionally performing an unqualified
   // name lookup would be redundant work, but would avoid duplicating the
   // handling of the `Self` expression.
-  auto self_inst_id = context.AddInst(
-      node_id,
+  auto self_inst_id = AddInst(
+      context, node_id,
       SemIR::NameRef{.type_id = SemIR::TypeType::SingletonTypeId,
                      .name_id = SemIR::NameId::SelfType,
                      .value_id = context.types().GetInstId(self_type_id)});
@@ -342,7 +343,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   SemIR::ImplDecl impl_decl = {.impl_id = SemIR::ImplId::None,
                                .decl_block_id = decl_block_id};
   auto impl_decl_id =
-      context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, impl_decl));
+      AddPlaceholderInst(context, SemIR::LocIdAndInst(node_id, impl_decl));
 
   SemIR::Impl impl_info = {
       name_context.MakeEntityWithParamsBase(name, impl_decl_id,
@@ -383,15 +384,15 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   }
 
   // Write the impl ID into the ImplDecl.
-  context.ReplaceInstBeforeConstantUse(impl_decl_id, impl_decl);
+  ReplaceInstBeforeConstantUse(context, impl_decl_id, impl_decl);
 
   // For an `extend impl` declaration, mark the impl as extending this `impl`.
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
     auto extend_node = introducer.modifier_node_id(ModifierOrder::Decl);
     if (impl_info.generic_id.has_value()) {
       SemIR::TypeId type_id = context.insts().Get(constraint_inst_id).type_id();
-      constraint_inst_id = context.AddInst<SemIR::SpecificConstant>(
-          context.insts().GetLocId(constraint_inst_id),
+      constraint_inst_id = AddInst<SemIR::SpecificConstant>(
+          context, context.insts().GetLocId(constraint_inst_id),
           {.type_id = type_id,
            .inst_id = constraint_inst_id,
            .specific_id =

+ 9 - 6
toolchain/check/handle_index.cpp

@@ -8,6 +8,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/literal.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/operator.h"
@@ -145,15 +146,17 @@ auto HandleParseNode(Context& context, Parse::IndexExprId node_id) -> bool {
       if (array_cat == SemIR::ExprCategory::Value) {
         // If the operand is an array value, convert it to an ephemeral
         // reference to an array so we can perform a primitive indexing into it.
-        operand_inst_id = context.AddInst<SemIR::ValueAsRef>(
-            node_id, {.type_id = operand_type_id, .value_id = operand_inst_id});
+        operand_inst_id = AddInst<SemIR::ValueAsRef>(
+            context, node_id,
+            {.type_id = operand_type_id, .value_id = operand_inst_id});
       }
       // Constant evaluation will perform a bounds check on this array indexing
       // if the index is constant.
-      auto elem_id = context.AddInst<SemIR::ArrayIndex>(
-          node_id, {.type_id = array_type.element_type_id,
-                    .array_id = operand_inst_id,
-                    .index_id = cast_index_id});
+      auto elem_id =
+          AddInst<SemIR::ArrayIndex>(context, node_id,
+                                     {.type_id = array_type.element_type_id,
+                                      .array_id = operand_inst_id,
+                                      .index_id = cast_index_id});
       if (array_cat != SemIR::ExprCategory::DurableRef) {
         // Indexing a durable reference gives a durable reference expression.
         // Indexing anything else gives a value expression.

+ 7 - 6
toolchain/check/handle_interface.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/eval.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
@@ -54,7 +55,7 @@ static auto BuildInterfaceDecl(Context& context,
       SemIR::InterfaceDecl{SemIR::TypeType::SingletonTypeId,
                            SemIR::InterfaceId::None, decl_block_id};
   auto interface_decl_id =
-      context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, interface_decl));
+      AddPlaceholderInst(context, SemIR::LocIdAndInst(node_id, interface_decl));
 
   SemIR::Interface interface_info = {name_context.MakeEntityWithParamsBase(
       name, interface_decl_id, /*is_extern=*/false,
@@ -128,7 +129,7 @@ static auto BuildInterfaceDecl(Context& context,
   }
 
   // Write the interface ID into the InterfaceDecl.
-  context.ReplaceInstBeforeConstantUse(interface_decl_id, interface_decl);
+  ReplaceInstBeforeConstantUse(context, interface_decl_id, interface_decl);
 
   return {interface_decl.interface_id, interface_decl_id};
 }
@@ -177,10 +178,10 @@ auto HandleParseNode(Context& context,
       context.scope_stack().AddCompileTimeBinding(),
       /*is_template=*/false);
   interface_info.self_param_id =
-      context.AddInst(SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
-          {.type_id = self_type_id,
-           .entity_name_id = entity_name_id,
-           .value_id = SemIR::InstId::None}));
+      AddInst(context, SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
+                           {.type_id = self_type_id,
+                            .entity_name_id = entity_name_id,
+                            .value_id = SemIR::InstId::None}));
   context.scope_stack().PushCompileTimeBinding(interface_info.self_param_id);
   context.name_scopes().AddRequiredName(interface_info.scope_id,
                                         SemIR::NameId::SelfType,

+ 11 - 9
toolchain/check/handle_let_and_var.cpp

@@ -7,6 +7,7 @@
 #include "toolchain/check/decl_introducer_state.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/interface.h"
 #include "toolchain/check/keyword_modifier_set.h"
 #include "toolchain/check/modifiers.h"
@@ -124,9 +125,11 @@ static auto GetOrAddStorage(Context& context, SemIR::InstId pattern_id)
     name_id =
         context.entity_names().Get(binding_pattern->entity_name_id).name_id;
   }
-  return context.AddInst(SemIR::LocIdAndInst::UncheckedLoc(
-      pattern.loc_id, SemIR::VarStorage{.type_id = pattern.inst.type_id(),
-                                        .pretty_name_id = name_id}));
+  return AddInst(
+      context,
+      SemIR::LocIdAndInst::UncheckedLoc(
+          pattern.loc_id, SemIR::VarStorage{.type_id = pattern.inst.type_id(),
+                                            .pretty_name_id = name_id}));
 }
 
 auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
@@ -145,8 +148,8 @@ auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
   }
   auto type_id = context.insts().Get(subpattern_id).type_id();
 
-  auto pattern_id = context.AddPatternInst<SemIR::VarPattern>(
-      node_id, {.type_id = type_id, .subpattern_id = subpattern_id});
+  auto pattern_id = AddPatternInst<SemIR::VarPattern>(
+      context, node_id, {.type_id = type_id, .subpattern_id = subpattern_id});
   context.node_stack().Push(node_id, pattern_id);
   return true;
 }
@@ -161,9 +164,8 @@ static auto EndFullPattern(Context& context) -> void {
     return;
   }
   auto pattern_block_id = context.pattern_block_stack().Pop();
-  context.AddInst<SemIR::NameBindingDecl>(
-      context.node_stack().PeekNodeId(),
-      {.pattern_block_id = pattern_block_id});
+  AddInst<SemIR::NameBindingDecl>(context, context.node_stack().PeekNodeId(),
+                                  {.pattern_block_id = pattern_block_id});
 
   // We need to emit the VarStorage insts early, because they may be output
   // arguments for the initializer. However, we can't emit them when we emit
@@ -331,7 +333,7 @@ static auto FinishAssociatedConstant(Context& context, Parse::LetDeclId node_id,
 
   // Store the decl block on the declaration.
   decl->decl_block_id = context.inst_block_stack().Pop();
-  context.ReplaceInstPreservingConstantValue(decl_info.pattern_id, *decl);
+  ReplaceInstPreservingConstantValue(context, decl_info.pattern_id, *decl);
 
   context.inst_block_stack().AddInstId(decl_info.pattern_id);
 }

+ 12 - 10
toolchain/check/handle_literal.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/call.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/literal.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
@@ -15,8 +16,8 @@ namespace Carbon::Check {
 
 auto HandleParseNode(Context& context, Parse::BoolLiteralFalseId node_id)
     -> bool {
-  context.AddInstAndPush<SemIR::BoolLiteral>(
-      node_id,
+  AddInstAndPush<SemIR::BoolLiteral>(
+      context, node_id,
       {.type_id = GetSingletonType(context, SemIR::BoolType::SingletonInstId),
        .value = SemIR::BoolValue::False});
   return true;
@@ -24,8 +25,8 @@ auto HandleParseNode(Context& context, Parse::BoolLiteralFalseId node_id)
 
 auto HandleParseNode(Context& context, Parse::BoolLiteralTrueId node_id)
     -> bool {
-  context.AddInstAndPush<SemIR::BoolLiteral>(
-      node_id,
+  AddInstAndPush<SemIR::BoolLiteral>(
+      context, node_id,
       {.type_id = GetSingletonType(context, SemIR::BoolType::SingletonInstId),
        .value = SemIR::BoolValue::True});
   return true;
@@ -76,16 +77,17 @@ auto HandleParseNode(Context& context, Parse::RealLiteralId node_id) -> bool {
                                real_value.exponent.getSExtValue());
 
   auto float_id = context.sem_ir().floats().Add(llvm::APFloat(double_val));
-  context.AddInstAndPush<SemIR::FloatLiteral>(
-      node_id, {.type_id = GetSingletonType(
-                    context, SemIR::LegacyFloatType::SingletonInstId),
-                .float_id = float_id});
+  AddInstAndPush<SemIR::FloatLiteral>(
+      context, node_id,
+      {.type_id =
+           GetSingletonType(context, SemIR::LegacyFloatType::SingletonInstId),
+       .float_id = float_id});
   return true;
 }
 
 auto HandleParseNode(Context& context, Parse::StringLiteralId node_id) -> bool {
-  context.AddInstAndPush<SemIR::StringLiteral>(
-      node_id,
+  AddInstAndPush<SemIR::StringLiteral>(
+      context, node_id,
       {.type_id = GetSingletonType(context, SemIR::StringType::SingletonInstId),
        .string_literal_id = context.tokens().GetStringLiteralValue(
            context.parse_tree().node_token(node_id))});

+ 6 - 5
toolchain/check/handle_loop_statement.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 
 namespace Carbon::Check {
 
@@ -60,7 +61,7 @@ auto HandleParseNode(Context& context, Parse::WhileStatementId node_id)
   context.break_continue_stack().pop_back();
 
   // Add the loop backedge.
-  context.AddInst<SemIR::Branch>(node_id, {.target_id = loop_header_id});
+  AddInst<SemIR::Branch>(context, node_id, {.target_id = loop_header_id});
   context.inst_block_stack().Pop();
 
   // Start emitting the loop exit block.
@@ -101,8 +102,8 @@ auto HandleParseNode(Context& context, Parse::BreakStatementStartId node_id)
                       "`break` can only be used in a loop");
     context.emitter().Emit(node_id, BreakOutsideLoop);
   } else {
-    context.AddInst<SemIR::Branch>(node_id,
-                                   {.target_id = stack.back().break_target});
+    AddInst<SemIR::Branch>(context, node_id,
+                           {.target_id = stack.back().break_target});
   }
 
   context.inst_block_stack().Pop();
@@ -126,8 +127,8 @@ auto HandleParseNode(Context& context, Parse::ContinueStatementStartId node_id)
                       "`continue` can only be used in a loop");
     context.emitter().Emit(node_id, ContinueOutsideLoop);
   } else {
-    context.AddInst<SemIR::Branch>(node_id,
-                                   {.target_id = stack.back().continue_target});
+    AddInst<SemIR::Branch>(context, node_id,
+                           {.target_id = stack.back().continue_target});
   }
 
   context.inst_block_stack().Pop();

+ 15 - 11
toolchain/check/handle_name.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/member_access.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/check/name_lookup.h"
@@ -115,14 +116,16 @@ static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id,
   // store the specific too.
   if (result.specific_id.has_value() &&
       context.constant_values().Get(inst_id).is_symbolic()) {
-    inst_id = context.AddInst<SemIR::SpecificConstant>(
-        node_id, {.type_id = type_id,
-                  .inst_id = inst_id,
-                  .specific_id = result.specific_id});
+    inst_id =
+        AddInst<SemIR::SpecificConstant>(context, node_id,
+                                         {.type_id = type_id,
+                                          .inst_id = inst_id,
+                                          .specific_id = result.specific_id});
   }
 
-  return context.AddInst<SemIR::NameRef>(
-      node_id, {.type_id = type_id, .name_id = name_id, .value_id = inst_id});
+  return AddInst<SemIR::NameRef>(
+      context, node_id,
+      {.type_id = type_id, .name_id = name_id, .value_id = inst_id});
 }
 
 static auto HandleIdentifierName(Context& context,
@@ -238,11 +241,12 @@ auto HandleParseNode(Context& context, Parse::DesignatorExprId node_id)
 }
 
 auto HandleParseNode(Context& context, Parse::PackageExprId node_id) -> bool {
-  context.AddInstAndPush<SemIR::NameRef>(
-      node_id, {.type_id = GetSingletonType(
-                    context, SemIR::NamespaceType::SingletonInstId),
-                .name_id = SemIR::NameId::PackageNamespace,
-                .value_id = SemIR::Namespace::PackageInstId});
+  AddInstAndPush<SemIR::NameRef>(
+      context, node_id,
+      {.type_id =
+           GetSingletonType(context, SemIR::NamespaceType::SingletonInstId),
+       .name_id = SemIR::NameId::PackageNamespace,
+       .value_id = SemIR::Namespace::PackageInstId});
   return true;
 }
 

+ 4 - 3
toolchain/check/handle_namespace.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/decl_introducer_state.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/check/name_lookup.h"
@@ -42,7 +43,7 @@ auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
       GetSingletonType(context, SemIR::NamespaceType::SingletonInstId),
       SemIR::NameScopeId::None, SemIR::InstId::None};
   auto namespace_id =
-      context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, namespace_inst));
+      AddPlaceholderInst(context, SemIR::LocIdAndInst(node_id, namespace_inst));
 
   SemIR::ScopeLookupResult lookup_result =
       context.decl_name_stack().LookupOrAddName(name_context, namespace_id,
@@ -75,7 +76,7 @@ auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
                  !context.insts().GetLocId(existing_inst_id).has_value()) {
         // When the name conflict is an imported namespace, fill the location ID
         // so that future diagnostics point at this declaration.
-        context.SetNamespaceNodeId(existing_inst_id, node_id);
+        SetNamespaceNodeId(context, existing_inst_id, node_id);
       }
     } else {
       DiagnoseDuplicateName(context, name_context.loc_id, existing_inst_id);
@@ -96,7 +97,7 @@ auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
     }
   }
 
-  context.ReplaceInstBeforeConstantUse(namespace_id, namespace_inst);
+  ReplaceInstBeforeConstantUse(context, namespace_id, namespace_inst);
 
   context.decl_name_stack().PopScope();
   return true;

+ 29 - 23
toolchain/check/handle_operator.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/operator.h"
 #include "toolchain/check/pointer_dereference.h"
 #include "toolchain/check/type.h"
@@ -85,7 +86,8 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id)
   // TODO: Destroy the old value before reinitializing. This will require
   // building the destruction code before we build the RHS subexpression.
   rhs_id = Initialize(context, node_id, lhs_id, rhs_id);
-  context.AddInst<SemIR::Assign>(node_id, {.lhs_id = lhs_id, .rhs_id = rhs_id});
+  AddInst<SemIR::Assign>(context, node_id,
+                         {.lhs_id = lhs_id, .rhs_id = rhs_id});
   // We model assignment as an expression, so we need to push a value for
   // it, even though it doesn't produce a value.
   // TODO: Consider changing our parse tree to model assignment as a
@@ -216,9 +218,10 @@ auto HandleParseNode(Context& context, Parse::PostfixOperatorStarId node_id)
     -> bool {
   auto value_id = context.node_stack().PopExpr();
   auto inner_type_id = ExprAsType(context, node_id, value_id).type_id;
-  context.AddInstAndPush<SemIR::PointerType>(
-      node_id, {.type_id = SemIR::TypeType::SingletonTypeId,
-                .pointee_id = inner_type_id});
+  AddInstAndPush<SemIR::PointerType>(
+      context, node_id,
+      {.type_id = SemIR::TypeType::SingletonTypeId,
+       .pointee_id = inner_type_id});
   return true;
 }
 
@@ -244,9 +247,10 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorAmpId node_id)
       value_id = SemIR::ErrorInst::SingletonInstId;
       break;
   }
-  context.AddInstAndPush<SemIR::AddrOf>(
-      node_id, SemIR::AddrOf{.type_id = GetPointerType(context, type_id),
-                             .lvalue_id = value_id});
+  AddInstAndPush<SemIR::AddrOf>(
+      context, node_id,
+      SemIR::AddrOf{.type_id = GetPointerType(context, type_id),
+                    .lvalue_id = value_id});
   return true;
 }
 
@@ -269,8 +273,8 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorConstId node_id)
     context.emitter().Emit(node_id, RepeatedConst);
   }
   auto inner_type_id = ExprAsType(context, node_id, value_id).type_id;
-  context.AddInstAndPush<SemIR::ConstType>(
-      node_id,
+  AddInstAndPush<SemIR::ConstType>(
+      context, node_id,
       {.type_id = SemIR::TypeType::SingletonTypeId, .inner_id = inner_type_id});
   return true;
 }
@@ -289,9 +293,10 @@ auto HandleParseNode(Context& context, Parse::PrefixOperatorNotId node_id)
     -> bool {
   auto value_id = context.node_stack().PopExpr();
   value_id = ConvertToBoolValue(context, node_id, value_id);
-  context.AddInstAndPush<SemIR::UnaryOperatorNot>(
-      node_id, {.type_id = context.insts().Get(value_id).type_id(),
-                .operand_id = value_id});
+  AddInstAndPush<SemIR::UnaryOperatorNot>(
+      context, node_id,
+      {.type_id = context.insts().Get(value_id).type_id(),
+       .operand_id = value_id});
   return true;
 }
 
@@ -341,12 +346,12 @@ static auto HandleShortCircuitOperand(Context& context, Parse::NodeId node_id,
 
   // Compute the branch value: the condition for `and`, inverted for `or`.
   SemIR::InstId branch_value_id =
-      is_or
-          ? context.AddInst<SemIR::UnaryOperatorNot>(
-                node_id, {.type_id = bool_type_id, .operand_id = cond_value_id})
-          : cond_value_id;
-  auto short_circuit_result_id = context.AddInst<SemIR::BoolLiteral>(
-      node_id,
+      is_or ? AddInst<SemIR::UnaryOperatorNot>(
+                  context, node_id,
+                  {.type_id = bool_type_id, .operand_id = cond_value_id})
+            : cond_value_id;
+  auto short_circuit_result_id = AddInst<SemIR::BoolLiteral>(
+      context, node_id,
       {.type_id = bool_type_id, .value = SemIR::BoolValue::From(is_or)});
 
   // Create a block for the right-hand side and for the continuation.
@@ -403,15 +408,16 @@ static auto HandleShortCircuitOperator(Context& context, Parse::NodeId node_id)
   // When the second operand is evaluated, the result of `and` and `or` is
   // its value.
   auto resume_block_id = context.inst_block_stack().PeekOrAdd(/*depth=*/1);
-  context.AddInst<SemIR::BranchWithArg>(
-      node_id, {.target_id = resume_block_id, .arg_id = rhs_id});
+  AddInst<SemIR::BranchWithArg>(
+      context, node_id, {.target_id = resume_block_id, .arg_id = rhs_id});
   context.inst_block_stack().Pop();
   context.region_stack().AddToRegion(resume_block_id, node_id);
 
   // Collect the result from either the first or second operand.
-  auto result_id = context.AddInst<SemIR::BlockArg>(
-      node_id, {.type_id = context.insts().Get(rhs_id).type_id(),
-                .block_id = resume_block_id});
+  auto result_id = AddInst<SemIR::BlockArg>(
+      context, node_id,
+      {.type_id = context.insts().Get(rhs_id).type_id(),
+       .block_id = resume_block_id});
   SetBlockArgResultBeforeConstantUse(context, result_id, branch_value_id,
                                      rhs_id, short_circuit_result_id);
   context.node_stack().Push(node_id, result_id);

+ 5 - 4
toolchain/check/handle_struct.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/format_providers.h"
 
@@ -145,8 +146,8 @@ auto HandleParseNode(Context& context, Parse::StructLiteralId node_id) -> bool {
     auto type_id = GetStructType(
         context, context.struct_type_fields().AddCanonical(fields));
 
-    auto value_id = context.AddInst<SemIR::StructLiteral>(
-        node_id, {.type_id = type_id, .elements_id = elements_id});
+    auto value_id = AddInst<SemIR::StructLiteral>(
+        context, node_id, {.type_id = type_id, .elements_id = elements_id});
     context.node_stack().Push(node_id, value_id);
   }
 
@@ -169,8 +170,8 @@ auto HandleParseNode(Context& context, Parse::StructTypeLiteralId node_id)
     context.node_stack().Push(node_id, SemIR::ErrorInst::SingletonInstId);
   } else {
     auto fields_id = context.struct_type_fields().AddCanonical(fields);
-    context.AddInstAndPush<SemIR::StructType>(
-        node_id,
+    AddInstAndPush<SemIR::StructType>(
+        context, node_id,
         {.type_id = SemIR::TypeType::SingletonTypeId, .fields_id = fields_id});
   }
 

+ 3 - 2
toolchain/check/handle_tuple_literal.cpp

@@ -4,6 +4,7 @@
 
 #include "toolchain/check/context.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/type.h"
 
 namespace Carbon::Check {
@@ -35,8 +36,8 @@ auto HandleParseNode(Context& context, Parse::TupleLiteralId node_id) -> bool {
   }
   auto type_id = GetTupleType(context, type_ids);
 
-  auto value_id = context.AddInst<SemIR::TupleLiteral>(
-      node_id, {.type_id = type_id, .elements_id = refs_id});
+  auto value_id = AddInst<SemIR::TupleLiteral>(
+      context, node_id, {.type_id = type_id, .elements_id = refs_id});
   context.node_stack().Push(node_id, value_id);
   return true;
 }

+ 17 - 16
toolchain/check/handle_where.cpp

@@ -6,6 +6,7 @@
 #include "toolchain/check/convert.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/inst.h"
 
 namespace Carbon::Check {
 
@@ -32,12 +33,12 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
   auto entity_name_id = context.entity_names().Add(
       {.name_id = SemIR::NameId::PeriodSelf,
        .parent_scope_id = context.scope_stack().PeekNameScopeId()});
-  auto inst_id =
-      context.AddInst(SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
-          {.type_id = self_type_id,
-           .entity_name_id = entity_name_id,
-           // `None` because there is no equivalent non-symbolic value.
-           .value_id = SemIR::InstId::None}));
+  auto inst_id = AddInst(
+      context, SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
+                   {.type_id = self_type_id,
+                    .entity_name_id = entity_name_id,
+                    // `None` because there is no equivalent non-symbolic value.
+                    .value_id = SemIR::InstId::None}));
   auto existing =
       context.scope_stack().LookupOrAddName(SemIR::NameId::PeriodSelf, inst_id);
   // Shouldn't have any names in newly created scope.
@@ -79,8 +80,8 @@ auto HandleParseNode(Context& context, Parse::RequirementEqualId node_id)
 
   // Build up the list of arguments for the `WhereExpr` inst.
   context.args_type_info_stack().AddInstId(
-      context.AddInstInNoBlock<SemIR::RequirementRewrite>(
-          node_id, {.lhs_id = lhs, .rhs_id = rhs_id}));
+      AddInstInNoBlock<SemIR::RequirementRewrite>(
+          context, node_id, {.lhs_id = lhs, .rhs_id = rhs_id}));
   return true;
 }
 
@@ -93,8 +94,8 @@ auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId node_id)
 
   // Build up the list of arguments for the `WhereExpr` inst.
   context.args_type_info_stack().AddInstId(
-      context.AddInstInNoBlock<SemIR::RequirementEquivalent>(
-          node_id, {.lhs_id = lhs, .rhs_id = rhs}));
+      AddInstInNoBlock<SemIR::RequirementEquivalent>(
+          context, node_id, {.lhs_id = lhs, .rhs_id = rhs}));
   return true;
 }
 
@@ -118,8 +119,8 @@ auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
 
   // Build up the list of arguments for the `WhereExpr` inst.
   context.args_type_info_stack().AddInstId(
-      context.AddInstInNoBlock<SemIR::RequirementImpls>(
-          node_id,
+      AddInstInNoBlock<SemIR::RequirementImpls>(
+          context, node_id,
           {.lhs_id = lhs_as_type.inst_id, .rhs_id = rhs_as_type.inst_id}));
   return true;
 }
@@ -137,10 +138,10 @@ auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool {
   SemIR::InstId period_self_id =
       context.node_stack().Pop<Parse::NodeKind::WhereOperand>();
   SemIR::InstBlockId requirements_id = context.args_type_info_stack().Pop();
-  context.AddInstAndPush<SemIR::WhereExpr>(
-      node_id, {.type_id = SemIR::TypeType::SingletonTypeId,
-                .period_self_id = period_self_id,
-                .requirements_id = requirements_id});
+  AddInstAndPush<SemIR::WhereExpr>(context, node_id,
+                                   {.type_id = SemIR::TypeType::SingletonTypeId,
+                                    .period_self_id = period_self_id,
+                                    .requirements_id = requirements_id});
   return true;
 }
 

+ 2 - 2
toolchain/check/impl.cpp

@@ -133,8 +133,8 @@ auto ImplWitnessForDeclaration(Context& context, const SemIR::Impl& impl)
   llvm::SmallVector<SemIR::InstId> table(assoc_entities.size(),
                                          SemIR::InstId::None);
   auto table_id = context.inst_blocks().Add(table);
-  return context.AddInst<SemIR::ImplWitness>(
-      context.insts().GetLocId(impl.latest_decl_id()),
+  return AddInst<SemIR::ImplWitness>(
+      context, context.insts().GetLocId(impl.latest_decl_id()),
       {.type_id =
            GetSingletonType(context, SemIR::WitnessType::SingletonInstId),
        .elements_id = table_id,

+ 11 - 9
toolchain/check/import.cpp

@@ -9,6 +9,7 @@
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/import_ref.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
@@ -128,17 +129,17 @@ auto AddImportNamespace(Context& context, SemIR::TypeId namespace_type_id,
                        .import_id = import_id};
   auto namespace_inst_and_loc =
       import_loc_id.is_import_ir_inst_id()
-          ? context.MakeImportedLocAndInst(import_loc_id.import_ir_inst_id(),
-                                           namespace_inst)
+          ? MakeImportedLocIdAndInst(context, import_loc_id.import_ir_inst_id(),
+                                     namespace_inst)
           // TODO: Check that this actually is an `AnyNamespaceId`.
           : SemIR::LocIdAndInst(Parse::AnyNamespaceId(import_loc_id.node_id()),
                                 namespace_inst);
   auto namespace_id =
-      context.AddPlaceholderInstInNoBlock(namespace_inst_and_loc);
+      AddPlaceholderInstInNoBlock(context, namespace_inst_and_loc);
   context.import_ref_ids().push_back(namespace_id);
   namespace_inst.name_scope_id =
       context.name_scopes().Add(namespace_id, name_id, parent_scope_id);
-  context.ReplaceInstBeforeConstantUse(namespace_id, namespace_inst);
+  ReplaceInstBeforeConstantUse(context, namespace_id, namespace_inst);
 
   // Note we have to get the parent scope freshly, creating the imported
   // namespace may invalidate the pointer above.
@@ -188,11 +189,12 @@ static auto CopySingleNameScopeFromImportIR(
         {.name_id = name_id, .parent_scope_id = parent_scope_id});
     auto import_ir_inst_id = context.import_ir_insts().Add(
         {.ir_id = ir_id, .inst_id = import_inst_id});
-    auto inst_id = context.AddInstInNoBlock(
-        context.MakeImportedLocAndInst<SemIR::ImportRefLoaded>(
-            import_ir_inst_id, {.type_id = namespace_type_id,
-                                .import_ir_inst_id = import_ir_inst_id,
-                                .entity_name_id = entity_name_id}));
+    auto inst_id = AddInstInNoBlock(
+        context, MakeImportedLocIdAndInst<SemIR::ImportRefLoaded>(
+                     context, import_ir_inst_id,
+                     {.type_id = namespace_type_id,
+                      .import_ir_inst_id = import_ir_inst_id,
+                      .entity_name_id = entity_name_id}));
     context.import_ref_ids().push_back(inst_id);
     return inst_id;
   };

+ 3 - 2
toolchain/check/import_cpp.cpp

@@ -17,6 +17,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/import.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/format_providers.h"
@@ -105,8 +106,8 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
              SemIR::NameScopeId::Package,
              /*diagnose_duplicate_namespace=*/false,
              [&]() {
-               return context.AddInst<SemIR::ImportCppDecl>(
-                   imports.front().node_id, {});
+               return AddInst<SemIR::ImportCppDecl>(
+                   context, imports.front().node_id, {});
              })
       .name_scope_id;
 }

+ 117 - 61
toolchain/check/import_ref.cpp

@@ -9,6 +9,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/generic.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
 #include "toolchain/parse/node_ids.h"
@@ -63,8 +64,8 @@ auto AddImportRef(Context& context, SemIR::ImportIRInst import_ir_inst,
   auto import_ir_inst_id = context.import_ir_insts().Add(import_ir_inst);
   SemIR::ImportRefUnloaded inst = {.import_ir_inst_id = import_ir_inst_id,
                                    .entity_name_id = entity_name_id};
-  auto import_ref_id = context.AddPlaceholderInstInNoBlock(
-      context.MakeImportedLocAndInst(import_ir_inst_id, inst));
+  auto import_ref_id = AddPlaceholderInstInNoBlock(
+      context, MakeImportedLocIdAndInst(context, import_ir_inst_id, inst));
 
   // ImportRefs have a dedicated block because this may be called during
   // processing where the instruction shouldn't be inserted in the current inst
@@ -84,8 +85,8 @@ static auto AddLoadedImportRef(Context& context, SemIR::TypeId type_id,
   SemIR::ImportRefLoaded inst = {.type_id = type_id,
                                  .import_ir_inst_id = import_ir_inst_id,
                                  .entity_name_id = SemIR::EntityNameId::None};
-  auto inst_id = context.AddPlaceholderInstInNoBlock(
-      context.MakeImportedLocAndInst(import_ir_inst_id, inst));
+  auto inst_id = AddPlaceholderInstInNoBlock(
+      context, MakeImportedLocIdAndInst(context, import_ir_inst_id, inst));
   context.import_ref_ids().push_back(inst_id);
 
   context.constant_values().Set(inst_id, const_id);
@@ -1033,10 +1034,9 @@ static auto GetLocalParamPatternsId(ImportContext& context,
       case SemIR::BindingPattern::Kind: {
         auto entity_name_id = context.local_entity_names().Add(
             {.name_id = name_id, .parent_scope_id = SemIR::NameScopeId::None});
-        new_param_id =
-            context.local_context().AddInstInNoBlock<SemIR::BindingPattern>(
-                AddImportIRInst(context, binding_id),
-                {.type_id = type_id, .entity_name_id = entity_name_id});
+        new_param_id = AddInstInNoBlock<SemIR::BindingPattern>(
+            context.local_context(), AddImportIRInst(context, binding_id),
+            {.type_id = type_id, .entity_name_id = entity_name_id});
         break;
       }
       case SemIR::SymbolicBindingPattern::Kind: {
@@ -1047,8 +1047,9 @@ static auto GetLocalParamPatternsId(ImportContext& context,
         auto new_binding_inst =
             context.local_insts().GetAs<SemIR::SymbolicBindingPattern>(
                 context.local_constant_values().GetInstId(bind_const_id));
-        new_param_id = context.local_context().AddInstInNoBlock(
-            AddImportIRInst(context, binding_id), new_binding_inst);
+        new_param_id = AddInstInNoBlock(context.local_context(),
+                                        AddImportIRInst(context, binding_id),
+                                        new_binding_inst);
         context.local_constant_values().Set(new_param_id, bind_const_id);
         break;
       }
@@ -1056,20 +1057,22 @@ static auto GetLocalParamPatternsId(ImportContext& context,
         CARBON_FATAL("Unexpected kind: ", binding.kind);
       }
     }
-    new_param_id = context.local_context().AddInstInNoBlock(
-        context.local_context()
-            .MakeImportedLocAndInst<SemIR::ValueParamPattern>(
-                AddImportIRInst(context, param_pattern_id),
-                {.type_id = type_id,
-                 .subpattern_id = new_param_id,
-                 .runtime_index = param_pattern.runtime_index}));
+    new_param_id = AddInstInNoBlock(
+        context.local_context(),
+        MakeImportedLocIdAndInst<SemIR::ValueParamPattern>(
+            context.local_context(), AddImportIRInst(context, param_pattern_id),
+            {.type_id = type_id,
+             .subpattern_id = new_param_id,
+             .runtime_index = param_pattern.runtime_index}));
     if (addr_inst) {
       type_id = context.local_context().types().GetTypeIdForTypeConstantId(
           GetLocalConstantIdChecked(context, addr_inst->type_id));
-      new_param_id = context.local_context().AddInstInNoBlock(
-          context.local_context().MakeImportedLocAndInst<SemIR::AddrPattern>(
-              AddImportIRInst(context, addr_pattern_id),
-              {.type_id = type_id, .inner_id = new_param_id}));
+      new_param_id =
+          AddInstInNoBlock(context.local_context(),
+                           MakeImportedLocIdAndInst<SemIR::AddrPattern>(
+                               context.local_context(),
+                               AddImportIRInst(context, addr_pattern_id),
+                               {.type_id = type_id, .inner_id = new_param_id}));
     }
     if (self_param_id &&
         context.import_entity_names().Get(binding.entity_name_id).name_id ==
@@ -1098,12 +1101,16 @@ static auto GetLocalReturnSlotPatternId(
   auto type_id = context.local_context().types().GetTypeIdForTypeConstantId(
       GetLocalConstantIdChecked(context, return_slot_pattern.type_id));
 
-  auto new_return_slot_pattern_id = context.local_context().AddInstInNoBlock(
-      context.local_context().MakeImportedLocAndInst<SemIR::ReturnSlotPattern>(
+  auto new_return_slot_pattern_id = AddInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst<SemIR::ReturnSlotPattern>(
+          context.local_context(),
           AddImportIRInst(context, param_pattern.subpattern_id),
           {.type_id = type_id, .type_inst_id = SemIR::InstId::None}));
-  return context.local_context().AddInstInNoBlock(
-      context.local_context().MakeImportedLocAndInst<SemIR::OutParamPattern>(
+  return AddInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst<SemIR::OutParamPattern>(
+          context.local_context(),
           AddImportIRInst(context, import_return_slot_pattern_id),
           {.type_id = type_id,
            .subpattern_id = new_return_slot_pattern_id,
@@ -1340,9 +1347,10 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
                          inst.adapted_type_inst_id, adapted_type_const_id);
 
   // Create a corresponding instruction to represent the declaration.
-  auto inst_id = resolver.local_context().AddInstInNoBlock(
-      resolver.local_context().MakeImportedLocAndInst<SemIR::AdaptDecl>(
-          AddImportIRInst(resolver, import_inst_id),
+  auto inst_id = AddInstInNoBlock(
+      resolver.local_context(),
+      MakeImportedLocIdAndInst<SemIR::AdaptDecl>(
+          resolver.local_context(), AddImportIRInst(resolver, import_inst_id),
           {.adapted_type_inst_id = adapted_type_inst_id}));
   return ResolveResult::Done(resolver.local_constant_values().Get(inst_id),
                              inst_id);
@@ -1375,11 +1383,12 @@ static auto MakeAssociatedConstant(
       .type_id = type_id,
       .assoc_const_id = SemIR::AssociatedConstantId::None,
       .decl_block_id = SemIR::InstBlockId::Empty};
-  auto assoc_const_decl_id =
-      context.local_context().AddPlaceholderInstInNoBlock(
-          context.local_context().MakeImportedLocAndInst(
-              AddImportIRInst(context, import_assoc_const.decl_id),
-              assoc_const_decl));
+  auto assoc_const_decl_id = AddPlaceholderInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst(
+          context.local_context(),
+          AddImportIRInst(context, import_assoc_const.decl_id),
+          assoc_const_decl));
   assoc_const_decl.assoc_const_id = context.local_associated_constants().Add({
       .name_id = GetLocalNameId(context, import_assoc_const.name_id),
       .parent_scope_id = SemIR::NameScopeId::None,
@@ -1393,8 +1402,8 @@ static auto MakeAssociatedConstant(
   });
 
   // Write the associated constant ID into the AssociatedConstantDecl.
-  context.local_context().ReplaceInstBeforeConstantUse(assoc_const_decl_id,
-                                                       assoc_const_decl);
+  ReplaceInstBeforeConstantUse(context.local_context(), assoc_const_decl_id,
+                               assoc_const_decl);
   auto const_id = context.local_constant_values().Get(assoc_const_decl_id);
   return {assoc_const_decl.assoc_const_id, const_id};
 }
@@ -1499,9 +1508,10 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
                          inst.base_type_inst_id, base_type_const_id);
 
   // Create a corresponding instruction to represent the declaration.
-  auto inst_id = resolver.local_context().AddInstInNoBlock(
-      resolver.local_context().MakeImportedLocAndInst<SemIR::BaseDecl>(
-          AddImportIRInst(resolver, import_inst_id),
+  auto inst_id = AddInstInNoBlock(
+      resolver.local_context(),
+      MakeImportedLocIdAndInst<SemIR::BaseDecl>(
+          resolver.local_context(), AddImportIRInst(resolver, import_inst_id),
           {.type_id =
                resolver.local_context().types().GetTypeIdForTypeConstantId(
                    type_const_id),
@@ -1629,8 +1639,10 @@ static auto MakeIncompleteClass(ImportContext& context,
   SemIR::ClassDecl class_decl = {.type_id = SemIR::TypeType::SingletonTypeId,
                                  .class_id = SemIR::ClassId::None,
                                  .decl_block_id = SemIR::InstBlockId::Empty};
-  auto class_decl_id = context.local_context().AddPlaceholderInstInNoBlock(
-      context.local_context().MakeImportedLocAndInst(
+  auto class_decl_id = AddPlaceholderInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst(
+          context.local_context(),
           AddImportIRInst(context, import_class.latest_decl_id()), class_decl));
   // Regardless of whether ClassDecl is a complete type, we first need an
   // incomplete type so that any references have something to point at.
@@ -1646,8 +1658,8 @@ static auto MakeIncompleteClass(ImportContext& context,
   }
 
   // Write the class ID into the ClassDecl.
-  context.local_context().ReplaceInstBeforeConstantUse(class_decl_id,
-                                                       class_decl);
+  ReplaceInstBeforeConstantUse(context.local_context(), class_decl_id,
+                               class_decl);
   auto self_const_id = context.local_constant_values().Get(class_decl_id);
   return {class_decl.class_id, self_const_id};
 }
@@ -1861,9 +1873,10 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry();
   }
-  auto inst_id = resolver.local_context().AddInstInNoBlock(
-      resolver.local_context().MakeImportedLocAndInst<SemIR::FieldDecl>(
-          AddImportIRInst(resolver, import_inst_id),
+  auto inst_id = AddInstInNoBlock(
+      resolver.local_context(),
+      MakeImportedLocIdAndInst<SemIR::FieldDecl>(
+          resolver.local_context(), AddImportIRInst(resolver, import_inst_id),
           {.type_id =
                resolver.local_context().types().GetTypeIdForTypeConstantId(
                    const_id),
@@ -1883,8 +1896,10 @@ static auto MakeFunctionDecl(ImportContext& context,
       .type_id = SemIR::TypeId::None,
       .function_id = SemIR::FunctionId::None,
       .decl_block_id = SemIR::InstBlockId::Empty};
-  auto function_decl_id = context.local_context().AddPlaceholderInstInNoBlock(
-      context.local_context().MakeImportedLocAndInst(
+  auto function_decl_id = AddPlaceholderInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst(
+          context.local_context(),
           AddImportIRInst(context, import_function.first_decl_id()),
           function_decl));
 
@@ -1898,8 +1913,8 @@ static auto MakeFunctionDecl(ImportContext& context,
       context.local_context(), function_decl.function_id, specific_id);
 
   // Write the function ID and type into the FunctionDecl.
-  context.local_context().ReplaceInstBeforeConstantUse(function_decl_id,
-                                                       function_decl);
+  ReplaceInstBeforeConstantUse(context.local_context(), function_decl_id,
+                               function_decl);
   return {function_decl.function_id,
           context.local_constant_values().Get(function_decl_id)};
 }
@@ -2056,8 +2071,10 @@ static auto MakeImplDeclaration(ImportContext& context,
     -> std::pair<SemIR::ImplId, SemIR::ConstantId> {
   SemIR::ImplDecl impl_decl = {.impl_id = SemIR::ImplId::None,
                                .decl_block_id = SemIR::InstBlockId::Empty};
-  auto impl_decl_id = context.local_context().AddPlaceholderInstInNoBlock(
-      context.local_context().MakeImportedLocAndInst(
+  auto impl_decl_id = AddPlaceholderInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst(
+          context.local_context(),
           AddImportIRInst(context, import_impl.latest_decl_id()), impl_decl));
   impl_decl.impl_id = context.local_impls().Add(
       {GetIncompleteLocalEntityBase(context, impl_decl_id, import_impl),
@@ -2066,7 +2083,8 @@ static auto MakeImplDeclaration(ImportContext& context,
         .witness_id = witness_id}});
 
   // Write the impl ID into the ImplDecl.
-  context.local_context().ReplaceInstBeforeConstantUse(impl_decl_id, impl_decl);
+  ReplaceInstBeforeConstantUse(context.local_context(), impl_decl_id,
+                               impl_decl);
   return {impl_decl.impl_id, context.local_constant_values().Get(impl_decl_id)};
 }
 
@@ -2203,8 +2221,10 @@ static auto MakeInterfaceDecl(ImportContext& context,
       .type_id = SemIR::TypeType::SingletonTypeId,
       .interface_id = SemIR::InterfaceId::None,
       .decl_block_id = SemIR::InstBlockId::Empty};
-  auto interface_decl_id = context.local_context().AddPlaceholderInstInNoBlock(
-      context.local_context().MakeImportedLocAndInst(
+  auto interface_decl_id = AddPlaceholderInstInNoBlock(
+      context.local_context(),
+      MakeImportedLocIdAndInst(
+          context.local_context(),
           AddImportIRInst(context, import_interface.first_owning_decl_id),
           interface_decl));
 
@@ -2221,8 +2241,8 @@ static auto MakeInterfaceDecl(ImportContext& context,
   }
 
   // Write the interface ID into the InterfaceDecl.
-  context.local_context().ReplaceInstBeforeConstantUse(interface_decl_id,
-                                                       interface_decl);
+  ReplaceInstBeforeConstantUse(context.local_context(), interface_decl_id,
+                               interface_decl);
   return {interface_decl.interface_id,
           context.local_constant_values().Get(interface_decl_id)};
 }
@@ -2539,9 +2559,11 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
       SemIR::Namespace{.type_id = namespace_type_id,
                        .name_scope_id = SemIR::NameScopeId::None,
                        .import_id = SemIR::AbsoluteInstId::None};
-  auto inst_id = resolver.local_context().AddPlaceholderInstInNoBlock(
-      resolver.local_context().MakeImportedLocAndInst(
-          AddImportIRInst(resolver, import_inst_id), namespace_decl));
+  auto inst_id = AddPlaceholderInstInNoBlock(
+      resolver.local_context(),
+      MakeImportedLocIdAndInst(resolver.local_context(),
+                               AddImportIRInst(resolver, import_inst_id),
+                               namespace_decl));
 
   auto name_id = GetLocalNameId(resolver, name_scope.name_id());
   namespace_decl.name_scope_id =
@@ -2551,8 +2573,8 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
   resolver.local_name_scopes()
       .Get(namespace_decl.name_scope_id)
       .set_is_closed_import(true);
-  resolver.local_context().ReplaceInstBeforeConstantUse(inst_id,
-                                                        namespace_decl);
+  ReplaceInstBeforeConstantUse(resolver.local_context(), inst_id,
+                               namespace_decl);
   return {.const_id = resolver.local_constant_values().Get(inst_id)};
 }
 
@@ -3209,4 +3231,38 @@ auto ImportImpl(Context& context, SemIR::ImportIRId import_ir_id,
   context.generic_region_stack().Pop();
 }
 
+// Returns whether a parse node associated with an imported instruction of kind
+// `imported_kind` is usable as the location of a corresponding local
+// instruction of kind `local_kind`.
+static auto HasCompatibleImportedNodeKind(SemIR::InstKind imported_kind,
+                                          SemIR::InstKind local_kind) -> bool {
+  if (imported_kind == local_kind) {
+    return true;
+  }
+  if (imported_kind == SemIR::ImportDecl::Kind &&
+      local_kind == SemIR::Namespace::Kind) {
+    static_assert(
+        std::is_convertible_v<decltype(SemIR::ImportDecl::Kind)::TypedNodeId,
+                              decltype(SemIR::Namespace::Kind)::TypedNodeId>);
+    return true;
+  }
+  return false;
+}
+
+namespace Internal {
+
+auto CheckCompatibleImportedNodeKind(Context& context,
+                                     SemIR::ImportIRInstId imported_loc_id,
+                                     SemIR::InstKind kind) -> void {
+  auto& import_ir_inst = context.import_ir_insts().Get(imported_loc_id);
+  const auto* import_ir = context.import_irs().Get(import_ir_inst.ir_id).sem_ir;
+  auto imported_kind = import_ir->insts().Get(import_ir_inst.inst_id).kind();
+  CARBON_CHECK(
+      HasCompatibleImportedNodeKind(imported_kind, kind),
+      "Node of kind {0} created with location of imported node of kind {1}",
+      kind, imported_kind);
+}
+
+}  // namespace Internal
+
 }  // namespace Carbon::Check

+ 24 - 0
toolchain/check/import_ref.h

@@ -48,6 +48,30 @@ auto ImportImplsFromApiFile(Context& context) -> void;
 auto ImportImpl(Context& context, SemIR::ImportIRId import_ir_id,
                 SemIR::ImplId impl_id) -> void;
 
+namespace Internal {
+
+// Checks that the provided imported location has a node kind that is
+// compatible with that of the given instruction.
+auto CheckCompatibleImportedNodeKind(Context& context,
+                                     SemIR::ImportIRInstId imported_loc_id,
+                                     SemIR::InstKind kind) -> void;
+}  // namespace Internal
+
+// Returns a LocIdAndInst for an instruction with an imported location. Checks
+// that the imported location is compatible with the kind of instruction being
+// created.
+template <typename InstT>
+  requires SemIR::Internal::HasNodeId<InstT>
+auto MakeImportedLocIdAndInst(Context& context,
+                              SemIR::ImportIRInstId imported_loc_id, InstT inst)
+    -> SemIR::LocIdAndInst {
+  if constexpr (!SemIR::Internal::HasUntypedNodeId<InstT>) {
+    Internal::CheckCompatibleImportedNodeKind(context, imported_loc_id,
+                                              InstT::Kind);
+  }
+  return SemIR::LocIdAndInst::UncheckedLoc(imported_loc_id, inst);
+}
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_IMPORT_REF_H_

+ 136 - 0
toolchain/check/inst.cpp

@@ -0,0 +1,136 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/check/inst.h"
+
+#include "common/vlog.h"
+#include "toolchain/check/context.h"
+#include "toolchain/check/eval.h"
+
+namespace Carbon::Check {
+
+// Finish producing an instruction. Set its constant value, and register it in
+// any applicable instruction lists.
+static auto FinishInst(Context& context, SemIR::InstId inst_id,
+                       SemIR::Inst inst) -> void {
+  GenericRegionStack::DependencyKind dep_kind =
+      GenericRegionStack::DependencyKind::None;
+
+  // If the instruction has a symbolic constant type, track that we need to
+  // substitute into it.
+  if (context.constant_values().DependsOnGenericParameter(
+          context.types().GetConstantId(inst.type_id()))) {
+    dep_kind |= GenericRegionStack::DependencyKind::SymbolicType;
+  }
+
+  // If the instruction has a constant value, compute it.
+  auto const_id = TryEvalInst(context, inst_id, inst);
+  context.constant_values().Set(inst_id, const_id);
+  if (const_id.is_constant()) {
+    CARBON_VLOG_TO(context.vlog_stream(), "Constant: {0} -> {1}\n", inst,
+                   context.constant_values().GetInstId(const_id));
+
+    // If the constant value is symbolic, track that we need to substitute into
+    // it.
+    if (context.constant_values().DependsOnGenericParameter(const_id)) {
+      dep_kind |= GenericRegionStack::DependencyKind::SymbolicConstant;
+    }
+  }
+
+  // Keep track of dependent instructions.
+  if (dep_kind != GenericRegionStack::DependencyKind::None) {
+    // TODO: Also check for template-dependent instructions.
+    context.generic_region_stack().AddDependentInst(
+        {.inst_id = inst_id, .kind = dep_kind});
+  }
+}
+
+auto AddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
+  context.inst_block_stack().AddInstId(inst_id);
+  return inst_id;
+}
+
+auto AddInstInNoBlock(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
+  CARBON_VLOG_TO(context.vlog_stream(), "AddInst: {0}\n", loc_id_and_inst.inst);
+  FinishInst(context, inst_id, loc_id_and_inst.inst);
+  return inst_id;
+}
+
+auto AddPatternInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
+  context.pattern_block_stack().AddInstId(inst_id);
+  return inst_id;
+}
+
+auto GetOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  if (loc_id_and_inst.loc_id.is_implicit()) {
+    auto const_id =
+        TryEvalInst(context, SemIR::InstId::None, loc_id_and_inst.inst);
+    if (const_id.has_value()) {
+      CARBON_VLOG_TO(context.vlog_stream(), "GetOrAddInst: constant: {0}\n",
+                     loc_id_and_inst.inst);
+      return context.constant_values().GetInstId(const_id);
+    }
+  }
+  // TODO: For an implicit instruction, this reattempts evaluation.
+  return AddInst(context, loc_id_and_inst);
+}
+
+auto AddPlaceholderInstInNoBlock(Context& context,
+                                 SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
+  CARBON_VLOG_TO(context.vlog_stream(), "AddPlaceholderInst: {0}\n",
+                 loc_id_and_inst.inst);
+  context.constant_values().Set(inst_id, SemIR::ConstantId::None);
+  return inst_id;
+}
+
+auto AddPlaceholderInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  auto inst_id = AddPlaceholderInstInNoBlock(context, loc_id_and_inst);
+  context.inst_block_stack().AddInstId(inst_id);
+  return inst_id;
+}
+
+auto ReplaceLocIdAndInstBeforeConstantUse(Context& context,
+                                          SemIR::InstId inst_id,
+                                          SemIR::LocIdAndInst loc_id_and_inst)
+    -> void {
+  context.sem_ir().insts().SetLocIdAndInst(inst_id, loc_id_and_inst);
+  CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
+                 loc_id_and_inst.inst);
+  FinishInst(context, inst_id, loc_id_and_inst.inst);
+}
+
+auto ReplaceInstBeforeConstantUse(Context& context, SemIR::InstId inst_id,
+                                  SemIR::Inst inst) -> void {
+  context.sem_ir().insts().Set(inst_id, inst);
+  CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
+                 inst);
+  FinishInst(context, inst_id, inst);
+}
+
+auto ReplaceInstPreservingConstantValue(Context& context, SemIR::InstId inst_id,
+                                        SemIR::Inst inst) -> void {
+  auto old_const_id = context.constant_values().Get(inst_id);
+  context.sem_ir().insts().Set(inst_id, inst);
+  CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
+                 inst);
+  auto new_const_id = TryEvalInst(context, inst_id, inst);
+  CARBON_CHECK(old_const_id == new_const_id);
+}
+
+auto SetNamespaceNodeId(Context& context, SemIR::InstId inst_id,
+                        Parse::NodeId node_id) -> void {
+  context.sem_ir().insts().SetLocId(inst_id, SemIR::LocId(node_id));
+}
+
+}  // namespace Carbon::Check

+ 120 - 0
toolchain/check/inst.h

@@ -0,0 +1,120 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_CHECK_INST_H_
+#define CARBON_TOOLCHAIN_CHECK_INST_H_
+
+#include "toolchain/check/context.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst.h"
+
+namespace Carbon::Check {
+
+// Adds an instruction to the current block, returning the produced ID.
+auto AddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId;
+
+// Convenience for AddInst with typed nodes.
+template <typename InstT, typename LocT>
+auto AddInst(Context& context, LocT loc, InstT inst)
+    -> decltype(AddInst(context, SemIR::LocIdAndInst(loc, inst))) {
+  return AddInst(context, SemIR::LocIdAndInst(loc, inst));
+}
+
+// Pushes a parse tree node onto the stack, storing the SemIR::Inst as the
+// result.
+template <typename InstT>
+  requires(SemIR::Internal::HasNodeId<InstT>)
+auto AddInstAndPush(Context& context,
+                    typename decltype(InstT::Kind)::TypedNodeId node_id,
+                    InstT inst) -> void {
+  context.node_stack().Push(node_id, AddInst(context, node_id, inst));
+}
+
+// Adds an instruction in no block, returning the produced ID. Should be used
+// rarely.
+auto AddInstInNoBlock(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId;
+
+// Convenience for AddInstInNoBlock with typed nodes.
+template <typename InstT, typename LocT>
+auto AddInstInNoBlock(Context& context, LocT loc, InstT inst)
+    -> decltype(AddInstInNoBlock(context, SemIR::LocIdAndInst(loc, inst))) {
+  return AddInstInNoBlock(context, SemIR::LocIdAndInst(loc, inst));
+}
+
+// If the instruction has an implicit location and a constant value, returns
+// the constant value's instruction ID. Otherwise, same as AddInst.
+auto GetOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId;
+
+// Convenience for GetOrAddInst with typed nodes.
+template <typename InstT, typename LocT>
+auto GetOrAddInst(Context& context, LocT loc, InstT inst)
+    -> decltype(GetOrAddInst(context, SemIR::LocIdAndInst(loc, inst))) {
+  return GetOrAddInst(context, SemIR::LocIdAndInst(loc, inst));
+}
+
+// Adds an instruction to the current pattern block, returning the produced
+// ID.
+// TODO: Is it possible to remove this and pattern_block_stack, now that
+// we have BeginSubpattern etc. instead?
+auto AddPatternInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId;
+
+// Convenience for AddPatternInst with typed nodes.
+template <typename InstT>
+  requires(SemIR::Internal::HasNodeId<InstT>)
+auto AddPatternInst(Context& context,
+                    typename decltype(InstT::Kind)::TypedNodeId node_id,
+                    InstT inst) -> SemIR::InstId {
+  return AddPatternInst(context, SemIR::LocIdAndInst(node_id, inst));
+}
+
+// Adds an instruction to the current block, returning the produced ID. The
+// instruction is a placeholder that is expected to be replaced by
+// `ReplaceInstBeforeConstantUse`.
+auto AddPlaceholderInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId;
+
+// Adds an instruction in no block, returning the produced ID. Should be used
+// rarely. The instruction is a placeholder that is expected to be replaced by
+// `ReplaceInstBeforeConstantUse`.
+auto AddPlaceholderInstInNoBlock(Context& context,
+                                 SemIR::LocIdAndInst loc_id_and_inst)
+    -> SemIR::InstId;
+
+// Replaces the instruction at `inst_id` with `loc_id_and_inst`. The
+// instruction is required to not have been used in any constant evaluation,
+// either because it's newly created and entirely unused, or because it's only
+// used in a position that constant evaluation ignores, such as a return slot.
+auto ReplaceLocIdAndInstBeforeConstantUse(Context& context,
+                                          SemIR::InstId inst_id,
+                                          SemIR::LocIdAndInst loc_id_and_inst)
+    -> void;
+
+// Replaces the instruction at `inst_id` with `inst`, not affecting location.
+// The instruction is required to not have been used in any constant
+// evaluation, either because it's newly created and entirely unused, or
+// because it's only used in a position that constant evaluation ignores, such
+// as a return slot.
+auto ReplaceInstBeforeConstantUse(Context& context, SemIR::InstId inst_id,
+                                  SemIR::Inst inst) -> void;
+
+// Replaces the instruction at `inst_id` with `inst`, not affecting location.
+// The instruction is required to not change its constant value.
+auto ReplaceInstPreservingConstantValue(Context& context, SemIR::InstId inst_id,
+                                        SemIR::Inst inst) -> void;
+
+// Sets only the parse node of an instruction. This is only used when setting
+// the parse node of an imported namespace. Versus
+// ReplaceInstBeforeConstantUse, it is safe to use after the namespace is used
+// in constant evaluation. It's exposed this way mainly so that `insts()` can
+// remain const.
+auto SetNamespaceNodeId(Context& context, SemIR::InstId inst_id,
+                        Parse::NodeId node_id) -> void;
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_INST_H_

+ 3 - 2
toolchain/check/interface.cpp

@@ -7,6 +7,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/generic.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/type.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
@@ -36,8 +37,8 @@ auto BuildAssociatedEntity(Context& context, SemIR::InterfaceId interface_id,
   // Name lookup for the declaration's name should name the associated entity,
   // not the declaration itself.
   auto type_id = GetAssociatedEntityType(context, self_type_id);
-  return context.AddInst<SemIR::AssociatedEntity>(
-      context.insts().GetLocId(decl_id),
+  return AddInst<SemIR::AssociatedEntity>(
+      context, context.insts().GetLocId(decl_id),
       {.type_id = type_id, .index = index, .decl_id = decl_id});
 }
 

+ 5 - 4
toolchain/check/literal.cpp

@@ -14,10 +14,11 @@ namespace Carbon::Check {
 
 auto MakeIntLiteral(Context& context, Parse::NodeId node_id, IntId int_id)
     -> SemIR::InstId {
-  return context.AddInst<SemIR::IntValue>(
-      node_id, {.type_id = GetSingletonType(
-                    context, SemIR::IntLiteralType::SingletonInstId),
-                .int_id = int_id});
+  return AddInst<SemIR::IntValue>(
+      context, node_id,
+      {.type_id =
+           GetSingletonType(context, SemIR::IntLiteralType::SingletonInstId),
+       .int_id = int_id});
 }
 
 auto MakeIntTypeLiteral(Context& context, Parse::NodeId node_id,

+ 39 - 33
toolchain/check/member_access.cpp

@@ -173,10 +173,10 @@ static auto AccessMemberOfImplWitness(Context& context, SemIR::LocId loc_id,
       context, loc_id, interface_specific_id, assoc_entity->decl_id,
       self_type_id, witness_id);
 
-  return context.GetOrAddInst<SemIR::ImplWitnessAccess>(
-      loc_id, {.type_id = assoc_type_id,
-               .witness_id = witness_id,
-               .index = assoc_entity->index});
+  return GetOrAddInst<SemIR::ImplWitnessAccess>(context, loc_id,
+                                                {.type_id = assoc_type_id,
+                                                 .witness_id = witness_id,
+                                                 .index = assoc_entity->index});
 }
 
 // Performs impl lookup for a member name expression. This finds the relevant
@@ -262,19 +262,21 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
           .Get(result.scope_result.target_inst_id())
           .is_symbolic()) {
     result.scope_result = SemIR::ScopeLookupResult::MakeFound(
-        context.GetOrAddInst<SemIR::SpecificConstant>(
-            loc_id, {.type_id = type_id,
-                     .inst_id = result.scope_result.target_inst_id(),
-                     .specific_id = result.specific_id}),
+        GetOrAddInst<SemIR::SpecificConstant>(
+            context, loc_id,
+            {.type_id = type_id,
+             .inst_id = result.scope_result.target_inst_id(),
+             .specific_id = result.specific_id}),
         SemIR::AccessKind::Public);
   }
 
   // TODO: Use a different kind of instruction that also references the
   // `base_id` so that `SemIR` consumers can find it.
-  auto member_id = context.GetOrAddInst<SemIR::NameRef>(
-      loc_id, {.type_id = type_id,
-               .name_id = name_id,
-               .value_id = result.scope_result.target_inst_id()});
+  auto member_id = GetOrAddInst<SemIR::NameRef>(
+      context, loc_id,
+      {.type_id = type_id,
+       .name_id = name_id,
+       .value_id = result.scope_result.target_inst_id()});
 
   // If member name lookup finds an associated entity name, and the scope is not
   // a facet type, perform impl lookup.
@@ -308,10 +310,11 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
         for (auto base_interface : facet_type_info.impls_constraints) {
           // Get the witness that `T` implements `base_type_id`.
           if (base_interface == *assoc_interface) {
-            witness_inst_id = context.GetOrAddInst<SemIR::FacetAccessWitness>(
-                loc_id, {.type_id = GetSingletonType(
-                             context, SemIR::WitnessType::SingletonInstId),
-                         .facet_value_inst_id = base_id});
+            witness_inst_id = GetOrAddInst<SemIR::FacetAccessWitness>(
+                context, loc_id,
+                {.type_id = GetSingletonType(
+                     context, SemIR::WitnessType::SingletonInstId),
+                 .facet_value_inst_id = base_id});
             // TODO: Result will eventually be a facet type witness instead of
             // an interface witness. Will need to use the index
             // `*assoc_interface` was found in
@@ -366,11 +369,12 @@ static auto PerformInstanceBinding(Context& context, SemIR::LocId loc_id,
       return member_id;
     }
 
-    return context.GetOrAddInst<SemIR::BoundMethod>(
-        loc_id, {.type_id = GetSingletonType(
-                     context, SemIR::BoundMethodType::SingletonInstId),
-                 .object_id = base_id,
-                 .function_decl_id = member_id});
+    return GetOrAddInst<SemIR::BoundMethod>(
+        context, loc_id,
+        {.type_id =
+             GetSingletonType(context, SemIR::BoundMethodType::SingletonInstId),
+         .object_id = base_id,
+         .function_decl_id = member_id});
   }
 
   // Otherwise, if it's a field, form a class element access.
@@ -388,10 +392,11 @@ static auto PerformInstanceBinding(Context& context, SemIR::LocId loc_id,
                  "Non-constant value {0} of unbound element type",
                  context.insts().Get(member_id));
     auto index = GetClassElementIndex(context, element_id);
-    auto access_id = context.GetOrAddInst<SemIR::ClassElementAccess>(
-        loc_id, {.type_id = unbound_element_type->element_type_id,
-                 .base_id = base_id,
-                 .index = index});
+    auto access_id = GetOrAddInst<SemIR::ClassElementAccess>(
+        context, loc_id,
+        {.type_id = unbound_element_type->element_type_id,
+         .base_id = base_id,
+         .index = index});
     if (SemIR::GetExprCategory(context.sem_ir(), base_id) ==
             SemIR::ExprCategory::Value &&
         SemIR::GetExprCategory(context.sem_ir(), access_id) !=
@@ -477,10 +482,11 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
         if (name_id == field.name_id) {
           // TODO: Model this as producing a lookup result, and do instance
           // binding separately. Perhaps a struct type should be a name scope.
-          return context.GetOrAddInst<SemIR::StructAccess>(
-              loc_id, {.type_id = field.type_id,
-                       .struct_id = base_id,
-                       .index = SemIR::ElementIndex(i)});
+          return GetOrAddInst<SemIR::StructAccess>(
+              context, loc_id,
+              {.type_id = field.type_id,
+               .struct_id = base_id,
+               .index = SemIR::ElementIndex(i)});
         }
       }
       CARBON_DIAGNOSTIC(QualifiedExprNameNotFound, Error,
@@ -613,10 +619,10 @@ auto PerformTupleAccess(Context& context, SemIR::LocId loc_id,
   element_type_id = type_block[index_val->getZExtValue()];
   auto tuple_index = SemIR::ElementIndex(index_val->getZExtValue());
 
-  return context.GetOrAddInst<SemIR::TupleAccess>(loc_id,
-                                                  {.type_id = element_type_id,
-                                                   .tuple_id = tuple_inst_id,
-                                                   .index = tuple_index});
+  return GetOrAddInst<SemIR::TupleAccess>(context, loc_id,
+                                          {.type_id = element_type_id,
+                                           .tuple_id = tuple_inst_id,
+                                           .index = tuple_index});
 }
 
 }  // namespace Carbon::Check

+ 25 - 23
toolchain/check/pattern_match.cpp

@@ -146,10 +146,11 @@ static auto InsertHere(Context& context, SemIR::ExprRegionId region_id)
       context.inst_block_stack().AddInstId(exit_block.front());
       return region.result_id;
     }
-    return context.AddInst<SemIR::SpliceBlock>(
-        loc_id, {.type_id = context.insts().Get(region.result_id).type_id(),
-                 .block_id = region.block_ids.front(),
-                 .result_id = region.result_id});
+    return AddInst<SemIR::SpliceBlock>(
+        context, loc_id,
+        {.type_id = context.insts().Get(region.result_id).type_id(),
+         .block_id = region.block_ids.front(),
+         .result_id = region.result_id});
   }
   if (context.region_stack().empty()) {
     context.TODO(loc_id,
@@ -157,8 +158,8 @@ static auto InsertHere(Context& context, SemIR::ExprRegionId region_id)
                  "functions.");
     return SemIR::ErrorInst::SingletonInstId;
   }
-  context.AddInst(SemIR::LocIdAndInst::NoLoc<SemIR::Branch>(
-      {.target_id = region.block_ids.front()}));
+  AddInst(context, SemIR::LocIdAndInst::NoLoc<SemIR::Branch>(
+                       {.target_id = region.block_ids.front()}));
   context.inst_block_stack().Pop();
   // TODO: this will cumulatively cost O(MN) running time for M blocks
   // at the Nth level of the stack. Figure out how to do better.
@@ -219,7 +220,7 @@ auto MatchContext::EmitPatternMatch(Context& context,
       auto bind_name = context.insts().GetAs<SemIR::AnyBindName>(bind_name_id);
       CARBON_CHECK(!bind_name.value_id.has_value());
       bind_name.value_id = value_id;
-      context.ReplaceInstBeforeConstantUse(bind_name_id, bind_name);
+      ReplaceInstBeforeConstantUse(context, bind_name_id, bind_name);
       context.inst_block_stack().AddInstId(bind_name_id);
       break;
     }
@@ -251,8 +252,8 @@ auto MatchContext::EmitPatternMatch(Context& context,
           return;
       }
       auto scrutinee_ref = context.insts().Get(scrutinee_ref_id);
-      auto new_scrutinee = context.AddInst<SemIR::AddrOf>(
-          context.insts().GetLocId(scrutinee_ref_id),
+      auto new_scrutinee = AddInst<SemIR::AddrOf>(
+          context, context.insts().GetLocId(scrutinee_ref_id),
           {.type_id = GetPointerType(context, scrutinee_ref.type_id()),
            .lvalue_id = scrutinee_ref_id});
       AddWork(
@@ -285,13 +286,13 @@ auto MatchContext::EmitPatternMatch(Context& context,
           if (param_pattern.runtime_index ==
               SemIR::RuntimeParamIndex::Unknown) {
             param_pattern.runtime_index = NextRuntimeIndex();
-            context.ReplaceInstBeforeConstantUse(entry.pattern_id,
-                                                 param_pattern);
+            ReplaceInstBeforeConstantUse(context, entry.pattern_id,
+                                         param_pattern);
           }
           AddWork(
               {.pattern_id = param_pattern.subpattern_id,
-               .scrutinee_id = context.AddInst<SemIR::ValueParam>(
-                   pattern.loc_id,
+               .scrutinee_id = AddInst<SemIR::ValueParam>(
+                   context, pattern.loc_id,
                    {.type_id = param_pattern.type_id,
                     .runtime_index = param_pattern.runtime_index,
                     .pretty_name_id = GetPrettyName(context, param_pattern)})});
@@ -322,13 +323,13 @@ auto MatchContext::EmitPatternMatch(Context& context,
           if (param_pattern.runtime_index ==
               SemIR::RuntimeParamIndex::Unknown) {
             param_pattern.runtime_index = NextRuntimeIndex();
-            context.ReplaceInstBeforeConstantUse(entry.pattern_id,
-                                                 param_pattern);
+            ReplaceInstBeforeConstantUse(context, entry.pattern_id,
+                                         param_pattern);
           }
           AddWork(
               {.pattern_id = param_pattern.subpattern_id,
-               .scrutinee_id = context.AddInst<SemIR::OutParam>(
-                   pattern.loc_id,
+               .scrutinee_id = AddInst<SemIR::OutParam>(
+                   context, pattern.loc_id,
                    {.type_id = param_pattern.type_id,
                     .runtime_index = param_pattern.runtime_index,
                     .pretty_name_id = GetPrettyName(context, param_pattern)})});
@@ -342,10 +343,11 @@ auto MatchContext::EmitPatternMatch(Context& context,
     }
     case CARBON_KIND(SemIR::ReturnSlotPattern return_slot_pattern): {
       CARBON_CHECK(kind_ == MatchKind::Callee);
-      auto return_slot_id = context.AddInst<SemIR::ReturnSlot>(
-          pattern.loc_id, {.type_id = return_slot_pattern.type_id,
-                           .type_inst_id = return_slot_pattern.type_inst_id,
-                           .storage_id = entry.scrutinee_id});
+      auto return_slot_id = AddInst<SemIR::ReturnSlot>(
+          context, pattern.loc_id,
+          {.type_id = return_slot_pattern.type_id,
+           .type_inst_id = return_slot_pattern.type_inst_id,
+           .storage_id = entry.scrutinee_id});
       bool already_in_lookup =
           context.scope_stack()
               .LookupOrAddName(SemIR::NameId::ReturnSlot, return_slot_id)
@@ -367,8 +369,8 @@ auto MatchContext::EmitPatternMatch(Context& context,
             Initialize(context, pattern.loc_id, var_id, entry.scrutinee_id);
         // TODO: Consider using different instruction kinds for assignment
         // versus initialization.
-        context.AddInst<SemIR::Assign>(pattern.loc_id,
-                                       {.lhs_id = var_id, .rhs_id = init_id});
+        AddInst<SemIR::Assign>(context, pattern.loc_id,
+                               {.lhs_id = var_id, .rhs_id = init_id});
       }
       AddWork(
           {.pattern_id = var_pattern.subpattern_id, .scrutinee_id = var_id});

+ 7 - 6
toolchain/check/pending_block.h

@@ -7,6 +7,7 @@
 
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/sem_ir/inst.h"
 
 namespace Carbon::Check {
@@ -42,7 +43,7 @@ class PendingBlock {
 
   template <typename InstT, typename LocT>
   auto AddInst(LocT loc_id, InstT inst) -> SemIR::InstId {
-    auto inst_id = context_.AddInstInNoBlock(loc_id, inst);
+    auto inst_id = AddInstInNoBlock(context_, loc_id, inst);
     insts_.push_back(inst_id);
     return inst_id;
   }
@@ -65,8 +66,8 @@ class PendingBlock {
     if (insts_.empty()) {
       // 1) The block is empty. Replace `target_id` with an empty splice
       // pointing at `value_id`.
-      context_.ReplaceLocIdAndInstBeforeConstantUse(
-          target_id,
+      ReplaceLocIdAndInstBeforeConstantUse(
+          context_, target_id,
           SemIR::LocIdAndInst(
               value.loc_id,
               SemIR::SpliceBlock{.type_id = value.inst.type_id(),
@@ -75,11 +76,11 @@ class PendingBlock {
     } else if (insts_.size() == 1 && insts_[0] == value_id) {
       // 2) The block is {value_id}. Replace `target_id` with the instruction
       // referred to by `value_id`. This is intended to be the common case.
-      context_.ReplaceLocIdAndInstBeforeConstantUse(target_id, value);
+      ReplaceLocIdAndInstBeforeConstantUse(context_, target_id, value);
     } else {
       // 3) Anything else: splice it into the IR, replacing `target_id`.
-      context_.ReplaceLocIdAndInstBeforeConstantUse(
-          target_id,
+      ReplaceLocIdAndInstBeforeConstantUse(
+          context_, target_id,
           SemIR::LocIdAndInst(
               value.loc_id,
               SemIR::SpliceBlock{.type_id = value.inst.type_id(),

+ 3 - 2
toolchain/check/pointer_dereference.cpp

@@ -7,6 +7,7 @@
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/ids.h"
 
@@ -32,8 +33,8 @@ auto PerformPointerDereference(
   } else if (type_id != SemIR::ErrorInst::SingletonTypeId) {
     diagnose_not_pointer(type_id);
   }
-  return context.AddInst<SemIR::Deref>(
-      node_id, {.type_id = result_type_id, .pointer_id = base_id});
+  return AddInst<SemIR::Deref>(
+      context, node_id, {.type_id = result_type_id, .pointer_id = base_id});
 }
 
 }  // namespace Carbon::Check

+ 7 - 5
toolchain/check/return.cpp

@@ -6,6 +6,7 @@
 
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/inst.h"
 
 namespace Carbon::Check {
 
@@ -126,7 +127,7 @@ auto BuildReturnWithNoExpr(Context& context, Parse::ReturnStatementId node_id)
     diag.Emit();
   }
 
-  context.AddInst<SemIR::Return>(node_id, {});
+  AddInst<SemIR::Return>(context, node_id, {});
 }
 
 auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id,
@@ -167,8 +168,8 @@ auto BuildReturnWithExpr(Context& context, Parse::ReturnStatementId node_id,
         ConvertToValueOfType(context, node_id, expr_id, return_info.type_id);
   }
 
-  context.AddInst<SemIR::ReturnExpr>(
-      node_id, {.expr_id = expr_id, .dest_id = return_slot_id});
+  AddInst<SemIR::ReturnExpr>(context, node_id,
+                             {.expr_id = expr_id, .dest_id = return_slot_id});
 }
 
 auto BuildReturnVar(Context& context, Parse::ReturnStatementId node_id)
@@ -192,8 +193,9 @@ auto BuildReturnVar(Context& context, Parse::ReturnStatementId node_id)
     return_slot_id = SemIR::InstId::None;
   }
 
-  context.AddInst<SemIR::ReturnExpr>(
-      node_id, {.expr_id = returned_var_id, .dest_id = return_slot_id});
+  AddInst<SemIR::ReturnExpr>(
+      context, node_id,
+      {.expr_id = returned_var_id, .dest_id = return_slot_id});
 }
 
 }  // namespace Carbon::Check

+ 5 - 2
toolchain/check/subpattern.cpp

@@ -4,6 +4,8 @@
 
 #include "toolchain/check/subpattern.h"
 
+#include "toolchain/check/inst.h"
+
 namespace Carbon::Check {
 
 auto BeginSubpattern(Context& context) -> void {
@@ -16,8 +18,9 @@ auto EndSubpatternAsExpr(Context& context, SemIR::InstId result_id)
   if (context.region_stack().PeekRegion().size() > 1) {
     // End the exit block with a branch to a successor block, whose contents
     // will be determined later.
-    context.AddInst(SemIR::LocIdAndInst::NoLoc<SemIR::Branch>(
-        {.target_id = context.inst_blocks().AddDefaultValue()}));
+    AddInst(context,
+            SemIR::LocIdAndInst::NoLoc<SemIR::Branch>(
+                {.target_id = context.inst_blocks().AddDefaultValue()}));
   } else {
     // This single-block region will be inserted as a SpliceBlock, so we don't
     // need control flow out of it.

+ 8 - 5
toolchain/check/type_completion.cpp

@@ -7,6 +7,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/generic.h"
+#include "toolchain/check/inst.h"
 #include "toolchain/check/type.h"
 
 namespace Carbon::Check {
@@ -489,11 +490,13 @@ auto RequireCompleteType(Context& context, SemIR::TypeId type_id,
   // specific type to be complete.
   if (type_id.AsConstantId().is_symbolic()) {
     // TODO: Deduplicate these.
-    context.AddInstInNoBlock(SemIR::LocIdAndInst(
-        loc_id, SemIR::RequireCompleteType{
-                    .type_id = GetSingletonType(
-                        context, SemIR::WitnessType::SingletonInstId),
-                    .complete_type_id = type_id}));
+    AddInstInNoBlock(
+        context,
+        SemIR::LocIdAndInst(
+            loc_id, SemIR::RequireCompleteType{
+                        .type_id = GetSingletonType(
+                            context, SemIR::WitnessType::SingletonInstId),
+                        .complete_type_id = type_id}));
   }
 
   return true;

+ 4 - 4
toolchain/docs/adding_features.md

@@ -285,7 +285,7 @@ If the resulting SemIR needs a new instruction:
             field, as in:
 
             ```
-            SemIR::InstId inst_id = context.AddInst<SemIR::NewInstKindName>(
+            SemIR::InstId inst_id = AddInst<SemIR::NewInstKindName>(context,
                 node_id, {.type_id = SemIR::TypeType::SingletonTypeId, ...});
             ```
 
@@ -302,9 +302,9 @@ If the resulting SemIR needs a new instruction:
 
         ```
         SemIR::TypeId witness_type_id =
-            GetSingletonType(context,SemIR::WitnessType::SingletonInstId);
-        SemIR::InstId inst_id = context.AddInst<SemIR::NewInstKindName>(
-            node_id, {.type_id = witness_type_id, ...});
+            GetSingletonType(context, SemIR::WitnessType::SingletonInstId);
+        SemIR::InstId inst_id = AddInst<SemIR::NewInstKindName>(
+            context, node_id, {.type_id = witness_type_id, ...});
         ```
 
     -   Instructions without types may still be used as arguments to