Преглед на файлове

Defer resolving the eval blocks and value blocks of generics and specifics until we've finished other resolution work. (#4202)

This avoids import cycles, and reduces the number of temporary vectors
we build (and potentially throw away on retry). Import the self specific
when importing a generic, now that there's no risk that will introduce
cycles.

Note that we could take the same approach to import classes, interfaces,
and so on, instead of the current third phase of resolution for those
instructions, but in this PR I'm just addressing the import cycle I'm
currently seeing in a work-in-progress PR.
Richard Smith преди 1 година
родител
ревизия
b2a13afb73
променени са 33 файла, в които са добавени 444 реда и са изтрити 105 реда
  1. 141 46
      toolchain/check/import_ref.cpp
  2. 9 3
      toolchain/check/testdata/class/generic/import.carbon
  3. 4 0
      toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon
  4. 2 0
      toolchain/check/testdata/impl/lookup/import.carbon
  5. 2 0
      toolchain/check/testdata/impl/lookup/no_prelude/import.carbon
  6. 4 0
      toolchain/check/testdata/impl/no_prelude/import_self.carbon
  7. 46 42
      toolchain/check/testdata/impl/no_prelude/interface_args.carbon
  8. 4 0
      toolchain/check/testdata/index/fail_negative_indexing.carbon
  9. 13 11
      toolchain/check/testdata/interface/no_prelude/generic_import.carbon
  10. 4 0
      toolchain/check/testdata/interface/no_prelude/import.carbon
  11. 4 0
      toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon
  12. 4 0
      toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon
  13. 9 0
      toolchain/check/testdata/operators/overloaded/add.carbon
  14. 9 0
      toolchain/check/testdata/operators/overloaded/bit_and.carbon
  15. 4 0
      toolchain/check/testdata/operators/overloaded/bit_complement.carbon
  16. 9 0
      toolchain/check/testdata/operators/overloaded/bit_or.carbon
  17. 9 0
      toolchain/check/testdata/operators/overloaded/bit_xor.carbon
  18. 5 0
      toolchain/check/testdata/operators/overloaded/dec.carbon
  19. 9 0
      toolchain/check/testdata/operators/overloaded/div.carbon
  20. 24 0
      toolchain/check/testdata/operators/overloaded/eq.carbon
  21. 10 0
      toolchain/check/testdata/operators/overloaded/fail_assign_non_ref.carbon
  22. 18 0
      toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon
  23. 9 0
      toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon
  24. 5 0
      toolchain/check/testdata/operators/overloaded/inc.carbon
  25. 9 0
      toolchain/check/testdata/operators/overloaded/left_shift.carbon
  26. 9 0
      toolchain/check/testdata/operators/overloaded/mod.carbon
  27. 9 0
      toolchain/check/testdata/operators/overloaded/mul.carbon
  28. 4 0
      toolchain/check/testdata/operators/overloaded/negate.carbon
  29. 32 0
      toolchain/check/testdata/operators/overloaded/ordered.carbon
  30. 9 0
      toolchain/check/testdata/operators/overloaded/right_shift.carbon
  31. 9 0
      toolchain/check/testdata/operators/overloaded/sub.carbon
  32. 3 2
      toolchain/sem_ir/formatter.cpp
  33. 3 1
      toolchain/sem_ir/generic.cpp

+ 141 - 46
toolchain/check/import_ref.cpp

