Просмотр исходного кода

Create a `Generic` object to represent a generic. (#4081)

Build a `Generic` object for generic functions. This object tracks the
generic parameters that are in scope for the generic entity. Eventually
it will track other information about the generic too.

Add basic SemIR formatting support for generic functions.
Richard Smith 1 год назад
Родитель
Сommit
e7b0529957
79 измененных файлов с 491 добавлено и 209 удалено
  1. 1 0
      toolchain/check/context.cpp
  2. 3 0
      toolchain/check/context.h
  3. 16 6
      toolchain/check/handle_binding_pattern.cpp
  4. 21 8
      toolchain/check/handle_function.cpp
  5. 1 0
      toolchain/check/handle_interface.cpp
  6. 3 0
      toolchain/check/import_ref.cpp
  7. 49 8
      toolchain/check/scope_stack.cpp
  8. 37 5
      toolchain/check/scope_stack.h
  9. 2 1
      toolchain/check/testdata/array/generic_empty.carbon
  10. 1 0
      toolchain/check/testdata/basics/builtin_insts.carbon
  11. 2 0
      toolchain/check/testdata/basics/no_prelude/multifile_raw_and_textual_ir.carbon
  12. 2 0
      toolchain/check/testdata/basics/no_prelude/multifile_raw_ir.carbon
  13. 1 0
      toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon
  14. 90 85
      toolchain/check/testdata/basics/no_prelude/raw_ir.carbon
  15. 2 1
      toolchain/check/testdata/builtins/int/make_type_signed.carbon
  16. 2 1
      toolchain/check/testdata/builtins/int/make_type_unsigned.carbon
  17. 4 2
      toolchain/check/testdata/class/fail_generic_method.carbon
  18. 4 2
      toolchain/check/testdata/class/generic/basic.carbon
  19. 2 1
      toolchain/check/testdata/class/generic/fail_todo_use.carbon
  20. 2 1
      toolchain/check/testdata/class/generic/import.carbon
  21. 2 1
      toolchain/check/testdata/class/generic/member_inline.carbon
  22. 16 8
      toolchain/check/testdata/class/generic/member_out_of_line.carbon
  23. 2 1
      toolchain/check/testdata/class/generic_method.carbon
  24. 2 1
      toolchain/check/testdata/eval/fail_symbolic.carbon
  25. 2 1
      toolchain/check/testdata/eval/symbolic.carbon
  26. 2 1
      toolchain/check/testdata/function/builtin/method.carbon
  27. 2 1
      toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon
  28. 2 1
      toolchain/check/testdata/function/generic/fail_todo_param_in_type.carbon
  29. 2 1
      toolchain/check/testdata/function/generic/no_prelude/fail_type_param_mismatch.carbon
  30. 2 1
      toolchain/check/testdata/function/generic/no_prelude/type_param.carbon
  31. 2 1
      toolchain/check/testdata/function/generic/no_prelude/type_param_scope.carbon
  32. 14 7
      toolchain/check/testdata/function/generic/redeclare.carbon
  33. 4 2
      toolchain/check/testdata/impl/compound.carbon
  34. 2 1
      toolchain/check/testdata/impl/extend_impl.carbon
  35. 2 1
      toolchain/check/testdata/impl/fail_call_invalid.carbon
  36. 4 2
      toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
  37. 2 1
      toolchain/check/testdata/impl/fail_impl_as_scope.carbon
  38. 6 3
      toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon
  39. 2 1
      toolchain/check/testdata/impl/impl_as.carbon
  40. 4 2
      toolchain/check/testdata/impl/impl_forall.carbon
  41. 2 1
      toolchain/check/testdata/impl/lookup/alias.carbon
  42. 2 1
      toolchain/check/testdata/impl/lookup/fail_alias_impl_not_found.carbon
  43. 2 1
      toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon
  44. 2 1
      toolchain/check/testdata/impl/lookup/import.carbon
  45. 2 1
      toolchain/check/testdata/impl/lookup/instance_method.carbon
  46. 2 1
      toolchain/check/testdata/impl/lookup/no_prelude/import.carbon
  47. 2 1
      toolchain/check/testdata/impl/no_prelude/basic.carbon
  48. 2 1
      toolchain/check/testdata/impl/no_prelude/import_self.carbon
  49. 2 1
      toolchain/check/testdata/impl/no_prelude/self_in_class.carbon
  50. 4 2
      toolchain/check/testdata/impl/no_prelude/self_in_signature.carbon
  51. 4 2
      toolchain/check/testdata/interface/fail_todo_define_default_fn_inline.carbon
  52. 4 2
      toolchain/check/testdata/interface/fail_todo_define_default_fn_out_of_line.carbon
  53. 2 1
      toolchain/check/testdata/interface/no_prelude/as_type_of_type.carbon
  54. 2 1
      toolchain/check/testdata/interface/no_prelude/basic.carbon
  55. 2 1
      toolchain/check/testdata/interface/no_prelude/default_fn.carbon
  56. 4 2
      toolchain/check/testdata/interface/no_prelude/fail_add_member_outside_definition.carbon
  57. 4 2
      toolchain/check/testdata/interface/no_prelude/fail_lookup_undefined.carbon
  58. 2 1
      toolchain/check/testdata/interface/no_prelude/fail_member_lookup.carbon
  59. 4 2
      toolchain/check/testdata/interface/no_prelude/fail_redeclare_member.carbon
  60. 6 3
      toolchain/check/testdata/interface/no_prelude/fail_todo_facet_lookup.carbon
  61. 4 2
      toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon
  62. 4 2
      toolchain/check/testdata/interface/no_prelude/fail_todo_modifiers.carbon
  63. 10 5
      toolchain/check/testdata/interface/no_prelude/generic.carbon
  64. 4 2
      toolchain/check/testdata/interface/no_prelude/generic_binding_after_assoc_const.carbon
  65. 2 1
      toolchain/check/testdata/interface/no_prelude/generic_import.carbon
  66. 4 2
      toolchain/check/testdata/interface/no_prelude/import.carbon
  67. 2 1
      toolchain/check/testdata/interface/no_prelude/self.carbon
  68. 4 2
      toolchain/check/testdata/interface/todo_define_not_default.carbon
  69. 2 1
      toolchain/check/testdata/namespace/fail_params.carbon
  70. 2 1
      toolchain/check/testdata/return/fail_let_in_type.carbon
  71. 1 0
      toolchain/sem_ir/BUILD
  72. 1 0
      toolchain/sem_ir/file.cpp
  73. 6 0
      toolchain/sem_ir/file.h
  74. 11 0
      toolchain/sem_ir/formatter.cpp
  75. 2 0
      toolchain/sem_ir/function.h
  76. 34 0
      toolchain/sem_ir/generic.h
  77. 3 3
      toolchain/sem_ir/id_kind.h
  78. 17 0
      toolchain/sem_ir/ids.h
  79. 1 0
      toolchain/sem_ir/yaml_test.cpp

+ 1 - 0
toolchain/check/context.cpp

@@ -633,6 +633,7 @@ auto Context::FinalizeGlobalInit() -> void {
         {.name_id = SemIR::NameId::ForIdentifier(name_id),
          .parent_scope_id = SemIR::NameScopeId::Package,
          .decl_id = SemIR::InstId::Invalid,
+         .generic_id = SemIR::GenericId::Invalid,
          .implicit_param_refs_id = SemIR::InstBlockId::Invalid,
          .param_refs_id = SemIR::InstBlockId::Empty,
          .return_storage_id = SemIR::InstId::Invalid,

+ 3 - 0
toolchain/check/context.h

@@ -387,6 +387,9 @@ class Context {
     return sem_ir().interfaces();
   }
   auto impls() -> SemIR::ImplStore& { return sem_ir().impls(); }
+  auto generics() -> ValueStore<SemIR::GenericId>& {
+    return sem_ir().generics();
+  }
   auto import_irs() -> ValueStore<SemIR::ImportIRId>& {
     return sem_ir().import_irs();
   }

+ 16 - 6
toolchain/check/handle_binding_pattern.cpp

@@ -59,6 +59,15 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
     }
   };
 
+  // Push the binding onto the node stack and, if necessary, onto the scope
+  // stack.
+  auto push_bind_name = [&](SemIR::InstId bind_id) {
+    context.node_stack().Push(node_id, bind_id);
+    if (is_generic && !is_associated_constant) {
+      context.scope_stack().PushCompileTimeBinding(bind_id);
+    }
+  };
+
   // A `self` binding can only appear in an implicit parameter list.
   if (name_id == SemIR::NameId::SelfValue &&
       !context.node_stack().PeekIs<Parse::NodeKind::ImplicitParamListStart>()) {
@@ -133,7 +142,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
             name_node, {.type_id = cast_type_id, .name_id = name_id});
       }
       auto bind_id = context.AddInst(make_bind_name(cast_type_id, value_id));
-      context.node_stack().Push(node_id, bind_id);
+      push_bind_name(bind_id);
 
       if (context_node_kind == Parse::NodeKind::ReturnedModifier) {
         RegisterReturnedVar(context, bind_id);
@@ -150,14 +159,14 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
       auto param_id = context.AddInst<SemIR::Param>(
           name_node, {.type_id = cast_type_id, .name_id = name_id});
       auto bind_id = context.AddInst(make_bind_name(cast_type_id, param_id));
+      push_bind_name(bind_id);
       // TODO: Bindings should come into scope immediately in other contexts
       // too.
       context.AddNameToLookup(name_id, bind_id);
-      context.node_stack().Push(node_id, bind_id);
       break;
     }
 