@@ -226,7 +226,7 @@ class ImportRefResolver {
   // Iteratively resolves an imported instruction's inner references until a
   // constant ID referencing the current IR is produced. See the class comment
   // for more details.
-  auto Resolve(SemIR::InstId inst_id) -> SemIR::ConstantId {
+  auto ResolveOneInst(SemIR::InstId inst_id) -> SemIR::ConstantId {
     work_stack_.push_back({.inst_id = inst_id});
     while (!work_stack_.empty()) {
       auto work = work_stack_.back();
@@ -264,6 +264,14 @@ class ImportRefResolver {
     return constant_id;
   }
 
+  // Performs resolution for one instruction and then performs all work we
+  // deferred.
+  auto Resolve(SemIR::InstId inst_id) -> SemIR::ConstantId {
+    auto const_id = ResolveOneInst(inst_id);
+    PerformPendingWork();
+    return const_id;
+  }
+
   // Wraps constant evaluation with logic to handle constants.
   auto ResolveConstant(SemIR::ConstantId import_const_id) -> SemIR::ConstantId {
     return Resolve(GetInstWithConstantValue(import_ir_, import_const_id));
@@ -328,17 +336,24 @@ class ImportRefResolver {
   // Local information associated with an imported generic.
   struct GenericData {
     llvm::SmallVector<SemIR::InstId> bindings;
-    // TODO: Add data for the self specific.
-    llvm::SmallVector<SemIR::InstId> decl_block;
-    llvm::SmallVector<SemIR::InstId> definition_block;
   };
 
   // Local information associated with an imported specific.
   struct SpecificData {
     SemIR::ConstantId generic_const_id;
     llvm::SmallVector<SemIR::InstId> args;
-    llvm::SmallVector<SemIR::InstId> decl_block;
-    llvm::SmallVector<SemIR::InstId> definition_block;
+  };
+
+  // A generic that we have partially imported.
+  struct PendingGeneric {
+    SemIR::GenericId import_id;
+    SemIR::GenericId local_id;
+  };
+
+  // A specific that we have partially imported.
+  struct PendingSpecific {
+    SemIR::SpecificId import_id;
+    SemIR::SpecificId local_id;
   };
 
   // Looks to see if an instruction has been resolved. If a constant is only
@@ -518,26 +533,7 @@ class ImportRefResolver {
     }
 
     const auto& generic = import_ir_.generics().Get(generic_id);
-    return {
-        .bindings = GetLocalInstBlockContents(generic.bindings_id),
-        .decl_block = GetLocalInstBlockContents(generic.decl_block_id),
-        .definition_block =
-            GetLocalInstBlockContents(generic.definition_block_id),
-    };
-  }
-
-  // Given the local constant values for the elements of the eval block, builds
-  // and returns the eval block for a region of a generic.
-  auto GetLocalEvalBlock(const SemIR::Generic& import_generic,
-                         SemIR::GenericId generic_id,
-                         SemIR::GenericInstIndex::Region region,
-                         llvm::ArrayRef<SemIR::InstId> inst_ids)
-      -> SemIR::InstBlockId {
-    auto import_block_id = import_generic.GetEvalBlock(region);
-    if (!import_block_id.is_valid()) {
-      return SemIR::InstBlockId::Invalid;
-    }
-    return RebuildGenericEvalBlock(context_, generic_id, region, inst_ids);
+    return {.bindings = GetLocalInstBlockContents(generic.bindings_id)};
   }
 
   // Adds the given local generic data to the given generic.
@@ -552,14 +548,9 @@ class ImportRefResolver {
     auto& new_generic = context_.generics().Get(new_generic_id);
     new_generic.bindings_id = GetLocalCanonicalInstBlockId(
         import_generic.bindings_id, generic_data.bindings);
-    // TODO: Import or rebuild the self specific.
-    new_generic.decl_block_id = GetLocalEvalBlock(
-        import_generic, new_generic_id,
-        SemIR::GenericInstIndex::Region::Declaration, generic_data.decl_block);
-    new_generic.definition_block_id =
-        GetLocalEvalBlock(import_generic, new_generic_id,
-                          SemIR::GenericInstIndex::Region::Definition,
-                          generic_data.definition_block);
+    // Fill in the remaining information in FinishPendingGeneric.
+    pending_generics_.push_back(
+        {.import_id = import_generic_id, .local_id = new_generic_id});
   }
 
   // Gets a local constant value corresponding to an imported generic ID. May
@@ -610,9 +601,6 @@ class ImportRefResolver {
     return {
         .generic_const_id = GetLocalConstantId(specific.generic_id),
         .args = GetLocalInstBlockContents(specific.args_id),
-        .decl_block = GetLocalInstBlockContents(specific.decl_block_id),
-        .definition_block =
-            GetLocalInstBlockContents(specific.definition_block_id),
     };
   }
 
@@ -631,17 +619,16 @@ class ImportRefResolver {
     auto args_id =
         GetLocalCanonicalInstBlockId(import_specific.args_id, data.args);
 
-    // Populate the specific. Note that we might get data from multiple
-    // different import IRs, so only import data we don't already have.
+    // Get the specific.
     auto specific_id = context_.specifics().GetOrAdd(generic_id, args_id);
+
+    // Fill in the remaining information in FinishPendingSpecific, if necessary.
     auto& specific = context_.specifics().Get(specific_id);
-    if (!specific.decl_block_id.is_valid()) {
-      specific.decl_block_id =
-          GetLocalInstBlockId(import_specific.decl_block_id, data.decl_block);
-    }
-    if (!specific.definition_block_id.is_valid()) {
-      specific.definition_block_id = GetLocalInstBlockId(
-          import_specific.definition_block_id, data.definition_block);
+    if (!specific.decl_block_id.is_valid() ||
+        (import_specific.definition_block_id.is_valid() &&
+         !specific.definition_block_id.is_valid())) {
+      pending_specifics_.push_back(
+          {.import_id = import_specific_id, .local_id = specific_id});
     }
     return specific_id;
   }
@@ -1811,6 +1798,110 @@ class ImportRefResolver {
          .element_type_id = context_.GetTypeIdForTypeConstant(elem_const_id)});
   }
 
+  // Perform any work that we deferred until the end of the main Resolve loop.
+  auto PerformPendingWork() -> void {
+    // Note that the individual Finish steps can add new pending work, so keep
+    // going until we have no more work to do.
+    while (!pending_generics_.empty() || !pending_specifics_.empty()) {
+      while (!pending_generics_.empty()) {
+        FinishPendingGeneric(pending_generics_.pop_back_val());
+      }
+      while (!pending_specifics_.empty()) {
+        FinishPendingSpecific(pending_specifics_.pop_back_val());
+      }
+    }
+  }
+
+  // Resolves and returns the local contents for an imported instruction block
+  // of constant instructions.
+  auto ResolveLocalInstBlockContents(SemIR::InstBlockId import_block_id)
+      -> llvm::SmallVector<SemIR::InstId> {
+    auto import_block = import_ir_.inst_blocks().Get(import_block_id);
+
+    llvm::SmallVector<SemIR::InstId> inst_ids;
+    inst_ids.reserve(import_block.size());
+    for (auto import_inst_id : import_block) {
+      inst_ids.push_back(
+          context_.constant_values().GetInstId(ResolveOneInst(import_inst_id)));
+    }
+    return inst_ids;
+  }
+
+  // Resolves and returns a local eval block for a region of an imported
+  // generic.
+  auto ResolveLocalEvalBlock(const SemIR::Generic& import_generic,
+                             SemIR::GenericId generic_id,
+                             SemIR::GenericInstIndex::Region region)
+      -> SemIR::InstBlockId {
+    auto import_block_id = import_generic.GetEvalBlock(region);
+    if (!import_block_id.is_valid()) {
+      return SemIR::InstBlockId::Invalid;
+    }
+
+    auto inst_ids = ResolveLocalInstBlockContents(import_block_id);
+    return RebuildGenericEvalBlock(context_, generic_id, region, inst_ids);
+  }
+
+  // Fills in the remaining information in a partially-imported generic.
+  auto FinishPendingGeneric(PendingGeneric pending) -> void {
+    const auto& import_generic = import_ir_.generics().Get(pending.import_id);
+
+    // Don't store the local generic between calls: the generics list can be
+    // reallocated by ResolveLocalEvalBlock importing more specifics.
+
+    auto decl_block_id =
+        ResolveLocalEvalBlock(import_generic, pending.local_id,
+                              SemIR::GenericInstIndex::Region::Declaration);
+    context_.generics().Get(pending.local_id).decl_block_id = decl_block_id;
+
+    auto self_specific_id = MakeSelfSpecific(context_, pending.local_id);
+    context_.generics().Get(pending.local_id).self_specific_id =
+        self_specific_id;
+    pending_specifics_.push_back({.import_id = import_generic.self_specific_id,
+                                  .local_id = self_specific_id});
+
+    auto definition_block_id =
+        ResolveLocalEvalBlock(import_generic, pending.local_id,
+                              SemIR::GenericInstIndex::Region::Definition);
+    context_.generics().Get(pending.local_id).definition_block_id =
+        definition_block_id;
+  }
+
+  // Resolves and returns a local inst block of constant instructions
+  // corresponding to an imported inst block.
+  auto ResolveLocalInstBlock(SemIR::InstBlockId import_block_id)
+      -> SemIR::InstBlockId {
+    if (!import_block_id.is_valid()) {
+      return SemIR::InstBlockId::Invalid;
+    }
+
+    auto inst_ids = ResolveLocalInstBlockContents(import_block_id);
+    return context_.inst_blocks().Add(inst_ids);
+  }
+
+  // Fills in the remaining information in a partially-imported specific.
+  auto FinishPendingSpecific(PendingSpecific pending) -> void {
+    const auto& import_specific = import_ir_.specifics().Get(pending.import_id);
+
+    // Don't store the local specific between calls: the specifics list can be
+    // reallocated by ResolveLocalInstBlock importing more specifics.
+
+    if (!context_.specifics().Get(pending.local_id).decl_block_id.is_valid()) {
+      auto decl_block_id = ResolveLocalInstBlock(import_specific.decl_block_id);
+      context_.specifics().Get(pending.local_id).decl_block_id = decl_block_id;
+    }
+
+    if (!context_.specifics()
+             .Get(pending.local_id)
+             .definition_block_id.is_valid() &&
+        import_specific.definition_block_id.is_valid()) {
+      auto definition_block_id =
+          ResolveLocalInstBlock(import_specific.definition_block_id);
+      context_.specifics().Get(pending.local_id).definition_block_id =
+          definition_block_id;
+    }
+  }
+
   auto import_ir_constant_values() -> SemIR::ConstantValueStore& {
     return context_.import_ir_constant_values()[import_ir_id_.index];
   }
@@ -1821,6 +1912,10 @@ class ImportRefResolver {
   llvm::SmallVector<Work> work_stack_;
   // The size of work_stack_ at the start of resolving the current instruction.
   size_t initial_work_ = 0;
+  // Generics that we have partially imported but not yet finished importing.
+  llvm::SmallVector<PendingGeneric> pending_generics_;
+  // Specifics that we have partially imported but not yet finished importing.
+  llvm::SmallVector<PendingSpecific> pending_specifics_;
 };
 
 // Returns a list of ImportIRInsts equivalent to the ImportRef currently being

+ 9 - 3
toolchain/check/testdata/class/generic/import.carbon

@@ -228,11 +228,11 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.4: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
-// CHECK:STDOUT:   %.5: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, @CompleteClass(i32) [template]
@@ -361,6 +361,8 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T) {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: specific @CompleteClass(i32) {
 // CHECK:STDOUT:   %T => i32
 // CHECK:STDOUT:
@@ -384,11 +386,11 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
-// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, @CompleteClass(i32) [template]
 // CHECK:STDOUT:   %.4: type = unbound_element_type %CompleteClass.3, i32 [template]
 // CHECK:STDOUT:   %F.type.3: type = fn_type @F.1, @CompleteClass(i32) [template]
@@ -526,6 +528,8 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T) {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: specific @CompleteClass(i32) {
 // CHECK:STDOUT:   %T => i32
 // CHECK:STDOUT:
@@ -549,11 +553,11 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
-// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %.4: type = ptr_type i32 [template]
@@ -657,6 +661,8 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T) {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: specific @CompleteClass(constants.%.4) {
 // CHECK:STDOUT:   %T => constants.%.4
 // CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon

@@ -222,3 +222,7 @@ var arr: [i32; 1 + 2] = (3, 4, 3 + 4);
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/impl/lookup/import.carbon

@@ -207,3 +207,5 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2();
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%Self) {}
+// CHECK:STDOUT:

+ 2 - 0
toolchain/check/testdata/impl/lookup/no_prelude/import.carbon

@@ -182,3 +182,5 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2();
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%Self) {}
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/no_prelude/import_self.carbon

@@ -192,3 +192,7 @@ fn F(x: (), y: ()) -> () {
 // CHECK:STDOUT:   return %.loc11_23
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 46 - 42
toolchain/check/testdata/impl/no_prelude/interface_args.carbon

@@ -191,22 +191,22 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %.3: type = interface_type @Action, @Action(%T) [symbolic]
 // CHECK:STDOUT:   %Self.1: @Action.%.1 (%.3) = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %.4: type = interface_type @Action, @Action(%B) [template]
+// CHECK:STDOUT:   %Self.2: %.3 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %Op.type.1: type = fn_type @Op.1 [template]
 // CHECK:STDOUT:   %Op.1: %Op.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %Op.type.2: type = fn_type @Op.1, @Action(%T) [symbolic]
-// CHECK:STDOUT:   %.4: type = assoc_entity_type %.3, %Op.type.2 [symbolic]
-// CHECK:STDOUT:   %.5: %.4 = assoc_entity element0, imports.%import_ref.7 [symbolic]
 // CHECK:STDOUT:   %Op.2: %Op.type.2 = struct_value () [symbolic]
-// CHECK:STDOUT:   %Self.2: %.3 = bind_symbolic_name Self 1 [symbolic]
-// CHECK:STDOUT:   %.6: type = interface_type @Action, @Action(%B) [template]
+// CHECK:STDOUT:   %.5: type = assoc_entity_type %.3, %Op.type.2 [symbolic]
+// CHECK:STDOUT:   %.6: %.5 = assoc_entity element0, imports.%import_ref.10 [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.7: type = ptr_type %.1 [template]
 // CHECK:STDOUT:   %Op.type.3: type = fn_type @Op.1, @Action(%B) [template]
 // CHECK:STDOUT:   %Op.3: %Op.type.3 = struct_value () [template]
-// CHECK:STDOUT:   %.8: type = assoc_entity_type %.6, %Op.type.3 [template]
-// CHECK:STDOUT:   %.9: %.8 = assoc_entity element0, imports.%import_ref.7 [template]
-// CHECK:STDOUT:   %.10: %.4 = assoc_entity element0, imports.%import_ref.12 [symbolic]
+// CHECK:STDOUT:   %.8: type = assoc_entity_type %.4, %Op.type.3 [template]
+// CHECK:STDOUT:   %.9: %.8 = assoc_entity element0, imports.%import_ref.10 [template]
+// CHECK:STDOUT:   %.10: %.5 = assoc_entity element0, imports.%import_ref.12 [symbolic]
 // CHECK:STDOUT:   %Op.type.4: type = fn_type @Op.2 [template]
 // CHECK:STDOUT:   %Op.4: %Op.type.4 = struct_value () [template]
 // CHECK:STDOUT:   %.11: <witness> = interface_witness (%Op.4) [template]
@@ -219,9 +219,9 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %import_ref.4 = import_ref Main//action, inst+29, unloaded
 // CHECK:STDOUT:   %import_ref.5 = import_ref Main//action, inst+25, unloaded
 // CHECK:STDOUT:   %import_ref.6 = import_ref Main//action, inst+28, unloaded
-// CHECK:STDOUT:   %import_ref.7 = import_ref Main//action, inst+12, unloaded
-// CHECK:STDOUT:   %import_ref.8 = import_ref Main//action, inst+10, unloaded
-// CHECK:STDOUT:   %import_ref.9: @Action.%.2 (%.4) = import_ref Main//action, inst+16, loaded [symbolic = @Action.%.3 (constants.%.10)]
+// CHECK:STDOUT:   %import_ref.7 = import_ref Main//action, inst+10, unloaded
+// CHECK:STDOUT:   %import_ref.8: @Action.%.2 (%.5) = import_ref Main//action, inst+16, loaded [symbolic = @Action.%.3 (constants.%.10)]
+// CHECK:STDOUT:   %import_ref.9 = import_ref Main//action, inst+12, unloaded
 // CHECK:STDOUT:   %import_ref.10 = import_ref Main//action, inst+12, unloaded
 // CHECK:STDOUT:   %import_ref.11: <witness> = import_ref Main//action, inst+42, loaded [template = constants.%.11]
 // CHECK:STDOUT:   %import_ref.12 = import_ref Main//action, inst+12, unloaded
@@ -252,18 +252,18 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.2)]
 // CHECK:STDOUT:   %Op.type: type = fn_type @Op.1, @Action(%T) [symbolic = %Op.type (constants.%Op.type.2)]
 // CHECK:STDOUT:   %Op: @Action.%Op.type (%Op.type.2) = struct_value () [symbolic = %Op (constants.%Op.2)]
-// CHECK:STDOUT:   %.2: type = assoc_entity_type @Action.%.1 (%.3), @Action.%Op.type (%Op.type.2) [symbolic = %.2 (constants.%.4)]
-// CHECK:STDOUT:   %.3: @Action.%.2 (%.4) = assoc_entity element0, imports.%import_ref.7 [symbolic = %.3 (constants.%.5)]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Action.%.1 (%.3), @Action.%Op.type (%Op.type.2) [symbolic = %.2 (constants.%.5)]
+// CHECK:STDOUT:   %.3: @Action.%.2 (%.5) = assoc_entity element0, imports.%import_ref.10 [symbolic = %.3 (constants.%.6)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   interface {
 // CHECK:STDOUT:   !members:
-// CHECK:STDOUT:     .Self = imports.%import_ref.8
-// CHECK:STDOUT:     .Op = imports.%import_ref.9
-// CHECK:STDOUT:     witness = (imports.%import_ref.10)
+// CHECK:STDOUT:     .Self = imports.%import_ref.7
+// CHECK:STDOUT:     .Op = imports.%import_ref.8
+// CHECK:STDOUT:     witness = (imports.%import_ref.9)
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl @impl: %A as %.6 {
+// CHECK:STDOUT: impl @impl: %A as %.4 {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = imports.%import_ref.11
 // CHECK:STDOUT: }
@@ -288,8 +288,8 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %a.ref: %A = name_ref a, %a
 // CHECK:STDOUT:   %Action.ref: %Action.type = name_ref Action, imports.%import_ref.1 [template = constants.%Action]
 // CHECK:STDOUT:   %B.ref: type = name_ref B, imports.%import_ref.3 [template = constants.%B]
-// CHECK:STDOUT:   %.loc4_23: init type = call %Action.ref(%B.ref) [template = constants.%.6]
-// CHECK:STDOUT:   %.loc4_26: %.8 = specific_constant imports.%import_ref.9, @Action(constants.%B) [template = constants.%.9]
+// CHECK:STDOUT:   %.loc4_23: init type = call %Action.ref(%B.ref) [template = constants.%.4]
+// CHECK:STDOUT:   %.loc4_26: %.8 = specific_constant imports.%import_ref.8, @Action(constants.%B) [template = constants.%.9]
 // CHECK:STDOUT:   %Op.ref: %.8 = name_ref Op, %.loc4_26 [template = constants.%.9]
 // CHECK:STDOUT:   %.1: %Op.type.3 = interface_witness_access imports.%import_ref.11, element0 [template = constants.%Op.4]
 // CHECK:STDOUT:   %Op.call: init %.2 = call %.1()
@@ -302,15 +302,11 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific @Action(@Action.%T) {
-// CHECK:STDOUT:   %T => constants.%T
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
 // CHECK:STDOUT: specific @Action(constants.%B) {
 // CHECK:STDOUT:   %T => constants.%B
 // CHECK:STDOUT:
 // CHECK:STDOUT: !definition:
-// CHECK:STDOUT:   %.1 => constants.%.6
+// CHECK:STDOUT:   %.1 => constants.%.4
 // CHECK:STDOUT:   %Self => constants.%Self.2
 // CHECK:STDOUT:   %Op.type => constants.%Op.type.3
 // CHECK:STDOUT:   %Op => constants.%Op.3
@@ -318,6 +314,12 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %.3 => constants.%.9
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Action(@Action.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.1(constants.%T, constants.%Self.1) {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_action.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -330,14 +332,14 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %.3: type = interface_type @Action, @Action(%T) [symbolic]
 // CHECK:STDOUT:   %Self.1: @Action.%.1 (%.3) = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %.4: type = interface_type @Action, @Action(%B) [template]
+// CHECK:STDOUT:   %Self.2: %.3 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %Op.type.1: type = fn_type @Op [template]
 // CHECK:STDOUT:   %Op.1: %Op.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %Op.type.2: type = fn_type @Op, @Action(%T) [symbolic]
-// CHECK:STDOUT:   %.4: type = assoc_entity_type %.3, %Op.type.2 [symbolic]
-// CHECK:STDOUT:   %.5: %.4 = assoc_entity element0, imports.%import_ref.7 [symbolic]
 // CHECK:STDOUT:   %Op.2: %Op.type.2 = struct_value () [symbolic]
-// CHECK:STDOUT:   %Self.2: %.3 = bind_symbolic_name Self 1 [symbolic]
-// CHECK:STDOUT:   %.6: type = interface_type @Action, @Action(%B) [template]
+// CHECK:STDOUT:   %.5: type = assoc_entity_type %.3, %Op.type.2 [symbolic]
+// CHECK:STDOUT:   %.6: %.5 = assoc_entity element0, imports.%import_ref.10 [symbolic]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [template]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [template]
 // CHECK:STDOUT:   %.7: type = ptr_type %.1 [template]
@@ -346,8 +348,8 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %Op.type.3: type = fn_type @Op, @Action(%C) [template]
 // CHECK:STDOUT:   %Op.3: %Op.type.3 = struct_value () [template]
 // CHECK:STDOUT:   %.9: type = assoc_entity_type %.8, %Op.type.3 [template]
-// CHECK:STDOUT:   %.10: %.9 = assoc_entity element0, imports.%import_ref.7 [template]
-// CHECK:STDOUT:   %.11: %.4 = assoc_entity element0, imports.%import_ref.13 [symbolic]
+// CHECK:STDOUT:   %.10: %.9 = assoc_entity element0, imports.%import_ref.10 [template]
+// CHECK:STDOUT:   %.11: %.5 = assoc_entity element0, imports.%import_ref.13 [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -357,9 +359,9 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %import_ref.4: type = import_ref Main//action, inst+29, loaded [template = constants.%C]
 // CHECK:STDOUT:   %import_ref.5 = import_ref Main//action, inst+25, unloaded
 // CHECK:STDOUT:   %import_ref.6 = import_ref Main//action, inst+28, unloaded
-// CHECK:STDOUT:   %import_ref.7 = import_ref Main//action, inst+12, unloaded
-// CHECK:STDOUT:   %import_ref.8 = import_ref Main//action, inst+10, unloaded
-// CHECK:STDOUT:   %import_ref.9: @Action.%.2 (%.4) = import_ref Main//action, inst+16, loaded [symbolic = @Action.%.3 (constants.%.11)]
+// CHECK:STDOUT:   %import_ref.7 = import_ref Main//action, inst+10, unloaded
+// CHECK:STDOUT:   %import_ref.8: @Action.%.2 (%.5) = import_ref Main//action, inst+16, loaded [symbolic = @Action.%.3 (constants.%.11)]
+// CHECK:STDOUT:   %import_ref.9 = import_ref Main//action, inst+12, unloaded
 // CHECK:STDOUT:   %import_ref.10 = import_ref Main//action, inst+12, unloaded
 // CHECK:STDOUT:   %import_ref.11 = import_ref Main//action, inst+42, unloaded
 // CHECK:STDOUT:   %import_ref.12 = import_ref Main//action, inst+30, unloaded
@@ -391,18 +393,18 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self.2)]
 // CHECK:STDOUT:   %Op.type: type = fn_type @Op, @Action(%T) [symbolic = %Op.type (constants.%Op.type.2)]
 // CHECK:STDOUT:   %Op: @Action.%Op.type (%Op.type.2) = struct_value () [symbolic = %Op (constants.%Op.2)]
-// CHECK:STDOUT:   %.2: type = assoc_entity_type @Action.%.1 (%.3), @Action.%Op.type (%Op.type.2) [symbolic = %.2 (constants.%.4)]
-// CHECK:STDOUT:   %.3: @Action.%.2 (%.4) = assoc_entity element0, imports.%import_ref.7 [symbolic = %.3 (constants.%.5)]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @Action.%.1 (%.3), @Action.%Op.type (%Op.type.2) [symbolic = %.2 (constants.%.5)]
+// CHECK:STDOUT:   %.3: @Action.%.2 (%.5) = assoc_entity element0, imports.%import_ref.10 [symbolic = %.3 (constants.%.6)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   interface {
 // CHECK:STDOUT:   !members:
-// CHECK:STDOUT:     .Self = imports.%import_ref.8
-// CHECK:STDOUT:     .Op = imports.%import_ref.9
-// CHECK:STDOUT:     witness = (imports.%import_ref.10)
+// CHECK:STDOUT:     .Self = imports.%import_ref.7
+// CHECK:STDOUT:     .Op = imports.%import_ref.8
+// CHECK:STDOUT:     witness = (imports.%import_ref.9)
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl @impl: %A as %.6 {
+// CHECK:STDOUT: impl @impl: %A as %.4 {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   witness = imports.%import_ref.11
 // CHECK:STDOUT: }
@@ -433,7 +435,7 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %Action.ref: %Action.type = name_ref Action, imports.%import_ref.1 [template = constants.%Action]
 // CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%import_ref.4 [template = constants.%C]
 // CHECK:STDOUT:   %.loc8_23: init type = call %Action.ref(%C.ref) [template = constants.%.8]
-// CHECK:STDOUT:   %.loc8_26: %.9 = specific_constant imports.%import_ref.9, @Action(constants.%C) [template = constants.%.10]
+// CHECK:STDOUT:   %.loc8_26: %.9 = specific_constant imports.%import_ref.8, @Action(constants.%C) [template = constants.%.10]
 // CHECK:STDOUT:   %Op.ref: %.9 = name_ref Op, %.loc8_26 [template = constants.%.10]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
@@ -442,13 +444,15 @@ impl A as Factory(B) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Action(constants.%B) {
+// CHECK:STDOUT:   %T => constants.%B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: specific @Action(@Action.%T) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific @Action(constants.%B) {
-// CHECK:STDOUT:   %T => constants.%B
-// CHECK:STDOUT: }
+// CHECK:STDOUT: specific @Op(constants.%T, constants.%Self.1) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Action(constants.%C) {
 // CHECK:STDOUT:   %T => constants.%C

+ 4 - 0
toolchain/check/testdata/index/fail_negative_indexing.carbon

@@ -113,3 +113,7 @@ var b: i32 = a[-10];
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 13 - 11
toolchain/check/testdata/interface/no_prelude/generic_import.carbon

@@ -101,13 +101,13 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %.3: type = interface_type @AddWith, @AddWith(%T) [symbolic]
 // CHECK:STDOUT:   %Self.1: @AddWith.%.1 (%.3) = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %Self.2: %.3 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @AddWith(%T) [symbolic]
-// CHECK:STDOUT:   %.4: type = assoc_entity_type %.3, %F.type.2 [symbolic]
-// CHECK:STDOUT:   %.5: %.4 = assoc_entity element0, imports.%import_ref.2 [symbolic]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
-// CHECK:STDOUT:   %Self.2: %.3 = bind_symbolic_name Self 1 [symbolic]
+// CHECK:STDOUT:   %.4: type = assoc_entity_type %.3, %F.type.2 [symbolic]
+// CHECK:STDOUT:   %.5: %.4 = assoc_entity element0, imports.%import_ref.5 [symbolic]
 // CHECK:STDOUT:   %.6: type = interface_type @AddWith, @AddWith(%C) [template]
 // CHECK:STDOUT:   %F.type.3: type = fn_type @F.2 [template]
 // CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
@@ -116,10 +116,10 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1: %AddWith.type = import_ref Main//a, inst+4, loaded [template = constants.%AddWith]
-// CHECK:STDOUT:   %import_ref.2 = import_ref Main//a, inst+12, unloaded
-// CHECK:STDOUT:   %import_ref.3 = import_ref Main//a, inst+10, unloaded
-// CHECK:STDOUT:   %import_ref.4 = import_ref Main//a, inst+16, unloaded
-// CHECK:STDOUT:   %import_ref.5: @AddWith.%F.type (%F.type.2) = import_ref Main//a, inst+12, loaded [symbolic = @AddWith.%F (constants.%F.1)]
+// CHECK:STDOUT:   %import_ref.2 = import_ref Main//a, inst+10, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref Main//a, inst+16, unloaded
+// CHECK:STDOUT:   %import_ref.4: @AddWith.%F.type (%F.type.2) = import_ref Main//a, inst+12, loaded [symbolic = @AddWith.%F (constants.%F.1)]
+// CHECK:STDOUT:   %import_ref.5 = import_ref Main//a, inst+12, unloaded
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -148,13 +148,13 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   %F.type: type = fn_type @F.1, @AddWith(%T) [symbolic = %F.type (constants.%F.type.2)]
 // CHECK:STDOUT:   %F: @AddWith.%F.type (%F.type.2) = struct_value () [symbolic = %F (constants.%F.2)]
 // CHECK:STDOUT:   %.2: type = assoc_entity_type @AddWith.%.1 (%.3), @AddWith.%F.type (%F.type.2) [symbolic = %.2 (constants.%.4)]
-// CHECK:STDOUT:   %.3: @AddWith.%.2 (%.4) = assoc_entity element0, imports.%import_ref.2 [symbolic = %.3 (constants.%.5)]
+// CHECK:STDOUT:   %.3: @AddWith.%.2 (%.4) = assoc_entity element0, imports.%import_ref.5 [symbolic = %.3 (constants.%.5)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   interface {
 // CHECK:STDOUT:   !members:
-// CHECK:STDOUT:     .Self = imports.%import_ref.3
-// CHECK:STDOUT:     .F = imports.%import_ref.4
-// CHECK:STDOUT:     witness = (imports.%import_ref.5)
+// CHECK:STDOUT:     .Self = imports.%import_ref.2
+// CHECK:STDOUT:     .F = imports.%import_ref.3
+// CHECK:STDOUT:     witness = (imports.%import_ref.4)
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -190,6 +190,8 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T, constants.%Self.1) {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: specific @AddWith(constants.%C) {
 // CHECK:STDOUT:   %T => constants.%C
 // CHECK:STDOUT: }

+ 4 - 0
toolchain/check/testdata/interface/no_prelude/import.carbon

@@ -300,3 +300,7 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%Self.2) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.2(constants.%Self.3) {}
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon

@@ -96,3 +96,7 @@ fn Main() -> i32 {
 // CHECK:STDOUT:   fn[%self: @Op.%Self (%Self)](%other: @Op.%Self (%Self)) -> @Op.%Self (%Self);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon

@@ -89,3 +89,7 @@ fn Main() -> i32 {
 // CHECK:STDOUT:   fn[%self: @Op.%Self (%Self)](%other: @Op.%Self (%Self)) -> @Op.%Self (%Self);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/add.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/bit_and.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/operators/overloaded/bit_complement.carbon

@@ -136,3 +136,7 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   return %Op.call to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/bit_or.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/bit_xor.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/operators/overloaded/dec.carbon

@@ -133,3 +133,8 @@ fn TestOp() {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT:   %.2 => constants.%.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/div.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 24 - 0
toolchain/check/testdata/operators/overloaded/eq.carbon

@@ -260,6 +260,14 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:   return %.loc16_16.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Equal.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @NotEqual.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_no_impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -384,6 +392,14 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:   fn[%self: @NotEqual.%Self (%Self)](%other: @NotEqual.%Self (%Self)) -> bool;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Equal(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @NotEqual(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_no_impl_for_args.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -568,3 +584,11 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Equal.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @NotEqual.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 10 - 0
toolchain/check/testdata/operators/overloaded/fail_assign_non_ref.carbon

@@ -221,3 +221,13 @@ fn TestAddAssignNonRef(a: C, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT:   %.2 => constants.%.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 18 - 0
toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon

@@ -240,3 +240,21 @@ fn TestRef(b: C) {
 // CHECK:STDOUT:   fn[addr %self: @Op.4.%.2 (%.15)]();
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.1(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.3(constants.%Self.3) {
+// CHECK:STDOUT:   %Self => constants.%Self.3
+// CHECK:STDOUT:   %.2 => constants.%.11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.4) {
+// CHECK:STDOUT:   %Self => constants.%Self.4
+// CHECK:STDOUT:   %.2 => constants.%.15
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon

@@ -245,3 +245,12 @@ fn TestAssign(b: D) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.7
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/operators/overloaded/inc.carbon

@@ -133,3 +133,8 @@ fn TestOp() {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT:   %.2 => constants.%.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/left_shift.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/mod.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/mul.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/operators/overloaded/negate.carbon

@@ -136,3 +136,7 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   return %Op.call to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 32 - 0
toolchain/check/testdata/operators/overloaded/ordered.carbon

@@ -371,6 +371,22 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:   return %.loc26_16.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Less.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @LessOrEquivalent.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Greater.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GreaterOrEquivalent.2(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_no_impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -567,3 +583,19 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:   fn[%self: @GreaterOrEquivalent.%Self (%Self)](%other: @GreaterOrEquivalent.%Self (%Self)) -> bool;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Less(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @LessOrEquivalent(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Greater(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GreaterOrEquivalent(constants.%Self) {
+// CHECK:STDOUT:   %Self => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/right_shift.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/operators/overloaded/sub.carbon

@@ -233,3 +233,12 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.2(constants.%Self.1) {
+// CHECK:STDOUT:   %Self => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Op.4(constants.%Self.2) {
+// CHECK:STDOUT:   %Self => constants.%Self.2
+// CHECK:STDOUT:   %.2 => constants.%.8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 2
toolchain/sem_ir/formatter.cpp

@@ -320,8 +320,9 @@ class FormatterImpl {
       out_ << "!" << region_name << ":\n";
     }
     for (auto [generic_inst_id, specific_inst_id] : llvm::zip_longest(
-             sem_ir_.inst_blocks().Get(generic.GetEvalBlock(region)),
-             sem_ir_.inst_blocks().Get(specific.GetValueBlock(region)))) {
+             sem_ir_.inst_blocks().GetOrEmpty(generic.GetEvalBlock(region)),
+             sem_ir_.inst_blocks().GetOrEmpty(
+                 specific.GetValueBlock(region)))) {
       if (generic_inst_id && specific_inst_id &&
           sem_ir_.insts().Is<StructTypeField>(*generic_inst_id) &&
           sem_ir_.insts().Is<StructTypeField>(*specific_inst_id)) {

+ 3 - 1
toolchain/sem_ir/generic.cpp

@@ -82,7 +82,9 @@ auto GetConstantInSpecific(const File& sem_ir, SpecificId specific_id,
 
   auto value_block_id = specific.GetValueBlock(symbolic.index.region());
   CARBON_CHECK(value_block_id.is_valid())
-      << "Queried region of " << specific_id << " before it was resolved.";
+      << "Queried " << symbolic.index << " in " << specific_id << " for "
+      << sem_ir.insts().Get(sem_ir.generics().Get(specific.generic_id).decl_id)
+      << " before it was resolved.";
   return sem_ir.constant_values().Get(
       sem_ir.inst_blocks().Get(value_block_id)[symbolic.index.index()]);
 }