-    case Parse::NodeKind::LetIntroducer:
+    case Parse::NodeKind::LetIntroducer: {
       cast_type_id = context.AsCompleteType(cast_type_id, [&] {
         CARBON_DIAGNOSTIC(IncompleteTypeInLetDecl, Error,
                           "`let` binding has incomplete type `{0}`.",
@@ -169,10 +178,11 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
       // formed its initializer.
       // TODO: For general pattern parsing, we'll need to create a block to hold
       // the `let` pattern before we see the initializer.
-      context.node_stack().Push(
-          node_id, context.AddPlaceholderInstInNoBlock(
-                       make_bind_name(cast_type_id, SemIR::InstId::Invalid)));
+      auto bind_id = context.AddPlaceholderInstInNoBlock(
+          make_bind_name(cast_type_id, SemIR::InstId::Invalid));
+      push_bind_name(bind_id);
       break;
+    }
 
     default:
       CARBON_FATAL() << "Found a pattern binding in unexpected context "

+ 21 - 8
toolchain/check/handle_function.cpp

@@ -13,10 +13,10 @@
 #include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_component.h"
-#include "toolchain/parse/tree_node_diagnostic_converter.h"
 #include "toolchain/sem_ir/builtin_function_kind.h"
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/function.h"
+#include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -240,18 +240,23 @@ static auto BuildFunctionDecl(Context& context,
   // Add the function declaration.
   auto function_decl = SemIR::FunctionDecl{
       SemIR::TypeId::Invalid, SemIR::FunctionId::Invalid, decl_block_id};
+  auto decl_id =
+      context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, function_decl));
+
+  // Build the function entity. This will be merged into an existing function if
+  // there is one, or otherwise added to the function store.
   auto function_info = SemIR::Function{
       .name_id = name_context.name_id_for_new_inst(),
       .parent_scope_id = name_context.parent_scope_id_for_new_inst(),
-      .decl_id = context.AddPlaceholderInst(
-          SemIR::LocIdAndInst(node_id, function_decl)),
+      .decl_id = decl_id,
+      .generic_id = SemIR::GenericId::Invalid,
       .implicit_param_refs_id = name.implicit_params_id,
       .param_refs_id = name.params_id,
       .return_storage_id = return_storage_id,
       .is_extern = is_extern,
       .return_slot = return_slot};
   if (is_definition) {
-    function_info.definition_id = function_info.decl_id;
+    function_info.definition_id = decl_id;
   }
 
   TryMergeRedecl(context, node_id, name_context.prev_inst_id(), function_decl,
@@ -259,6 +264,14 @@ static auto BuildFunctionDecl(Context& context,
 
   // Create a new function if this isn't a valid redeclaration.
   if (!function_decl.function_id.is_valid()) {
+    // For a generic function, build the corresponding Generic entity.
+    if (!context.scope_stack().compile_time_binding_stack().empty()) {
+      function_info.generic_id = context.generics().Add(SemIR::Generic{
+          .decl_id = decl_id,
+          .bindings_id = context.inst_blocks().Add(
+              context.scope_stack().compile_time_binding_stack())});
+    }
+
     function_decl.function_id = context.functions().Add(function_info);
   } else {
     // TODO: Validate that the redeclaration doesn't set an access modifier.
@@ -266,7 +279,7 @@ static auto BuildFunctionDecl(Context& context,
   function_decl.type_id = context.GetFunctionType(function_decl.function_id);
 
   // Write the function ID into the FunctionDecl.
-  context.ReplaceInstBeforeConstantUse(function_info.decl_id, function_decl);
+  context.ReplaceInstBeforeConstantUse(decl_id, function_decl);
 
   // Check if we need to add this to name lookup, now that the function decl is
   // done.
@@ -278,7 +291,7 @@ static auto BuildFunctionDecl(Context& context,
       if (auto interface_scope =
               parent_scope_inst->TryAs<SemIR::InterfaceDecl>()) {
         lookup_result_id = BuildAssociatedEntity(
-            context, interface_scope->interface_id, function_info.decl_id);
+            context, interface_scope->interface_id, decl_id);
       }
     }
 
@@ -304,10 +317,10 @@ static auto BuildFunctionDecl(Context& context,
   }
 
   if (!is_definition && context.IsImplFile() && !is_extern) {
-    context.definitions_required().push_back(function_info.decl_id);
+    context.definitions_required().push_back(decl_id);
   }
 
-  return {function_decl.function_id, function_info.decl_id};
+  return {function_decl.function_id, decl_id};
 }
 
 auto HandleFunctionDecl(Context& context, Parse::FunctionDeclId node_id)

+ 1 - 0
toolchain/check/handle_interface.cpp

@@ -161,6 +161,7 @@ auto HandleInterfaceDefinitionStart(Context& context,
         SemIR::LocId::Invalid, {.type_id = self_type_id,
                                 .bind_name_id = bind_name_id,
                                 .value_id = SemIR::InstId::Invalid});
+    context.scope_stack().PushCompileTimeBinding(interface_info.self_param_id);
     context.name_scopes().AddRequiredName(interface_info.scope_id,
                                           SemIR::NameId::SelfType,
                                           interface_info.self_param_id);

+ 3 - 0
toolchain/check/import_ref.cpp

@@ -1068,6 +1068,8 @@ class ImportRefResolver {
                                                  : function.decl_id);
     auto function_decl_id = context_.AddPlaceholderInstInNoBlock(
         SemIR::LocIdAndInst(import_ir_inst_id, function_decl));
+    // TODO: Implement import for generics.
+    auto generic_id = SemIR::GenericId::Invalid;
 
     auto new_return_storage = SemIR::InstId::Invalid;
     if (function.return_storage_id.is_valid()) {
@@ -1083,6 +1085,7 @@ class ImportRefResolver {
         {.name_id = GetLocalNameId(function.name_id),
          .parent_scope_id = parent_scope_id,
          .decl_id = function_decl_id,
+         .generic_id = generic_id,
          .implicit_param_refs_id = GetLocalParamRefsId(
              function.implicit_param_refs_id, implicit_param_const_ids),
          .param_refs_id =

+ 49 - 8
toolchain/check/scope_stack.cpp

@@ -56,6 +56,16 @@ auto ScopeStack::Pop() -> void {
     CARBON_CHECK(return_scope_stack_.back().returned_var.is_valid());
     return_scope_stack_.back().returned_var = SemIR::InstId::Invalid;
   }
+
+  CARBON_CHECK(scope.next_compile_time_bind_index.index ==
+               static_cast<int32_t>(compile_time_binding_stack_.size()))
+      << "Wrong number of entries in compile-time binding stack, have "
+      << compile_time_binding_stack_.size() << ", expected "
+      << scope.next_compile_time_bind_index.index;
+  compile_time_binding_stack_.truncate(
+      scope_stack_.empty()
+          ? 0
+          : scope_stack_.back().next_compile_time_bind_index.index);
 }
 
 auto ScopeStack::PopTo(ScopeIndex index) -> void {
@@ -147,13 +157,35 @@ auto ScopeStack::SetReturnedVarOrGetExisting(SemIR::InstId inst_id)
 auto ScopeStack::Suspend() -> SuspendedScope {
   CARBON_CHECK(!scope_stack_.empty()) << "No scope to suspend";
   SuspendedScope result = {.entry = scope_stack_.pop_back_val(),
-                           .suspended_lookups = {}};
+                           .suspended_items = {}};
   if (result.entry.scope_id.is_valid()) {
     non_lexical_scope_stack_.pop_back();
   }
+
+  auto remaining_compile_time_bindings =
+      scope_stack_.empty()
+          ? 0
+          : scope_stack_.back().next_compile_time_bind_index.index;
+
+  result.suspended_items.reserve(result.entry.names.size() +
+                                 compile_time_binding_stack_.size() -
+                                 remaining_compile_time_bindings);
   for (auto name_id : result.entry.names) {
-    result.suspended_lookups.push_back(lexical_lookup_.Suspend(name_id));
+    auto [index, inst_id] = lexical_lookup_.Suspend(name_id);
+    CARBON_CHECK(index !=
+                 SuspendedScope::ScopeItem::IndexForCompileTimeBinding);
+    result.suspended_items.push_back({.index = index, .inst_id = inst_id});
+  }
+
+  // Move any compile-time bindings into the suspended scope.
+  for (auto inst_id : llvm::ArrayRef(compile_time_binding_stack_)
+                          .drop_back(remaining_compile_time_bindings)) {
+    result.suspended_items.push_back(
+        {.index = SuspendedScope::ScopeItem::IndexForCompileTimeBinding,
+         .inst_id = inst_id});
   }
+  compile_time_binding_stack_.truncate(remaining_compile_time_bindings);
+
   // This would be easy to support if we had a need, but currently we do not.
   CARBON_CHECK(!result.entry.has_returned_var)
       << "Should not suspend a scope with a returned var.";
@@ -161,13 +193,22 @@ auto ScopeStack::Suspend() -> SuspendedScope {
 }
 
 auto ScopeStack::Restore(SuspendedScope scope) -> void {
-  for (auto entry : scope.suspended_lookups) {
-    // clang-tidy warns that the `std::move` below has no effect. While that's
-    // true, this `move` defends against the suspended lookup growing more state
-    // later.
-    // NOLINTNEXTLINE(performance-move-const-arg)
-    lexical_lookup_.Restore(std::move(entry), scope.entry.index);
+  for (auto [index, inst_id] : scope.suspended_items) {
+    if (index == SuspendedScope::ScopeItem::IndexForCompileTimeBinding) {
+      compile_time_binding_stack_.push_back(inst_id);
+    } else {
+      lexical_lookup_.Restore({.index = index, .inst_id = inst_id},
+                              scope.entry.index);
+    }
   }
+
+  CARBON_CHECK(scope.entry.next_compile_time_bind_index.index ==
+               static_cast<int32_t>(compile_time_binding_stack_.size()))
+      << "Wrong number of entries in compile-time binding stack "
+         "when restoring, have "
+      << compile_time_binding_stack_.size() << ", expected "
+      << scope.entry.next_compile_time_bind_index.index;
+
   if (scope.entry.scope_id.is_valid()) {
     non_lexical_scope_stack_.push_back({.scope_index = scope.entry.index,
                                         .name_scope_id = scope.entry.scope_id});

+ 37 - 5
toolchain/check/scope_stack.h

@@ -115,13 +115,20 @@ class ScopeStack {
   auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id)
       -> SemIR::InstId;
 
-  // Adds a compile-time binding in the current scope, and returns its index.
+  // Prepares to add a compile-time binding in the current scope, and returns
+  // its index. The added binding must then be pushed using
+  // `PushCompileTimeBinding`.
   auto AddCompileTimeBinding() -> SemIR::CompileTimeBindIndex {
     auto index = scope_stack_.back().next_compile_time_bind_index;
     ++scope_stack_.back().next_compile_time_bind_index.index;
     return index;
   }
 
+  // Pushes a compile-time binding into the current scope.
+  auto PushCompileTimeBinding(SemIR::InstId bind_id) -> void {
+    compile_time_binding_stack_.push_back(bind_id);
+  }
+
   // Temporarily removes the top of the stack and its lexical lookup results.
   auto Suspend() -> SuspendedScope;
 
@@ -139,6 +146,10 @@ class ScopeStack {
     return break_continue_stack_;
   }
 
+  auto compile_time_binding_stack() -> llvm::SmallVector<SemIR::InstId>& {
+    return compile_time_binding_stack_;
+  }
+
  private:
   // An entry in scope_stack_.
   struct ScopeStackEntry {
@@ -195,6 +206,9 @@ class ScopeStack {
   // the information in scope_stack_.
   llvm::SmallVector<NonLexicalScope> non_lexical_scope_stack_;
 
+  // A stack of the current compile time bindings.
+  llvm::SmallVector<SemIR::InstId> compile_time_binding_stack_;
+
   // The index of the next scope that will be pushed onto scope_stack_. The
   // first is always the package scope.
   ScopeIndex next_scope_index_ = ScopeIndex::Package;
@@ -204,12 +218,30 @@ class ScopeStack {
 };
 
 struct ScopeStack::SuspendedScope {
+  // An item that was suspended within this scope. This represents either a
+  // lexical lookup entry in this scope, or a compile time binding entry in this
+  // scope.
+  //
+  // TODO: For compile-time bindings, the common case is that they will both
+  // have a suspended lexical lookup entry and a suspended compile time binding
+  // entry. We should be able to store that as a single ScopeItem rather than
+  // two.
+  struct ScopeItem {
+    static constexpr uint32_t IndexForCompileTimeBinding = -1;
+
+    // The scope index for a LexicalLookup::SuspendedResult, or
+    // CompileTimeBindingIndex for a suspended compile time binding.
+    uint32_t index;
+    // The instruction within the scope.
+    SemIR::InstId inst_id;
+  };
+
   // The suspended scope stack entry.
   ScopeStackEntry entry;
-  // The lexical lookups for the suspended entry. The inline size is an attempt
-  // to keep the size of a `SuspendedFunction` reasonable while avoiding heap
-  // allocations most of the time.
-  llvm::SmallVector<LexicalLookup::SuspendedResult, 8> suspended_lookups;
+  // The list of items that were within this scope when it was suspended. The
+  // inline size is an attempt to keep the size of a `SuspendedFunction`
+  // reasonable while avoiding heap allocations most of the time.
+  llvm::SmallVector<ScopeItem, 8> suspended_items;
 };
 
 }  // namespace Carbon::Check

+ 2 - 1
toolchain/check/testdata/array/generic_empty.carbon

@@ -38,7 +38,8 @@ fn G(T:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(%T: type) {
+// CHECK:STDOUT: fn @G(%T: type)
+// CHECK:STDOUT:   generic [%T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc13_16: i32 = int_literal 0 [template = constants.%.2]

+ 1 - 0
toolchain/check/testdata/basics/builtin_insts.carbon

@@ -19,6 +19,7 @@
 // CHECK:STDOUT:   bind_names:      {}
 // CHECK:STDOUT:   functions:       {}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:        {}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:   type_blocks:     {}

+ 2 - 0
toolchain/check/testdata/basics/no_prelude/multifile_raw_and_textual_ir.carbon

@@ -32,6 +32,7 @@ fn B() {}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: empty, body: [block3]}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:        {}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:     type1:           {constant: template inst+2, value_rep: {kind: none, type: type2}}
@@ -93,6 +94,7 @@ fn B() {}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: empty, body: [block3]}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:        {}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:     type1:           {constant: template inst+2, value_rep: {kind: none, type: type2}}

+ 2 - 0
toolchain/check/testdata/basics/no_prelude/multifile_raw_ir.carbon

@@ -32,6 +32,7 @@ fn B() {}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: empty, body: [block3]}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:        {}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:     type1:           {constant: template inst+2, value_rep: {kind: none, type: type2}}
@@ -72,6 +73,7 @@ fn B() {}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: empty, body: [block3]}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:        {}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:     type1:           {constant: template inst+2, value_rep: {kind: none, type: type2}}

+ 1 - 0
toolchain/check/testdata/basics/no_prelude/raw_and_textual_ir.carbon

@@ -27,6 +27,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block3, return_storage: inst+13, return_slot: present, body: [block6]}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:        {}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:     type1:           {constant: template inst+1, value_rep: {kind: none, type: type1}}

+ 90 - 85
toolchain/check/testdata/basics/no_prelude/raw_ir.carbon

@@ -12,7 +12,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/basics/no_prelude/raw_ir.carbon
 
-fn Foo(n: ()) -> ((), ()) {
+fn Foo[T:! type](n: T) -> (T, ()) {
   return (n, ());
 }
 
@@ -21,123 +21,128 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs_size: 1
 // CHECK:STDOUT:   name_scopes:
-// CHECK:STDOUT:     name_scope0:     {inst: inst+0, parent_scope: name_scope<invalid>, has_error: false, extended_scopes: [], names: {name0: inst+14}}
+// CHECK:STDOUT:     name_scope0:     {inst: inst+0, parent_scope: name_scope<invalid>, has_error: false, extended_scopes: [], names: {name0: inst+16}}
 // CHECK:STDOUT:   bind_names:
-// CHECK:STDOUT:     bindName0:       {name: name1, parent_scope: name_scope<invalid>, index: compTimeBind<invalid>}
+// CHECK:STDOUT:     bindName0:       {name: name1, parent_scope: name_scope<invalid>, index: compTimeBind0}
+// CHECK:STDOUT:     bindName1:       {name: name2, parent_scope: name_scope<invalid>, index: compTimeBind<invalid>}
 // CHECK:STDOUT:   functions:
-// CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block3, return_storage: inst+13, return_slot: present, body: [block6]}
+// CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block4, return_storage: inst+15, return_slot: present, body: [block8]}
 // CHECK:STDOUT:   classes:         {}
+// CHECK:STDOUT:   generics:
+// CHECK:STDOUT:     generic0:        {decl: inst+16, bindings: block7}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {constant: template inst+1, value_rep: {kind: none, type: type1}}
-// CHECK:STDOUT:     type2:           {constant: template inst+8, value_rep: {kind: pointer, type: type4}}
-// CHECK:STDOUT:     type3:           {constant: template inst+15, value_rep: {kind: none, type: type1}}
-// CHECK:STDOUT:     type4:           {constant: template inst+17, value_rep: {kind: copy, type: type4}}
+// CHECK:STDOUT:     type1:           {constant: symbolic inst+3, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type2:           {constant: template inst+8, value_rep: {kind: none, type: type2}}
+// CHECK:STDOUT:     type3:           {constant: template inst+10, value_rep: {kind: unknown, type: type<invalid>}}
+// CHECK:STDOUT:     type4:           {constant: symbolic inst+13, value_rep: {kind: pointer, type: type6}}
+// CHECK:STDOUT:     type5:           {constant: template inst+17, value_rep: {kind: none, type: type2}}
+// CHECK:STDOUT:     type6:           {constant: symbolic inst+19, value_rep: {kind: copy, type: type6}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:      {}
 // CHECK:STDOUT:     typeBlock1:
+// CHECK:STDOUT:       0:               typeTypeType
+// CHECK:STDOUT:       1:               type2
+// CHECK:STDOUT:     typeBlock2:
 // CHECK:STDOUT:       0:               type1
-// CHECK:STDOUT:       1:               type1
+// CHECK:STDOUT:       1:               type2
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     'inst+0':          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
-// CHECK:STDOUT:     'inst+1':          {kind: TupleType, arg0: typeBlock0, type: typeTypeType}
-// CHECK:STDOUT:     'inst+2':          {kind: TupleLiteral, arg0: empty, type: type1}
-// CHECK:STDOUT:     'inst+3':          {kind: Converted, arg0: inst+2, arg1: inst+1, type: typeTypeType}
-// CHECK:STDOUT:     'inst+4':          {kind: Param, arg0: name1, type: type1}
-// CHECK:STDOUT:     'inst+5':          {kind: BindName, arg0: bindName0, arg1: inst+4, type: type1}
-// CHECK:STDOUT:     'inst+6':          {kind: TupleLiteral, arg0: empty, type: type1}
-// CHECK:STDOUT:     'inst+7':          {kind: TupleLiteral, arg0: empty, type: type1}
-// CHECK:STDOUT:     'inst+8':          {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
-// CHECK:STDOUT:     'inst+9':          {kind: TupleLiteral, arg0: block4, type: type2}
-// CHECK:STDOUT:     'inst+10':         {kind: Converted, arg0: inst+6, arg1: inst+1, type: typeTypeType}
-// CHECK:STDOUT:     'inst+11':         {kind: Converted, arg0: inst+7, arg1: inst+1, type: typeTypeType}
+// CHECK:STDOUT:     'inst+1':          {kind: Param, arg0: name1, type: typeTypeType}
+// CHECK:STDOUT:     'inst+2':          {kind: BindSymbolicName, arg0: bindName0, arg1: inst+1, type: typeTypeType}
+// CHECK:STDOUT:     'inst+3':          {kind: BindSymbolicName, arg0: bindName0, arg1: inst<invalid>, type: typeTypeType}
+// CHECK:STDOUT:     'inst+4':          {kind: NameRef, arg0: name1, arg1: inst+2, type: typeTypeType}
+// CHECK:STDOUT:     'inst+5':          {kind: Param, arg0: name2, type: type1}
+// CHECK:STDOUT:     'inst+6':          {kind: BindName, arg0: bindName1, arg1: inst+5, type: type1}
+// CHECK:STDOUT:     'inst+7':          {kind: NameRef, arg0: name1, arg1: inst+2, type: typeTypeType}
+// CHECK:STDOUT:     'inst+8':          {kind: TupleType, arg0: typeBlock0, type: typeTypeType}
+// CHECK:STDOUT:     'inst+9':          {kind: TupleLiteral, arg0: empty, type: type2}
+// CHECK:STDOUT:     'inst+10':         {kind: TupleType, arg0: typeBlock1, type: typeTypeType}
+// CHECK:STDOUT:     'inst+11':         {kind: TupleLiteral, arg0: block5, type: type3}
 // CHECK:STDOUT:     'inst+12':         {kind: Converted, arg0: inst+9, arg1: inst+8, type: typeTypeType}
-// CHECK:STDOUT:     'inst+13':         {kind: VarStorage, arg0: nameReturnSlot, type: type2}
-// CHECK:STDOUT:     'inst+14':         {kind: FunctionDecl, arg0: function0, arg1: block5, type: type3}
-// CHECK:STDOUT:     'inst+15':         {kind: FunctionType, arg0: function0, type: typeTypeType}
-// CHECK:STDOUT:     'inst+16':         {kind: StructValue, arg0: empty, type: type3}
-// CHECK:STDOUT:     'inst+17':         {kind: PointerType, arg0: type2, type: typeTypeType}
-// CHECK:STDOUT:     'inst+18':         {kind: NameRef, arg0: name1, arg1: inst+5, type: type1}
-// CHECK:STDOUT:     'inst+19':         {kind: TupleLiteral, arg0: empty, type: type1}
-// CHECK:STDOUT:     'inst+20':         {kind: TupleLiteral, arg0: block7, type: type2}
-// CHECK:STDOUT:     'inst+21':         {kind: TupleAccess, arg0: inst+13, arg1: element0, type: type1}
-// CHECK:STDOUT:     'inst+22':         {kind: TupleInit, arg0: block8, arg1: inst+21, type: type1}
-// CHECK:STDOUT:     'inst+23':         {kind: TupleValue, arg0: block9, type: type1}
-// CHECK:STDOUT:     'inst+24':         {kind: Converted, arg0: inst+18, arg1: inst+22, type: type1}
-// CHECK:STDOUT:     'inst+25':         {kind: TupleAccess, arg0: inst+13, arg1: element1, type: type1}
-// CHECK:STDOUT:     'inst+26':         {kind: TupleInit, arg0: empty, arg1: inst+25, type: type1}
-// CHECK:STDOUT:     'inst+27':         {kind: Converted, arg0: inst+19, arg1: inst+26, type: type1}
-// CHECK:STDOUT:     'inst+28':         {kind: TupleInit, arg0: block10, arg1: inst+13, type: type2}
-// CHECK:STDOUT:     'inst+29':         {kind: TupleValue, arg0: block11, type: type2}
-// CHECK:STDOUT:     'inst+30':         {kind: Converted, arg0: inst+20, arg1: inst+28, type: type2}
-// CHECK:STDOUT:     'inst+31':         {kind: ReturnExpr, arg0: inst+30, arg1: inst+13}
+// CHECK:STDOUT:     'inst+13':         {kind: TupleType, arg0: typeBlock2, type: typeTypeType}
+// CHECK:STDOUT:     'inst+14':         {kind: Converted, arg0: inst+11, arg1: inst+13, type: typeTypeType}
+// CHECK:STDOUT:     'inst+15':         {kind: VarStorage, arg0: nameReturnSlot, type: type4}
+// CHECK:STDOUT:     'inst+16':         {kind: FunctionDecl, arg0: function0, arg1: block6, type: type5}
+// CHECK:STDOUT:     'inst+17':         {kind: FunctionType, arg0: function0, type: typeTypeType}
+// CHECK:STDOUT:     'inst+18':         {kind: StructValue, arg0: empty, type: type5}
+// CHECK:STDOUT:     'inst+19':         {kind: PointerType, arg0: type4, type: typeTypeType}
+// CHECK:STDOUT:     'inst+20':         {kind: NameRef, arg0: name2, arg1: inst+6, type: type1}
+// CHECK:STDOUT:     'inst+21':         {kind: TupleLiteral, arg0: empty, type: type2}
+// CHECK:STDOUT:     'inst+22':         {kind: TupleLiteral, arg0: block9, type: type4}
+// CHECK:STDOUT:     'inst+23':         {kind: TupleAccess, arg0: inst+15, arg1: element0, type: type1}
+// CHECK:STDOUT:     'inst+24':         {kind: InitializeFrom, arg0: inst+20, arg1: inst+23, type: type1}
+// CHECK:STDOUT:     'inst+25':         {kind: TupleAccess, arg0: inst+15, arg1: element1, type: type2}
+// CHECK:STDOUT:     'inst+26':         {kind: TupleInit, arg0: empty, arg1: inst+25, type: type2}
+// CHECK:STDOUT:     'inst+27':         {kind: TupleValue, arg0: block11, type: type2}
+// CHECK:STDOUT:     'inst+28':         {kind: Converted, arg0: inst+21, arg1: inst+26, type: type2}
+// CHECK:STDOUT:     'inst+29':         {kind: TupleInit, arg0: block10, arg1: inst+15, type: type4}
+// CHECK:STDOUT:     'inst+30':         {kind: Converted, arg0: inst+22, arg1: inst+29, type: type4}
+// CHECK:STDOUT:     'inst+31':         {kind: ReturnExpr, arg0: inst+30, arg1: inst+15}
 // CHECK:STDOUT:   constant_values:
 // CHECK:STDOUT:     'inst+0':          template inst+0
-// CHECK:STDOUT:     'inst+1':          template inst+1
-// CHECK:STDOUT:     'inst+3':          template inst+1
+// CHECK:STDOUT:     'inst+2':          symbolic inst+3
+// CHECK:STDOUT:     'inst+3':          symbolic inst+3
+// CHECK:STDOUT:     'inst+4':          symbolic inst+3
+// CHECK:STDOUT:     'inst+7':          symbolic inst+3
 // CHECK:STDOUT:     'inst+8':          template inst+8
-// CHECK:STDOUT:     'inst+10':         template inst+1
-// CHECK:STDOUT:     'inst+11':         template inst+1
+// CHECK:STDOUT:     'inst+10':         template inst+10
 // CHECK:STDOUT:     'inst+12':         template inst+8
-// CHECK:STDOUT:     'inst+14':         template inst+16
-// CHECK:STDOUT:     'inst+15':         template inst+15
-// CHECK:STDOUT:     'inst+16':         template inst+16
+// CHECK:STDOUT:     'inst+13':         symbolic inst+13
+// CHECK:STDOUT:     'inst+14':         symbolic inst+13
+// CHECK:STDOUT:     'inst+16':         template inst+18
 // CHECK:STDOUT:     'inst+17':         template inst+17
-// CHECK:STDOUT:     'inst+22':         template inst+23
-// CHECK:STDOUT:     'inst+23':         template inst+23
-// CHECK:STDOUT:     'inst+24':         template inst+23
-// CHECK:STDOUT:     'inst+26':         template inst+23
-// CHECK:STDOUT:     'inst+27':         template inst+23
-// CHECK:STDOUT:     'inst+28':         template inst+29
-// CHECK:STDOUT:     'inst+29':         template inst+29
-// CHECK:STDOUT:     'inst+30':         template inst+29
+// CHECK:STDOUT:     'inst+18':         template inst+18
+// CHECK:STDOUT:     'inst+19':         symbolic inst+19
+// CHECK:STDOUT:     'inst+26':         template inst+27
+// CHECK:STDOUT:     'inst+27':         template inst+27
+// CHECK:STDOUT:     'inst+28':         template inst+27
 // CHECK:STDOUT:   inst_blocks:
 // CHECK:STDOUT:     empty:           {}
 // CHECK:STDOUT:     exports:
-// CHECK:STDOUT:       0:               inst+14
+// CHECK:STDOUT:       0:               inst+16
 // CHECK:STDOUT:     global_init:     {}
 // CHECK:STDOUT:     block3:
-// CHECK:STDOUT:       0:               inst+5
+// CHECK:STDOUT:       0:               inst+2
 // CHECK:STDOUT:     block4:
 // CHECK:STDOUT:       0:               inst+6
-// CHECK:STDOUT:       1:               inst+7
 // CHECK:STDOUT:     block5:
-// CHECK:STDOUT:       0:               inst+2
-// CHECK:STDOUT:       1:               inst+3
+// CHECK:STDOUT:       0:               inst+7
+// CHECK:STDOUT:       1:               inst+9
+// CHECK:STDOUT:     block6:
+// CHECK:STDOUT:       0:               inst+1
+// CHECK:STDOUT:       1:               inst+2
 // CHECK:STDOUT:       2:               inst+4
 // CHECK:STDOUT:       3:               inst+5
 // CHECK:STDOUT:       4:               inst+6
 // CHECK:STDOUT:       5:               inst+7
 // CHECK:STDOUT:       6:               inst+9
-// CHECK:STDOUT:       7:               inst+10
-// CHECK:STDOUT:       8:               inst+11
-// CHECK:STDOUT:       9:               inst+12
-// CHECK:STDOUT:       10:              inst+13
-// CHECK:STDOUT:     block6:
-// CHECK:STDOUT:       0:               inst+18
-// CHECK:STDOUT:       1:               inst+19
-// CHECK:STDOUT:       2:               inst+20
-// CHECK:STDOUT:       3:               inst+21
-// CHECK:STDOUT:       4:               inst+22
-// CHECK:STDOUT:       5:               inst+24
-// CHECK:STDOUT:       6:               inst+25
-// CHECK:STDOUT:       7:               inst+26
-// CHECK:STDOUT:       8:               inst+27
-// CHECK:STDOUT:       9:               inst+28
-// CHECK:STDOUT:       10:              inst+30
-// CHECK:STDOUT:       11:              inst+31
+// CHECK:STDOUT:       7:               inst+11
+// CHECK:STDOUT:       8:               inst+12
+// CHECK:STDOUT:       9:               inst+14
+// CHECK:STDOUT:       10:              inst+15
 // CHECK:STDOUT:     block7:
-// CHECK:STDOUT:       0:               inst+18
-// CHECK:STDOUT:       1:               inst+19
-// CHECK:STDOUT:     block8:          {}
-// CHECK:STDOUT:     block9:          {}
+// CHECK:STDOUT:       0:               inst+2
+// CHECK:STDOUT:     block8:
+// CHECK:STDOUT:       0:               inst+20
+// CHECK:STDOUT:       1:               inst+21
+// CHECK:STDOUT:       2:               inst+22
+// CHECK:STDOUT:       3:               inst+23
+// CHECK:STDOUT:       4:               inst+24
+// CHECK:STDOUT:       5:               inst+25
+// CHECK:STDOUT:       6:               inst+26
+// CHECK:STDOUT:       7:               inst+28
+// CHECK:STDOUT:       8:               inst+29
+// CHECK:STDOUT:       9:               inst+30
+// CHECK:STDOUT:       10:              inst+31
+// CHECK:STDOUT:     block9:
+// CHECK:STDOUT:       0:               inst+20
+// CHECK:STDOUT:       1:               inst+21
 // CHECK:STDOUT:     block10:
 // CHECK:STDOUT:       0:               inst+24
-// CHECK:STDOUT:       1:               inst+27
-// CHECK:STDOUT:     block11:
-// CHECK:STDOUT:       0:               inst+23
-// CHECK:STDOUT:       1:               inst+23
+// CHECK:STDOUT:       1:               inst+28
+// CHECK:STDOUT:     block11:         {}
 // CHECK:STDOUT:     block12:
 // CHECK:STDOUT:       0:               inst+0
-// CHECK:STDOUT:       1:               inst+14
+// CHECK:STDOUT:       1:               inst+16
 // CHECK:STDOUT: ...

+ 2 - 1
toolchain/check/testdata/builtins/int/make_type_signed.carbon

@@ -201,7 +201,8 @@ var m: Int(1000000000);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Symbolic(%N: i32, %x: %.6) -> %.6 {
+// CHECK:STDOUT: fn @Symbolic(%N: i32, %x: %.6) -> %.6
+// CHECK:STDOUT:   generic [%N: i32] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %x.ref: %.6 = name_ref x, %x
 // CHECK:STDOUT:   return %x.ref

+ 2 - 1
toolchain/check/testdata/builtins/int/make_type_unsigned.carbon

@@ -201,7 +201,8 @@ var m: UInt(1000000000);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Symbolic(%N: i32, %x: %.6) -> %.6 {
+// CHECK:STDOUT: fn @Symbolic(%N: i32, %x: %.6) -> %.6
+// CHECK:STDOUT:   generic [%N: i32] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %x.ref: %.6 = name_ref x, %x
 // CHECK:STDOUT:   return %x.ref

+ 4 - 2
toolchain/check/testdata/class/fail_generic_method.carbon

@@ -94,11 +94,13 @@ fn Class(N:! i32).F[self: Self](n: T) {}
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F[@Class.%self.loc13_8.2: %Class.2](@Class.%n.loc13_20.2: %T);
+// CHECK:STDOUT: fn @F[@Class.%self.loc13_8.2: %Class.2](@Class.%n.loc13_20.2: %T)
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1[%self: <error>](%n: <error>) {
+// CHECK:STDOUT: fn @.1[%self: <error>](%n: <error>)
+// CHECK:STDOUT:   generic [file.%N.loc32_10.2: i32] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 4 - 2
toolchain/check/testdata/class/generic/basic.carbon

@@ -81,7 +81,8 @@ class Class(T:! type) {
 // CHECK:STDOUT:   .k = %.loc21
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @GetAddr[addr @Class.%self.loc12_19.3: %.2]() -> %.3 {
+// CHECK:STDOUT: fn @GetAddr[addr @Class.%self.loc12_19.3: %.2]() -> %.3
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: %.2 = name_ref self, @Class.%self.loc12_19.3
 // CHECK:STDOUT:   %.loc13_17.1: ref %Class.2 = deref %self.ref
@@ -91,7 +92,8 @@ class Class(T:! type) {
 // CHECK:STDOUT:   return %.loc13_12
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @GetValue[@Class.%self.loc17_15.2: %Class.2]() -> %T {
+// CHECK:STDOUT: fn @GetValue[@Class.%self.loc17_15.2: %Class.2]() -> %T
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: %Class.2 = name_ref self, @Class.%self.loc17_15.2
 // CHECK:STDOUT:   %k.ref: %.4 = name_ref k, @Class.%.loc21 [template = @Class.%.loc21]

+ 2 - 1
toolchain/check/testdata/class/generic/fail_todo_use.carbon

@@ -102,7 +102,8 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   .k = %.loc16
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Get[addr @Class.%self.loc12_15.3: %.2]() -> %.3 {
+// CHECK:STDOUT: fn @Get[addr @Class.%self.loc12_15.3: %.2]() -> %.3
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: %.2 = name_ref self, @Class.%self.loc12_15.3
 // CHECK:STDOUT:   %.loc13_17.1: ref %Class.2 = deref %self.ref

+ 2 - 1
toolchain/check/testdata/class/generic/import.carbon

@@ -164,7 +164,8 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1() -> i32 {
+// CHECK:STDOUT: fn @F.1() -> i32
+// CHECK:STDOUT:   generic [file.%T.loc6_21.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc8: i32 = int_literal 0 [template = constants.%.4]
 // CHECK:STDOUT:   return %.loc8

+ 2 - 1
toolchain/check/testdata/class/generic/member_inline.carbon

@@ -53,7 +53,8 @@ class Class(T:! type) {
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(@Class.%n.loc12_8.2: %T) -> %T {
+// CHECK:STDOUT: fn @F(@Class.%n.loc12_8.2: %T) -> %T
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %n.ref: %T = name_ref n, @Class.%n.loc12_8.2
 // CHECK:STDOUT:   return %n.ref

+ 16 - 8
toolchain/check/testdata/class/generic/member_out_of_line.carbon

@@ -147,7 +147,8 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%n: %T) -> %T {
+// CHECK:STDOUT: fn @F(%n: %T) -> %T
+// CHECK:STDOUT:   generic [file.%T.loc4_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %n.ref: %T = name_ref n, %n
 // CHECK:STDOUT:   return %n.ref
@@ -223,7 +224,8 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F[%self: %B.2](%a: %T) {
+// CHECK:STDOUT: fn @F[%self: %B.2](%a: %T)
+// CHECK:STDOUT:   generic [file.%T.loc4_9.2: type, @A.%N.loc5_11.2: %T] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
@@ -264,7 +266,8 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F();
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: fn @.1()
+// CHECK:STDOUT:   generic [file.%T.loc15_15.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
@@ -305,7 +308,8 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   .TooFew = %TooFew.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @TooFew();
+// CHECK:STDOUT: fn @TooFew()
+// CHECK:STDOUT:   generic [file.%T.loc4_15.2: type];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @.1() {
 // CHECK:STDOUT: !entry:
@@ -354,9 +358,11 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   .TooMany = %TooMany.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @TooMany();
+// CHECK:STDOUT: fn @TooMany()
+// CHECK:STDOUT:   generic [file.%T.loc4_15.2: type];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: fn @.1()
+// CHECK:STDOUT:   generic [file.%T.loc15_12.2: type, file.%U.loc15_22.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
@@ -403,9 +409,11 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   .WrongType = %WrongType.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @WrongType();
+// CHECK:STDOUT: fn @WrongType()
+// CHECK:STDOUT:   generic [file.%T.loc4_15.2: type];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: fn @.1()
+// CHECK:STDOUT:   generic [file.%T.loc14_12.2: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 2 - 1
toolchain/check/testdata/class/generic_method.carbon

@@ -70,7 +70,8 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F[%self: %Class.2](%n: %T) {
+// CHECK:STDOUT: fn @F[%self: %Class.2](%n: %T)
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 2 - 1
toolchain/check/testdata/eval/fail_symbolic.carbon

@@ -46,7 +46,8 @@ fn G(N:! i32) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(%N: i32) {
+// CHECK:STDOUT: fn @G(%N: i32)
+// CHECK:STDOUT:   generic [%N: i32] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:   %N.ref: i32 = name_ref N, %N [symbolic = constants.%N]

+ 2 - 1
toolchain/check/testdata/eval/symbolic.carbon

@@ -46,7 +46,8 @@ fn F(T:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type) {
+// CHECK:STDOUT: fn @F(%T: type)
+// CHECK:STDOUT:   generic [%T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref.loc13_11: type = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc13_12: type = ptr_type %T [symbolic = constants.%.2]

+ 2 - 1
toolchain/check/testdata/function/builtin/method.carbon

@@ -125,7 +125,8 @@ var arr: [i32; 1.(I.F)(2)];
 // CHECK:STDOUT:   witness = %.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1[@I.%self.loc12_8.2: %Self](@I.%other.loc12_20.2: %Self) -> %Self;
+// CHECK:STDOUT: fn @F.1[@I.%self.loc12_8.2: %Self](@I.%other.loc12_20.2: %Self) -> %Self
+// CHECK:STDOUT:   generic [@I.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon

@@ -83,7 +83,8 @@ var arr: [i32; 1 + 2] = (3, 4, 3 + 4);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op[@Add.%self.loc7_9.2: %Self](@Add.%other.loc7_21.2: %Self) -> %Self;
+// CHECK:STDOUT: fn @Op[@Add.%self.loc7_9.2: %Self](@Add.%other.loc7_21.2: %Self) -> %Self
+// CHECK:STDOUT:   generic [@Add.%Self: %.2];
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- user.carbon
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/function/generic/fail_todo_param_in_type.carbon

@@ -51,5 +51,6 @@ fn F(N:! i32, a: [i32; N]*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%N: i32, %a: <error>);
+// CHECK:STDOUT: fn @F(%N: i32, %a: <error>)
+// CHECK:STDOUT:   generic [%N: i32];
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/function/generic/no_prelude/fail_type_param_mismatch.carbon

@@ -39,7 +39,8 @@ fn F(T:! type, U:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type, %U: type) {
+// CHECK:STDOUT: fn @F(%T: type, %U: type)
+// CHECK:STDOUT:   generic [%T: type, %U: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: type = ptr_type %T [symbolic = constants.%.2]

+ 2 - 1
toolchain/check/testdata/function/generic/no_prelude/type_param.carbon

@@ -33,7 +33,8 @@ fn F(T:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type) {
+// CHECK:STDOUT: fn @F(%T: type)
+// CHECK:STDOUT:   generic [%T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: type = ptr_type %T [symbolic = constants.%.2]

+ 2 - 1
toolchain/check/testdata/function/generic/no_prelude/type_param_scope.carbon

@@ -37,7 +37,8 @@ fn F(T:! type, n: T) -> T {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type, %n: %T) -> %T {
+// CHECK:STDOUT: fn @F(%T: type, %n: %T) -> %T
+// CHECK:STDOUT:   generic [%T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %n.ref: %T = name_ref n, %n

+ 14 - 7
toolchain/check/testdata/function/generic/redeclare.carbon

@@ -133,7 +133,8 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type) -> %.1 {
+// CHECK:STDOUT: fn @F(%T: type) -> %.1
+// CHECK:STDOUT:   generic [file.%T.loc4_6.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl.loc4 [template = constants.%F]
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T]
@@ -183,9 +184,11 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type, %U: type) -> %.1;
+// CHECK:STDOUT: fn @F(%T: type, %U: type) -> %.1
+// CHECK:STDOUT:   generic [%T: type, %U: type];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1(%T: type, %U: type) -> %.3 {
+// CHECK:STDOUT: fn @.1(%T: type, %U: type) -> %.3
+// CHECK:STDOUT:   generic [%T: type, %U: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [template = constants.%F]
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T]
@@ -235,9 +238,11 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type, %U: type) -> %.1;
+// CHECK:STDOUT: fn @F(%T: type, %U: type) -> %.1
+// CHECK:STDOUT:   generic [%T: type, %U: type];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1(%U: type, %T: type) -> %.3 {
+// CHECK:STDOUT: fn @.1(%U: type, %T: type) -> %.3
+// CHECK:STDOUT:   generic [%U: type, %T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [template = constants.%F]
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T.2]
@@ -287,9 +292,11 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: type, %U: type) -> %.1;
+// CHECK:STDOUT: fn @F(%T: type, %U: type) -> %.1
+// CHECK:STDOUT:   generic [%T: type, %U: type];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1(%U: type, %T: type) -> %.3 {
+// CHECK:STDOUT: fn @.1(%U: type, %T: type) -> %.3
+// CHECK:STDOUT:   generic [%U: type, %T: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [template = constants.%F]
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = constants.%T.2]

+ 4 - 2
toolchain/check/testdata/impl/compound.carbon

@@ -158,9 +158,11 @@ fn InstanceCallIndirect(p: i32*) {
 // CHECK:STDOUT:   witness = %.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G.1[@Simple.%self.loc13_8.2: %Self]();
+// CHECK:STDOUT: fn @G.1[@Simple.%self.loc13_8.2: %Self]()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/impl/extend_impl.carbon

@@ -90,7 +90,8 @@ fn G(c: C) {
 // CHECK:STDOUT:   extend name_scope2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@HasF.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/impl/fail_call_invalid.carbon

@@ -96,7 +96,8 @@ fn InstanceCall(n: i32) {
 // CHECK:STDOUT:   witness = %.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G.1[@Simple.%self.loc12_8.2: %Self]();
+// CHECK:STDOUT: fn @G.1[@Simple.%self.loc12_8.2: %Self]()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:

+ 4 - 2
toolchain/check/testdata/impl/fail_extend_impl_forall.carbon

@@ -99,9 +99,11 @@ class C {
 // CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1(@GenericInterface.%x.loc12_8.2: %T);
+// CHECK:STDOUT: fn @F.1(@GenericInterface.%x.loc12_8.2: %T)
+// CHECK:STDOUT:   generic [file.%T.loc11_28.2: type, @GenericInterface.%Self: %GenericInterface];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2(@impl.%x.loc20_10.2: %T) {
+// CHECK:STDOUT: fn @F.2(@impl.%x.loc20_10.2: %T)
+// CHECK:STDOUT:   generic [@C.%T.loc19_23.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 2 - 1
toolchain/check/testdata/impl/fail_impl_as_scope.carbon

@@ -66,7 +66,8 @@ impl as Simple {
 // CHECK:STDOUT:   witness = %.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 6 - 3
toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon

@@ -844,7 +844,8 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:   .Self = constants.%SelfNestedBadReturnType
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@I.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @PossiblyF();
 // CHECK:STDOUT:
@@ -856,7 +857,8 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.4() -> bool;
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.5[@J.%self.loc93_20.2: bool](@J.%b.loc93_32.2: bool) -> bool;
+// CHECK:STDOUT: fn @F.5[@J.%self.loc93_20.2: bool](@J.%b.loc93_32.2: bool) -> bool
+// CHECK:STDOUT:   generic [@J.%Self: %.6];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.6[@impl.7.%self.loc104_10.2: bool]() -> bool;
 // CHECK:STDOUT:
@@ -874,7 +876,8 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.13(@SelfNested.%x.loc188_8.2: %.13) -> %.15;
+// CHECK:STDOUT: fn @F.13(@SelfNested.%x.loc188_8.2: %.13) -> %.15
+// CHECK:STDOUT:   generic [@SelfNested.%Self: %.9];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.14(@impl.14.%x.loc200_10.2: %.20) -> %.21;
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/impl/impl_as.carbon

@@ -80,7 +80,8 @@ class C {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 4 - 2
toolchain/check/testdata/impl/impl_forall.carbon

@@ -67,9 +67,11 @@ impl forall [T:! type] T as Simple {
 // CHECK:STDOUT:   witness = %.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2() {
+// CHECK:STDOUT: fn @F.2()
+// CHECK:STDOUT:   generic [file.%T.loc15_14.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 2 - 1
toolchain/check/testdata/impl/lookup/alias.carbon

@@ -96,7 +96,8 @@ fn G(c: C) {
 // CHECK:STDOUT:   .G = %G
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@HasF.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/impl/lookup/fail_alias_impl_not_found.carbon

@@ -83,7 +83,8 @@ fn F(c: C) {
 // CHECK:STDOUT:   .F = %F
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@I.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2(%c: %C) {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon

@@ -102,7 +102,8 @@ impl C as I {
 // CHECK:STDOUT:   extend name_scope2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@I.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:

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

@@ -87,7 +87,8 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@HasF.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/impl/lookup/instance_method.carbon

@@ -121,7 +121,8 @@ fn F(c: C) -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1[@I.%self.loc14_8.2: %Self]() -> i32;
+// CHECK:STDOUT: fn @F.1[@I.%self.loc14_8.2: %Self]() -> i32
+// CHECK:STDOUT:   generic [@I.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2[@impl.%self.loc19_10.2: %C]() -> i32;
 // CHECK:STDOUT:

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

@@ -85,7 +85,8 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@HasF.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/impl/no_prelude/basic.carbon

@@ -73,7 +73,8 @@ impl C as Simple {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Simple.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/impl/no_prelude/import_self.carbon

@@ -75,7 +75,8 @@ fn F(x: (), y: ()) -> () {
 // CHECK:STDOUT:   witness = (%Op.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op[@Add.%self.loc5_9.2: %Self](@Add.%other.loc5_21.2: %Self) -> %Self;
+// CHECK:STDOUT: fn @Op[@Add.%self.loc5_9.2: %Self](@Add.%other.loc5_21.2: %Self) -> %Self
+// CHECK:STDOUT:   generic [@Add.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- b.carbon
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/impl/no_prelude/self_in_class.carbon

@@ -96,7 +96,8 @@ class A {
 // CHECK:STDOUT:   .Self = constants.%A
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Make.1() -> %Self;
+// CHECK:STDOUT: fn @Make.1() -> %Self
+// CHECK:STDOUT:   generic [@DefaultConstructible.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make.2() -> @impl.%return.var: %C {
 // CHECK:STDOUT: !entry:

+ 4 - 2
toolchain/check/testdata/impl/no_prelude/self_in_signature.carbon

@@ -249,7 +249,8 @@ impl D as SelfNested {
 // CHECK:STDOUT:   .Self = constants.%D
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1[@UseSelf.%self.loc12_8.2: %Self.1](@UseSelf.%x.loc12_20.2: %Self.1) -> %Self.1;
+// CHECK:STDOUT: fn @F.1[@UseSelf.%self.loc12_8.2: %Self.1](@UseSelf.%x.loc12_20.2: %Self.1) -> %Self.1
+// CHECK:STDOUT:   generic [@UseSelf.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2[@impl.1.%self.loc20_8.2: %C](@impl.1.%x.loc20_17.2: %C) -> @impl.1.%return.var: %C {
 // CHECK:STDOUT: !entry:
@@ -267,7 +268,8 @@ impl D as SelfNested {
 // CHECK:STDOUT:   return %.loc24_48 to @impl.2.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.4(@SelfNested.%x.loc28_8.2: %.13);
+// CHECK:STDOUT: fn @F.4(@SelfNested.%x.loc28_8.2: %.13)
+// CHECK:STDOUT:   generic [@SelfNested.%Self: %.9];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.5(@impl.3.%x.loc32_8.2: %.18);
 // CHECK:STDOUT:

+ 4 - 2
toolchain/check/testdata/interface/fail_todo_define_default_fn_inline.carbon

@@ -80,12 +80,14 @@ interface Interface {
 // CHECK:STDOUT:   witness = (%F.decl, %G.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(@Interface.%a.loc21_16.2: i32, @Interface.%b.loc21_24.2: i32) -> i32 = "int.sadd";
+// CHECK:STDOUT: fn @G(@Interface.%a.loc21_16.2: i32, @Interface.%b.loc21_24.2: i32) -> i32 = "int.sadd"
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:

+ 4 - 2
toolchain/check/testdata/interface/fail_todo_define_default_fn_out_of_line.carbon

@@ -122,11 +122,13 @@ fn Interface.G(a: i32, b: i32) -> i32 = "int.sadd";
 // CHECK:STDOUT:   witness = (%F.decl, %G.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(@Interface.%a.loc22_16.2: i32, @Interface.%b.loc22_24.2: i32) -> i32;
+// CHECK:STDOUT: fn @G(@Interface.%a.loc22_16.2: i32, @Interface.%b.loc22_24.2: i32) -> i32
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @.1() {
 // CHECK:STDOUT: !entry:

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/as_type_of_type.carbon

@@ -46,7 +46,8 @@ fn F(T:! Empty) {
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: %.1) {
+// CHECK:STDOUT: fn @F(%T: %.1)
+// CHECK:STDOUT:   generic [%T: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: %.1 = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc14_10.1: type = facet_type_access %T.ref [symbolic = constants.%T]

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/basic.carbon

@@ -60,5 +60,6 @@ interface ForwardDeclared {
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [@ForwardDeclared.%Self: %.2];
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/default_fn.carbon

@@ -81,7 +81,8 @@ class C {
 // CHECK:STDOUT:   .I = %I.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1() {
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@I.%Self: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
 // CHECK:STDOUT:   %c.var: ref %C = var c

+ 4 - 2
toolchain/check/testdata/interface/no_prelude/fail_add_member_outside_definition.carbon

@@ -94,7 +94,9 @@ interface Outer {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT: fn @.1()
+// CHECK:STDOUT:   generic [@Outer.%Self: %.3, @Inner.%Self: %.4];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2();
+// CHECK:STDOUT: fn @F.2()
+// CHECK:STDOUT:   generic [@Outer.%Self: %.3];
 // CHECK:STDOUT:

+ 4 - 2
toolchain/check/testdata/interface/no_prelude/fail_lookup_undefined.carbon

@@ -107,7 +107,9 @@ interface BeingDefined {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @H() -> <error>;
+// CHECK:STDOUT: fn @H() -> <error>
+// CHECK:STDOUT:   generic [@BeingDefined.%Self: %.4];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.2();
+// CHECK:STDOUT: fn @.2()
+// CHECK:STDOUT:   generic [@BeingDefined.%Self: %.4];
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/fail_member_lookup.carbon

@@ -66,7 +66,8 @@ fn F() {
 // CHECK:STDOUT:   witness = (%F.decl, %T)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:

+ 4 - 2
toolchain/check/testdata/interface/no_prelude/fail_redeclare_member.carbon

@@ -52,7 +52,9 @@ interface Interface {
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT: fn @.1()
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:

+ 6 - 3
toolchain/check/testdata/interface/no_prelude/fail_todo_facet_lookup.carbon

@@ -77,16 +77,19 @@ fn CallFacet(T:! Interface, x: T) {
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @CallStatic(%T: %.1) {
+// CHECK:STDOUT: fn @CallStatic(%T: %.1)
+// CHECK:STDOUT:   generic [%T: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: %.1 = name_ref T, %T [symbolic = constants.%T]
 // CHECK:STDOUT:   %F.ref: %.3 = name_ref F, @Interface.%.loc11 [template = constants.%.4]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @CallFacet(%T: %.1, %x: %T) {
+// CHECK:STDOUT: fn @CallFacet(%T: %.1, %x: %T)
+// CHECK:STDOUT:   generic [%T: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %x.ref: %T = name_ref x, %x
 // CHECK:STDOUT:   return

+ 4 - 2
toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon

@@ -90,9 +90,11 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F[@I.%self.loc22_8.2: <error>]() -> <error>;
+// CHECK:STDOUT: fn @F[@I.%self.loc22_8.2: <error>]() -> <error>
+// CHECK:STDOUT:   generic [file.%T.loc11_13.2: type, @I.%Self: %I];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1[%self: <error>]() -> <error> {
+// CHECK:STDOUT: fn @.1[%self: <error>]() -> <error>
+// CHECK:STDOUT:   generic [file.%T.loc39_6.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: <error> = name_ref self, %self
 // CHECK:STDOUT:   return <error>

+ 4 - 2
toolchain/check/testdata/interface/no_prelude/fail_todo_modifiers.carbon

@@ -57,12 +57,14 @@ interface Modifiers {
 // CHECK:STDOUT:   witness = (%Final.decl, %Default.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Final() {
+// CHECK:STDOUT: fn @Final()
+// CHECK:STDOUT:   generic [@Modifiers.%Self: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Default() {
+// CHECK:STDOUT: fn @Default()
+// CHECK:STDOUT:   generic [@Modifiers.%Self: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 10 - 5
toolchain/check/testdata/interface/no_prelude/generic.carbon

@@ -209,7 +209,8 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1() -> %X;
+// CHECK:STDOUT: fn @F.1() -> %X
+// CHECK:STDOUT:   generic [file.%T.loc8_23.2: type, @WithAssocFn.%Self: %WithAssocFn];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() -> @impl.2.%return.var: %X {
 // CHECK:STDOUT: !entry:
@@ -219,9 +220,11 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   return %.loc17_16 to @impl.2.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Receive(%T: %.5);
+// CHECK:STDOUT: fn @Receive(%T: %.5)
+// CHECK:STDOUT:   generic [%T: %.5];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Pass(%T: %.5) {
+// CHECK:STDOUT: fn @Pass(%T: %.5)
+// CHECK:STDOUT:   generic [%T: %.5] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Receive.ref: %Receive.type = name_ref Receive, file.%Receive.decl [template = constants.%Receive]
 // CHECK:STDOUT:   %T.ref: %.5 = name_ref T, %T [symbolic = constants.%T.2]
@@ -302,9 +305,11 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   .Self = constants.%B
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%T: %.3);
+// CHECK:STDOUT: fn @F(%T: %.3)
+// CHECK:STDOUT:   generic [%T: %.3];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(%T: %.4) {
+// CHECK:STDOUT: fn @G(%T: %.4)
+// CHECK:STDOUT:   generic [%T: %.4] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [template = constants.%F]
 // CHECK:STDOUT:   %T.ref: %.4 = name_ref T, %T [symbolic = constants.%T.3]

+ 4 - 2
toolchain/check/testdata/interface/no_prelude/generic_binding_after_assoc_const.carbon

@@ -65,7 +65,9 @@ interface I {
 // CHECK:STDOUT:   witness = (%F.decl, %U, %G.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(@I.%T.loc12_8.2: type);
+// CHECK:STDOUT: fn @F(@I.%T.loc12_8.2: type)
+// CHECK:STDOUT:   generic [@I.%Self: %.1, @I.%T.loc12_8.2: type];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(@I.%T.loc16_8.2: type);
+// CHECK:STDOUT: fn @G(@I.%T.loc16_8.2: type)
+// CHECK:STDOUT:   generic [@I.%Self: %.1, @I.%T.loc16_8.2: type];
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/generic_import.carbon

@@ -62,7 +62,8 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [file.%T.loc4_19.2: type, @AddWith.%Self: %AddWith];
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- b.carbon
 // CHECK:STDOUT:

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

@@ -126,9 +126,11 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:   witness = (%T, %F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: fn @F.1()
+// CHECK:STDOUT:   generic [@Basic.%Self: %.2];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2();
+// CHECK:STDOUT: fn @F.2()
+// CHECK:STDOUT:   generic [@ForwardDeclared.%Self: %.8];
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- b.carbon
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/interface/no_prelude/self.carbon

@@ -52,5 +52,6 @@ interface UseSelf {
 // CHECK:STDOUT:   witness = (%F.decl)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F[@UseSelf.%self.loc12_8.2: %Self]() -> %Self;
+// CHECK:STDOUT: fn @F[@UseSelf.%self.loc12_8.2: %Self]() -> %Self
+// CHECK:STDOUT:   generic [@UseSelf.%Self: %.1];
 // CHECK:STDOUT:

+ 4 - 2
toolchain/check/testdata/interface/todo_define_not_default.carbon

@@ -106,12 +106,14 @@ interface I {
 // CHECK:STDOUT:   witness = (%F.decl, %G.decl, %T, %N)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: fn @F()
+// CHECK:STDOUT:   generic [@I.%Self: %.1] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @G(@I.%a.loc14_8.2: i32, @I.%b.loc14_16.2: i32) -> i32 = "int.sadd";
+// CHECK:STDOUT: fn @G(@I.%a.loc14_8.2: i32, @I.%b.loc14_16.2: i32) -> i32 = "int.sadd"
+// CHECK:STDOUT:   generic [@I.%Self: %.1];
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/namespace/fail_params.carbon

@@ -91,7 +91,8 @@ fn D(T:! type).F() {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: fn @.1()
+// CHECK:STDOUT:   generic [file.%T.loc39_6.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 2 - 1
toolchain/check/testdata/return/fail_let_in_type.carbon

@@ -54,7 +54,8 @@ fn FirstPerfectNumber() -> z { return 6; }
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @HalfDozen() -> %y {
+// CHECK:STDOUT: fn @HalfDozen() -> %y
+// CHECK:STDOUT:   generic [<unexpected instref inst+20>: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc24: i32 = int_literal 6 [template = constants.%.2]
 // CHECK:STDOUT:   return <error>

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -94,6 +94,7 @@ cc_library(
         "copy_on_write_block.h",
         "file.h",
         "function.h",
+        "generic.h",
         "impl.h",
         "import_ir.h",
         "interface.h",

+ 1 - 0
toolchain/sem_ir/file.cpp

@@ -140,6 +140,7 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
           map.Add("bind_names", bind_names_.OutputYaml());
           map.Add("functions", functions_.OutputYaml());
           map.Add("classes", classes_.OutputYaml());
+          map.Add("generics", generics_.OutputYaml());
           map.Add("types", types_.OutputYaml());
           map.Add("type_blocks", type_blocks_.OutputYaml());
           map.Add("insts",

+ 6 - 0
toolchain/sem_ir/file.h

@@ -16,6 +16,7 @@
 #include "toolchain/sem_ir/class.h"
 #include "toolchain/sem_ir/constant.h"
 #include "toolchain/sem_ir/function.h"
+#include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/impl.h"
 #include "toolchain/sem_ir/import_ir.h"
@@ -128,6 +129,8 @@ class File : public Printable<File> {
   }
   auto impls() -> ImplStore& { return impls_; }
   auto impls() const -> const ImplStore& { return impls_; }
+  auto generics() -> ValueStore<GenericId>& { return generics_; }
+  auto generics() const -> const ValueStore<GenericId>& { return generics_; }
   auto import_irs() -> ValueStore<ImportIRId>& { return import_irs_; }
   auto import_irs() const -> const ValueStore<ImportIRId>& {
     return import_irs_;
@@ -208,6 +211,9 @@ class File : public Printable<File> {
   // Storage for impls.
   ImplStore impls_;
 
+  // Storage for generics.
+  ValueStore<GenericId> generics_;
+
   // Related IRs. There are some fixed entries at the start; see ImportIRId.
   ValueStore<ImportIRId> import_irs_;
 

+ 11 - 0
toolchain/sem_ir/formatter.cpp

@@ -262,6 +262,11 @@ class Formatter {
       out_ << "\"";
     }
 
+    if (fn.generic_id.is_valid()) {
+      out_ << "\n  ";
+      FormatGeneric(fn.generic_id);
+    }
+
     if (!fn.body_block_ids.empty()) {
       out_ << ' ';
       OpenBrace();
@@ -281,6 +286,12 @@ class Formatter {
     }
   }
 
+  auto FormatGeneric(GenericId generic_id) -> void {
+    out_ << "generic [";
+    FormatParamList(sem_ir_.generics().Get(generic_id).bindings_id);
+    out_ << "]";
+  }
+
   auto FormatParamList(InstBlockId param_refs_id) -> void {
     llvm::ListSeparator sep;
     for (InstId param_id : sem_ir_.inst_blocks().Get(param_refs_id)) {

+ 2 - 0
toolchain/sem_ir/function.h

@@ -84,6 +84,8 @@ struct Function : public Printable<Function> {
   NameScopeId parent_scope_id;
   // The first declaration of the function. This is a FunctionDecl.
   InstId decl_id;
+  // If this is a generic function, information about the generic.
+  GenericId generic_id;
   // A block containing a single reference instruction per implicit parameter.
   InstBlockId implicit_param_refs_id;
   // A block containing a single reference instruction per parameter.

+ 34 - 0
toolchain/sem_ir/generic.h

@@ -0,0 +1,34 @@
+// 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_SEM_IR_GENERIC_H_
+#define CARBON_TOOLCHAIN_SEM_IR_GENERIC_H_
+
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+// Information for a generic entity, such as a generic class, a generic
+// interface, or generic function.
+//
+// Note that this includes both checked generics and template generics.
+struct Generic : public Printable<Generic> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{decl: " << decl_id << ", bindings: " << bindings_id << "}";
+  }
+
+  // The following members always have values, and do not change throughout the
+  // lifetime of the generic.
+
+  // The first declaration of the generic entity.
+  InstId decl_id;
+  // A block containing the IDs of compile time bindings in this generic scope.
+  // The index in this block will match the `bind_index` in the name binding
+  // instruction's `BindNameInfo`.
+  InstBlockId bindings_id;
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_GENERIC_H_

+ 3 - 3
toolchain/sem_ir/id_kind.h

@@ -121,9 +121,9 @@ using IdKind = TypeEnum<
     IntId, RealId, FloatId, StringLiteralValueId,
     // From sem_ir/id.h.
     InstId, ConstantId, BindNameId, CompileTimeBindIndex, FunctionId, ClassId,
-    InterfaceId, ImplId, ImportIRId, ImportIRInstId, LocId, BoolValue, IntKind,
-    NameId, NameScopeId, InstBlockId, TypeId, TypeBlockId, ElementIndex,
-    FloatKind>;
+    InterfaceId, ImplId, GenericId, ImportIRId, ImportIRInstId, LocId,
+    BoolValue, IntKind, NameId, NameScopeId, InstBlockId, TypeId, TypeBlockId,
+    ElementIndex, FloatKind>;
 
 }  // namespace Carbon::SemIR
 

+ 17 - 0
toolchain/sem_ir/ids.h

@@ -21,6 +21,7 @@ class Inst;
 struct BindNameInfo;
 struct Class;
 struct Function;
+struct Generic;
 struct ImportIR;
 struct ImportIRInst;
 struct Interface;
@@ -283,6 +284,22 @@ struct ImplId : public IdBase, public Printable<ImplId> {
 
 constexpr ImplId ImplId::Invalid = ImplId(InvalidIndex);
 
+// The ID of a generic.
+struct GenericId : public IdBase, public Printable<GenericId> {
+  using ValueType = Generic;
+
+  // An explicitly invalid ID.
+  static const GenericId Invalid;
+
+  using IdBase::IdBase;
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "generic";
+    IdBase::Print(out);
+  }
+};
+
+constexpr GenericId GenericId::Invalid = GenericId(InvalidIndex);
+
 // The ID of an IR within the set of imported IRs, both direct and indirect.
 struct ImportIRId : public IdBase, public Printable<ImportIRId> {
   using ValueType = ImportIR;

+ 1 - 0
toolchain/sem_ir/yaml_test.cpp

@@ -62,6 +62,7 @@ TEST(SemIRTest, YAML) {
       Pair("bind_names", Yaml::Mapping(SizeIs(1))),
       Pair("functions", Yaml::Mapping(SizeIs(1))),
       Pair("classes", Yaml::Mapping(SizeIs(0))),
+      Pair("generics", Yaml::Mapping(SizeIs(0))),
       Pair("types", Yaml::Mapping(Each(type_builtin))),
       Pair("type_blocks", Yaml::Mapping(SizeIs(Ge(1)))),
       Pair("insts",