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

Add an instruction to represent a use of a dependent value from a generic instance. (#4122)

We can't use the instruction from the generic directly, because it
doesn't have the right constant value. Instead add an instruction that
models the transition from the constant value in the generic to the
constant value in the generic instance.

Also start associating the self generic instance with unqualified
lookups that find results in an enclosing generic, so that we track the
information necessary to create the new instruction.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 1 год назад
Родитель
Сommit
50d56aa7c9
30 измененных файлов с 259 добавлено и 144 удалено
  1. 1 0
      toolchain/check/check.cpp
  2. 3 7
      toolchain/check/context.cpp
  3. 1 3
      toolchain/check/context.h
  4. 22 13
      toolchain/check/decl_name_stack.cpp
  5. 2 1
      toolchain/check/decl_name_stack.h
  6. 10 3
      toolchain/check/eval.cpp
  7. 10 31
      toolchain/check/generic.cpp
  8. 2 19
      toolchain/check/generic.h
  9. 4 2
      toolchain/check/handle_class.cpp
  10. 4 2
      toolchain/check/handle_interface.cpp
  11. 10 0
      toolchain/check/handle_name.cpp
  12. 13 0
      toolchain/check/member_access.cpp
  13. 2 3
      toolchain/check/operator.cpp
  14. 15 4
      toolchain/check/scope_stack.cpp
  15. 11 3
      toolchain/check/scope_stack.h
  16. 12 9
      toolchain/check/testdata/basics/no_prelude/raw_ir.carbon
  17. 5 4
      toolchain/check/testdata/class/fail_generic_method.carbon
  18. 7 5
      toolchain/check/testdata/class/generic/basic.carbon
  19. 2 1
      toolchain/check/testdata/class/generic/fail_todo_use.carbon
  20. 6 4
      toolchain/check/testdata/class/generic/member_out_of_line.carbon
  21. 6 4
      toolchain/check/testdata/class/generic/self.carbon
  22. 6 4
      toolchain/check/testdata/class/generic_method.carbon
  23. 22 18
      toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon
  24. 1 1
      toolchain/lower/constant.cpp
  25. 6 0
      toolchain/sem_ir/file.cpp
  26. 3 3
      toolchain/sem_ir/file.h
  27. 28 0
      toolchain/sem_ir/generic.cpp
  28. 28 0
      toolchain/sem_ir/generic.h
  29. 1 0
      toolchain/sem_ir/inst_kind.def
  30. 16 0
      toolchain/sem_ir/typed_insts.h

+ 1 - 0
toolchain/check/check.cpp

@@ -207,6 +207,7 @@ static auto ImportCurrentPackage(Context& context, UnitInfo& unit_info,
 
   context.scope_stack().Push(
       package_inst_id, SemIR::NameScopeId::Package,
+      SemIR::GenericInstanceId::Invalid,
       context.name_scopes().Get(SemIR::NameScopeId::Package).has_error);
 }
 

+ 3 - 7
toolchain/check/context.cpp

@@ -273,15 +273,11 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
       scope_stack().LookupInLexicalScopes(name_id);
 
   // Walk the non-lexical scopes and perform lookups into each of them.
-  for (auto [index, lookup_scope_id] : llvm::reverse(non_lexical_scopes)) {
-    // Enclosing non-lexical scopes cannot correspond to an instance of a
-    // generic, so it's always OK to pass an invalid generic instance here.
-    // Note that the lookup result might still be found in an extended scope, so
-    // it can be in a generic instance.
+  for (auto [index, lookup_scope_id, instance_id] :
+       llvm::reverse(non_lexical_scopes)) {
     if (auto non_lexical_result = LookupQualifiedName(
             node_id, name_id,
-            {.name_scope_id = lookup_scope_id,
-             .instance_id = SemIR::GenericInstanceId::Invalid},
+            {.name_scope_id = lookup_scope_id, .instance_id = instance_id},
             /*required=*/false);
         non_lexical_result.inst_id.is_valid()) {
       return non_lexical_result;

+ 1 - 3
toolchain/check/context.h

@@ -419,9 +419,7 @@ class Context {
     return sem_ir().interfaces();
   }
   auto impls() -> SemIR::ImplStore& { return sem_ir().impls(); }
-  auto generics() -> ValueStore<SemIR::GenericId>& {
-    return sem_ir().generics();
-  }
+  auto generics() -> SemIR::GenericStore& { return sem_ir().generics(); }
   auto generic_instances() -> SemIR::GenericInstanceStore& {
     return sem_ir().generic_instances();
   }

+ 22 - 13
toolchain/check/decl_name_stack.cpp

@@ -7,6 +7,7 @@
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
+#include "toolchain/check/generic.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/name_component.h"
 #include "toolchain/diagnostics/diagnostic.h"
@@ -199,12 +200,13 @@ auto DeclNameStack::LookupOrAddName(NameContext name_context,
 static auto PushNameQualifierScope(Context& context,
                                    SemIR::InstId scope_inst_id,
                                    SemIR::NameScopeId scope_id,
+                                   SemIR::GenericInstanceId instance_id,
                                    bool has_error = false) -> void {
   // If the qualifier has no parameters, we don't need to keep around a
   // parameter scope.
   context.scope_stack().PopIfEmpty();
 
-  context.scope_stack().Push(scope_inst_id, scope_id, has_error);
+  context.scope_stack().Push(scope_inst_id, scope_id, instance_id, has_error);
 
   // Enter a parameter scope in case the qualified name itself has parameters.
   context.scope_stack().Push();
@@ -216,9 +218,10 @@ auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
   name_context.has_qualifiers = true;
 
   // Resolve the qualifier as a scope and enter the new scope.
-  auto scope_id = ResolveAsScope(name_context, name);
+  auto [scope_id, instance_id] = ResolveAsScope(name_context, name);
   if (scope_id.is_valid()) {
     PushNameQualifierScope(*context_, name_context.resolved_inst_id, scope_id,
+                           instance_id,
                            context_->name_scopes().Get(scope_id).has_error);
     name_context.parent_scope_id = scope_id;
   } else {
@@ -349,9 +352,13 @@ static auto DiagnoseQualifiedDeclInNonScope(Context& context, SemIRLoc use_loc,
 
 auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
                                    const NameComponent& name) const
-    -> SemIR::NameScopeId {
+    -> std::pair<SemIR::NameScopeId, SemIR::GenericInstanceId> {
+  constexpr std::pair<SemIR::NameScopeId, SemIR::GenericInstanceId>
+      InvalidResult = {SemIR::NameScopeId::Invalid,
+                       SemIR::GenericInstanceId::Invalid};
+
   if (!CheckQualifierIsResolved(*context_, name_context)) {
-    return SemIR::NameScopeId::Invalid;
+    return InvalidResult;
   }
 
   auto new_params =
@@ -363,29 +370,31 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
       const auto& class_info = context_->classes().Get(class_decl.class_id);
       if (!CheckRedeclParamsMatch(*context_, new_params,
                                   DeclParams(class_info))) {
-        return SemIR::NameScopeId::Invalid;
+        return InvalidResult;
       }
       if (!class_info.is_defined()) {
         DiagnoseQualifiedDeclInIncompleteClassScope(
             *context_, name_context.loc_id, class_decl.class_id);
-        return SemIR::NameScopeId::Invalid;
+        return InvalidResult;
       }
-      return class_info.scope_id;
+      return {class_info.scope_id,
+              context_->generics().GetSelfInstance(class_info.generic_id)};
     }
     case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
       const auto& interface_info =
           context_->interfaces().Get(interface_decl.interface_id);
       if (!CheckRedeclParamsMatch(*context_, new_params,
                                   DeclParams(interface_info))) {
-        return SemIR::NameScopeId::Invalid;
+        return InvalidResult;
       }
       if (!interface_info.is_defined()) {
         DiagnoseQualifiedDeclInUndefinedInterfaceScope(
             *context_, name_context.loc_id, interface_decl.interface_id,
             name_context.resolved_inst_id);
-        return SemIR::NameScopeId::Invalid;
+        return InvalidResult;
       }
-      return interface_info.scope_id;
+      return {interface_info.scope_id,
+              context_->generics().GetSelfInstance(interface_info.generic_id)};
     }
     case CARBON_KIND(SemIR::Namespace resolved_inst): {
       auto scope_id = resolved_inst.name_scope_id;
@@ -394,7 +403,7 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
                                   DeclParams(name_context.resolved_inst_id,
                                              SemIR::InstBlockId::Invalid,
                                              SemIR::InstBlockId::Invalid))) {
-        return SemIR::NameScopeId::Invalid;
+        return InvalidResult;
       }
       if (scope.is_closed_import) {
         DiagnoseQualifiedDeclInImportedPackage(*context_, name_context.loc_id,
@@ -403,12 +412,12 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
         // be used as a name qualifier.
         scope.is_closed_import = false;
       }
-      return scope_id;
+      return {scope_id, SemIR::GenericInstanceId::Invalid};
     }
     default: {
       DiagnoseQualifiedDeclInNonScope(*context_, name_context.loc_id,
                                       name_context.resolved_inst_id);
-      return SemIR::NameScopeId::Invalid;
+      return InvalidResult;
     }
   }
 }

+ 2 - 1
toolchain/check/decl_name_stack.h

@@ -233,7 +233,8 @@ class DeclNameStack {
   // corresponding scope. Issues a suitable diagnostic and returns Invalid if
   // the name doesn't resolve to a scope.
   auto ResolveAsScope(const NameContext& name_context,
-                      const NameComponent& name) const -> SemIR::NameScopeId;
+                      const NameComponent& name) const
+      -> std::pair<SemIR::NameScopeId, SemIR::GenericInstanceId>;
 
   // The linked context.
   Context* context_;

+ 10 - 3
toolchain/check/eval.cpp

@@ -10,6 +10,7 @@
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/sem_ir/builtin_function_kind.h"
 #include "toolchain/sem_ir/function.h"
+#include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst_kind.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -1122,6 +1123,12 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
           Phase::Template);
     }
 
+    case CARBON_KIND(SemIR::SpecificConstant instance): {
+      // Pull the instance-specific constant value out of the generic instance.
+      return SemIR::GetConstantValueInInstance(
+          context.sem_ir(), instance.instance_id, instance.inst_id);
+    }
+
     // These cases are treated as being the unique canonical definition of the
     // corresponding constant value.
     // TODO: This doesn't properly handle redeclarations. Consider adding a
@@ -1187,9 +1194,9 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       // Map from an instance-specific constant value to the canonical value.
       // TODO: Remove this once we properly model instructions with
       // instance-dependent constant values.
-      return GetConstantInInstance(
-          context, SemIR::GenericInstanceId::Invalid,
-          context.constant_values().Get(typed_inst.value_id));
+      return GetConstantValueInInstance(context.sem_ir(),
+                                        SemIR::GenericInstanceId::Invalid,
+                                        typed_inst.value_id);
     }
     case CARBON_KIND(SemIR::Converted typed_inst): {
       return context.constant_values().Get(typed_inst.result_id);

+ 10 - 31
toolchain/check/generic.cpp

@@ -196,13 +196,19 @@ auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
 
   auto bindings_id = context.inst_blocks().Add(all_bindings);
   auto generic_id = context.generics().Add(
-      SemIR::Generic{.decl_id = decl_id, .bindings_id = bindings_id});
+      SemIR::Generic{.decl_id = decl_id,
+                     .bindings_id = bindings_id,
+                     .self_instance_id = SemIR::GenericInstanceId::Invalid});
 
   auto decl_block_id = MakeGenericEvalBlock(
       context, generic_id, SemIR::GenericInstIndex::Region::Declaration);
   context.generic_region_stack().Pop();
 
-  context.generics().Get(generic_id).decl_block_id = decl_block_id;
+  auto self_instance_id = MakeGenericSelfInstance(context, generic_id);
+
+  auto& generic_info = context.generics().Get(generic_id);
+  generic_info.decl_block_id = decl_block_id;
+  generic_info.self_instance_id = self_instance_id;
   return generic_id;
 }
 
@@ -238,7 +244,6 @@ auto MakeGenericInstance(Context& context, SemIR::GenericId generic_id,
 
 auto MakeGenericSelfInstance(Context& context, SemIR::GenericId generic_id)
     -> SemIR::GenericInstanceId {
-  // TODO: Remove this once we import generics properly.
   if (!generic_id.is_valid()) {
     return SemIR::GenericInstanceId::Invalid;
   }
@@ -261,37 +266,11 @@ auto MakeGenericSelfInstance(Context& context, SemIR::GenericId generic_id)
   return MakeGenericInstance(context, generic_id, args_id);
 }
 
-auto GetConstantInInstance(Context& context,
-                           SemIR::GenericInstanceId /*instance_id*/,
-                           SemIR::ConstantId const_id) -> SemIR::ConstantId {
-  if (!const_id.is_symbolic()) {
-    // Type does not depend on a generic parameter.
-    return const_id;
-  }
-
-  const auto& symbolic =
-      context.constant_values().GetSymbolicConstant(const_id);
-  if (!symbolic.generic_id.is_valid()) {
-    // Constant is an abstract symbolic constant, not an instance-specific one.
-    return const_id;
-  }
-
-  // TODO: Look up the value in the generic instance. For now, return the
-  // canonical constant value.
-  return context.constant_values().Get(symbolic.inst_id);
-}
-
-auto GetConstantValueInInstance(Context& context,
-                                SemIR::GenericInstanceId instance_id,
-                                SemIR::InstId inst_id) -> SemIR::ConstantId {
-  return GetConstantInInstance(context, instance_id,
-                               context.constant_values().Get(inst_id));
-}
-
 auto GetTypeInInstance(Context& context, SemIR::GenericInstanceId instance_id,
                        SemIR::TypeId type_id) -> SemIR::TypeId {
   auto const_id = context.types().GetConstantId(type_id);
-  auto inst_const_id = GetConstantInInstance(context, instance_id, const_id);
+  auto inst_const_id =
+      GetConstantInInstance(context.sem_ir(), instance_id, const_id);
   if (inst_const_id == const_id) {
     // Common case: not an instance constant.
     return type_id;

+ 2 - 19
toolchain/check/generic.h

@@ -41,28 +41,11 @@ auto MakeGenericInstance(Context& context, SemIR::GenericId generic_id,
     -> SemIR::GenericInstanceId;
 
 // Builds the generic instance corresponding to the generic itself. For example,
-// for a generic `G(T:! type)`, this is `G(T)`.
+// for a generic `G(T:! type)`, this is `G(T)`. For an invalid `generic_id`,
+// returns an invalid instance ID.
 auto MakeGenericSelfInstance(Context& context, SemIR::GenericId generic_id)
     -> SemIR::GenericInstanceId;
 
-// Gets the substituted value of a constant within a specified instance of a
-// generic. Note that this does not perform substitution, and will return
-// `Invalid` if the substituted constant value is not yet known.
-//
-// TODO: Move this to sem_ir so that lowering can use it.
-auto GetConstantInInstance(Context& context,
-                           SemIR::GenericInstanceId instance_id,
-                           SemIR::ConstantId const_id) -> SemIR::ConstantId;
-
-// Gets the substituted constant value of an instruction within a specified
-// instance of a generic. Note that this does not perform substitution, and will
-// return `Invalid` if the substituted constant value is not yet known.
-//
-// TODO: Move this to sem_ir so that lowering can use it.
-auto GetConstantValueInInstance(Context& context,
-                                SemIR::GenericInstanceId instance_id,
-                                SemIR::InstId inst_id) -> SemIR::ConstantId;
-
 // Gets the substituted value of a type within a specified instance of a
 // generic. Note that this does not perform substitution, and will return
 // `Invalid` if the substituted type is not yet known.

+ 4 - 2
toolchain/check/handle_class.cpp

@@ -261,7 +261,7 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
     auto& class_info = context.classes().Get(class_decl.class_id);
     if (class_info.is_generic()) {
       auto instance_id =
-          MakeGenericSelfInstance(context, class_info.generic_id);
+          context.generics().GetSelfInstance(class_info.generic_id);
       class_info.self_type_id = context.GetTypeIdForTypeConstant(
           TryEvalInst(context, SemIR::InstId::Invalid,
                       SemIR::ClassType{.type_id = SemIR::TypeId::TypeType,
@@ -299,7 +299,9 @@ auto HandleClassDefinitionStart(Context& context,
   }
 
   // Enter the class scope.
-  context.scope_stack().Push(class_decl_id, class_info.scope_id);
+  context.scope_stack().Push(
+      class_decl_id, class_info.scope_id,
+      context.generics().GetSelfInstance(class_info.generic_id));
   StartGenericDefinition(context);
 
   // Introduce `Self`.

+ 4 - 2
toolchain/check/handle_interface.cpp

@@ -141,7 +141,9 @@ auto HandleInterfaceDefinitionStart(Context& context,
   }
 
   // Enter the interface scope.
-  context.scope_stack().Push(interface_decl_id, interface_info.scope_id);
+  context.scope_stack().Push(
+      interface_decl_id, interface_info.scope_id,
+      context.generics().GetSelfInstance(interface_info.generic_id));
   StartGenericDefinition(context);
 
   context.inst_block_stack().Push();
@@ -155,7 +157,7 @@ auto HandleInterfaceDefinitionStart(Context& context,
     SemIR::TypeId self_type_id = SemIR::TypeId::Invalid;
     if (interface_info.is_generic()) {
       auto instance_id =
-          MakeGenericSelfInstance(context, interface_info.generic_id);
+          context.generics().GetSelfInstance(interface_info.generic_id);
       self_type_id = context.GetTypeIdForTypeConstant(
           TryEvalInst(context, SemIR::InstId::Invalid,
                       SemIR::InterfaceType{.type_id = SemIR::TypeId::TypeType,

+ 10 - 0
toolchain/check/handle_name.cpp

@@ -86,6 +86,16 @@ static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id,
       GetTypeInInstance(context, result.instance_id, value.type_id());
   CARBON_CHECK(type_id.is_valid()) << "Missing type for " << value;
 
+  // If the named entity has a constant value that depends on its generic
+  // instance, store the instance too.
+  if (result.instance_id.is_valid() &&
+      context.constant_values().Get(result.inst_id).is_symbolic()) {
+    result.inst_id = context.AddInst<SemIR::SpecificConstant>(
+        node_id, {.type_id = type_id,
+                  .inst_id = result.inst_id,
+                  .instance_id = result.instance_id});
+  }
+
   context.AddInstAndPush<SemIR::NameRef>(
       node_id,
       {.type_id = type_id, .name_id = name_id, .value_id = result.inst_id});

+ 13 - 0
toolchain/check/member_access.cpp

@@ -223,8 +223,21 @@ static auto LookupMemberNameInScope(Context& context, Parse::NodeId node_id,
     result = context.LookupQualifiedName(node_id, name_id, lookup_scope);
   }
 
+  // TODO: This duplicates the work that HandleNameAsExpr does. Factor this out.
   auto inst = context.insts().Get(result.inst_id);
   auto type_id = GetTypeInInstance(context, result.instance_id, inst.type_id());
+  CARBON_CHECK(type_id.is_valid()) << "Missing type for member " << inst;
+
+  // If the named entity has a constant value that depends on its generic
+  // instance, store the instance too.
+  if (result.instance_id.is_valid() &&
+      context.constant_values().Get(result.inst_id).is_symbolic()) {
+    result.inst_id = context.AddInst<SemIR::SpecificConstant>(
+        node_id, {.type_id = type_id,
+                  .inst_id = result.inst_id,
+                  .instance_id = result.instance_id});
+  }
+
   // TODO: Use a different kind of instruction that also references the
   // `base_id` so that `SemIR` consumers can find it.
   auto member_id = context.AddInst<SemIR::NameRef>(

+ 2 - 3
toolchain/check/operator.cpp

@@ -53,9 +53,8 @@ static auto GetOperatorOpFunction(Context& context, Parse::AnyExprId node_id,
   }
 
   // Look through import_refs and aliases.
-  auto op_const_id =
-      GetConstantInInstance(context, op_result.instance_id,
-                            context.constant_values().Get(op_result.inst_id));
+  auto op_const_id = GetConstantValueInInstance(
+      context.sem_ir(), op_result.instance_id, op_result.inst_id);
   auto op_id = context.constant_values().GetInstId(op_const_id);
 
   // We expect it to be an associated function.

+ 15 - 4
toolchain/check/scope_stack.cpp

@@ -14,19 +14,28 @@ auto ScopeStack::VerifyOnFinish() -> void {
 }
 
 auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
+                      SemIR::GenericInstanceId instance_id,
                       bool lexical_lookup_has_load_error) -> void {
   compile_time_binding_stack_.PushArray();
   scope_stack_.push_back(
       {.index = next_scope_index_,
        .scope_inst_id = scope_inst_id,
        .scope_id = scope_id,
+       .instance_id = instance_id,
        .next_compile_time_bind_index = SemIR::CompileTimeBindIndex(
            compile_time_binding_stack_.all_values_size()),
        .lexical_lookup_has_load_error =
            LexicalLookupHasLoadError() || lexical_lookup_has_load_error});
   if (scope_id.is_valid()) {
-    non_lexical_scope_stack_.push_back(
-        {.scope_index = next_scope_index_, .name_scope_id = scope_id});
+    non_lexical_scope_stack_.push_back({.scope_index = next_scope_index_,
+                                        .name_scope_id = scope_id,
+                                        .instance_id = instance_id});
+  } else {
+    // For lexical lookups, unqualified lookup doesn't know how to find the
+    // associated generic instance, so if we start adding lexical scopes with
+    // generic instances, we'll need to somehow track them in lookup.
+    CARBON_CHECK(!instance_id.is_valid())
+        << "Lexical scope should not have an associated generic instance.";
   }
 
   // TODO: Handle this case more gracefully.
@@ -207,8 +216,10 @@ auto ScopeStack::Restore(SuspendedScope scope) -> void {
       << 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});
+    non_lexical_scope_stack_.push_back(
+        {.scope_index = scope.entry.index,
+         .name_scope_id = scope.entry.scope_id,
+         .instance_id = scope.entry.instance_id});
   }
   scope_stack_.push_back(std::move(scope.entry));
 }

+ 11 - 3
toolchain/check/scope_stack.h

@@ -46,6 +46,9 @@ class ScopeStack {
 
     // The corresponding name scope.
     SemIR::NameScopeId name_scope_id;
+
+    // The corresponding generic instance.
+    SemIR::GenericInstanceId instance_id;
   };
 
   // Information about a scope that has been temporarily removed from the stack.
@@ -55,9 +58,11 @@ class ScopeStack {
   // scopes. lexical_lookup_has_load_error is used to limit diagnostics when a
   // given namespace may contain a mix of both successful and failed name
   // imports.
-  auto Push(SemIR::InstId scope_inst_id = SemIR::InstId::Invalid,
-            SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid,
-            bool lexical_lookup_has_load_error = false) -> void;
+  auto Push(
+      SemIR::InstId scope_inst_id = SemIR::InstId::Invalid,
+      SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid,
+      SemIR::GenericInstanceId instance_id = SemIR::GenericInstanceId::Invalid,
+      bool lexical_lookup_has_load_error = false) -> void;
 
   // Pops the top scope from scope_stack_, cleaning up names from
   // lexical_lookup_.
@@ -168,6 +173,9 @@ class ScopeStack {
     // The name scope associated with this entry, if any.
     SemIR::NameScopeId scope_id;
 
+    // The generic instance associated with this entry, if any.
+    SemIR::GenericInstanceId instance_id;
+
     // The next compile-time binding index to allocate in this scope.
     SemIR::CompileTimeBindIndex next_compile_time_bind_index;
 

+ 12 - 9
toolchain/check/testdata/basics/no_prelude/raw_ir.carbon

@@ -28,11 +28,12 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:     bind_name0:      {name: name1, parent_scope: name_scope<invalid>, index: comp_time_bind0}
 // CHECK:STDOUT:     bind_name1:      {name: name2, parent_scope: name_scope<invalid>, index: comp_time_bind<invalid>}
 // CHECK:STDOUT:   functions:
-// CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block5, return_storage: inst+15, return_slot: present, body: [block10]}
+// CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block5, return_storage: inst+15, return_slot: present, body: [block11]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   generics:
 // CHECK:STDOUT:     generic0:        {decl: inst+16, bindings: block8}
-// CHECK:STDOUT:   generic_instances: {}
+// CHECK:STDOUT:   generic_instances:
+// CHECK:STDOUT:     genericInstance0: {generic: generic0, args: block10}
 // CHECK:STDOUT:   types:
 // CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:     type1:           {constant: symbolic 0, value_rep: {kind: copy, type: type1}}
@@ -74,14 +75,14 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // 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: block11, type: type4}
+// CHECK:STDOUT:     'inst+22':         {kind: TupleLiteral, arg0: block12, 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: block13, type: type2}
+// CHECK:STDOUT:     'inst+27':         {kind: TupleValue, arg0: block14, type: type2}
 // CHECK:STDOUT:     'inst+28':         {kind: Converted, arg0: inst+21, arg1: inst+26, type: type2}
-// CHECK:STDOUT:     'inst+29':         {kind: TupleInit, arg0: block12, arg1: inst+15, type: type4}
+// CHECK:STDOUT:     'inst+29':         {kind: TupleInit, arg0: block13, 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:
@@ -133,6 +134,8 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:       0:               inst+2
 // CHECK:STDOUT:       1:               inst+14
 // CHECK:STDOUT:     block10:
+// CHECK:STDOUT:       0:               inst+3
+// CHECK:STDOUT:     block11:
 // CHECK:STDOUT:       0:               inst+20
 // CHECK:STDOUT:       1:               inst+21
 // CHECK:STDOUT:       2:               inst+22
@@ -144,14 +147,14 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:       8:               inst+29
 // CHECK:STDOUT:       9:               inst+30
 // CHECK:STDOUT:       10:              inst+31
-// CHECK:STDOUT:     block11:
+// CHECK:STDOUT:     block12:
 // CHECK:STDOUT:       0:               inst+20
 // CHECK:STDOUT:       1:               inst+21
-// CHECK:STDOUT:     block12:
+// CHECK:STDOUT:     block13:
 // CHECK:STDOUT:       0:               inst+24
 // CHECK:STDOUT:       1:               inst+28
-// CHECK:STDOUT:     block13:         {}
-// CHECK:STDOUT:     block14:
+// CHECK:STDOUT:     block14:         {}
+// CHECK:STDOUT:     block15:
 // CHECK:STDOUT:       0:               inst+0
 // CHECK:STDOUT:       1:               inst+16
 // CHECK:STDOUT: ...

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

@@ -85,9 +85,10 @@ fn Class(N:! i32).F[self: Self](n: T) {}
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: %.2 = field_decl a, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [symbolic = %Self.ref (constants.%Class.2)]
-// CHECK:STDOUT:     %self.loc13_8.1: @Class.%Self.ref (%Class.2) = param self
-// CHECK:STDOUT:     %self.loc13_8.2: @Class.%Self.ref (%Class.2) = bind_name self, %self.loc13_8.1
+// CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc13 [symbolic = %.loc13 (constants.%Class.2)]
+// CHECK:STDOUT:     %self.loc13_8.1: @Class.%.loc13 (%Class.2) = param self
+// CHECK:STDOUT:     %self.loc13_8.2: @Class.%.loc13 (%Class.2) = bind_name self, %self.loc13_8.1
 // CHECK:STDOUT:     %T.ref.loc13: type = name_ref T, file.%T.loc11_13.2 [symbolic = %T.ref.loc13 (constants.%T)]
 // CHECK:STDOUT:     %n.loc13_20.1: @Class.%T.ref.loc13 (%T) = param n
 // CHECK:STDOUT:     %n.loc13_20.2: @Class.%T.ref.loc13 (%T) = bind_name n, %n.loc13_20.1
@@ -99,7 +100,7 @@ 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.%Self.ref (%Class.2)](@Class.%n.loc13_20.2: @Class.%T.ref.loc13 (%T))
+// CHECK:STDOUT: fn @F[@Class.%self.loc13_8.2: @Class.%.loc13 (%Class.2)](@Class.%n.loc13_20.2: @Class.%T.ref.loc13 (%T))
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";

+ 7 - 5
toolchain/check/testdata/class/generic/basic.carbon

@@ -57,7 +57,8 @@ class Class(T:! type) {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %GetAddr.decl: %GetAddr.type = fn_decl @GetAddr [template = constants.%GetAddr] {
-// CHECK:STDOUT:     %Self.ref.loc12: type = name_ref Self, constants.%Class.2 [symbolic = %Self.ref.loc12 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc12_25: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc12_25 (constants.%Class.2)]
+// CHECK:STDOUT:     %Self.ref.loc12: type = name_ref Self, %.loc12_25 [symbolic = %.loc12_25 (constants.%Class.2)]
 // CHECK:STDOUT:     %.loc12_29: type = ptr_type %Class.2 [symbolic = %.loc12_29 (constants.%.2)]
 // CHECK:STDOUT:     %self.loc12_19.1: @Class.%.loc12_29 (%.2) = param self
 // CHECK:STDOUT:     %self.loc12_19.3: @Class.%.loc12_29 (%.2) = bind_name self, %self.loc12_19.1
@@ -67,9 +68,10 @@ class Class(T:! type) {
 // CHECK:STDOUT:     %return.var.loc12: ref %.3 = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %GetValue.decl: %GetValue.type = fn_decl @GetValue [template = constants.%GetValue] {
-// CHECK:STDOUT:     %Self.ref.loc17: type = name_ref Self, constants.%Class.2 [symbolic = %Self.ref.loc17 (constants.%Class.2)]
-// CHECK:STDOUT:     %self.loc17_15.1: @Class.%Self.ref.loc17 (%Class.2) = param self
-// CHECK:STDOUT:     %self.loc17_15.2: @Class.%Self.ref.loc17 (%Class.2) = bind_name self, %self.loc17_15.1
+// CHECK:STDOUT:     %.loc17: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc17 (constants.%Class.2)]
+// CHECK:STDOUT:     %Self.ref.loc17: type = name_ref Self, %.loc17 [symbolic = %.loc17 (constants.%Class.2)]
+// CHECK:STDOUT:     %self.loc17_15.1: @Class.%.loc17 (%Class.2) = param self
+// CHECK:STDOUT:     %self.loc17_15.2: @Class.%.loc17 (%Class.2) = bind_name self, %self.loc17_15.1
 // CHECK:STDOUT:     %T.ref.loc17: type = name_ref T, file.%T.loc11_13.2 [symbolic = %T.ref.loc17 (constants.%T)]
 // CHECK:STDOUT:     %return.var.loc17: ref %T = var <return slot>
 // CHECK:STDOUT:   }
@@ -94,7 +96,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:   return %.loc13_12
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @GetValue[@Class.%self.loc17_15.2: @Class.%Self.ref.loc17 (%Class.2)]() -> %T
+// CHECK:STDOUT: fn @GetValue[@Class.%self.loc17_15.2: @Class.%.loc17 (%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

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

@@ -89,7 +89,8 @@ fn Run() -> i32 {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %Get.decl: %Get.type = fn_decl @Get [template = constants.%Get] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [symbolic = %Self.ref (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc12_21: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc12_21 (constants.%Class.2)]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc12_21 [symbolic = %.loc12_21 (constants.%Class.2)]
 // CHECK:STDOUT:     %.loc12_25: type = ptr_type %Class.2 [symbolic = %.loc12_25 (constants.%.2)]
 // CHECK:STDOUT:     %self.loc12_15.1: @Class.%.loc12_25 (%.2) = param self
 // CHECK:STDOUT:     %self.loc12_15.3: @Class.%.loc12_25 (%.2) = bind_name self, %self.loc12_15.1

+ 6 - 4
toolchain/check/testdata/class/generic/member_out_of_line.carbon

@@ -191,7 +191,8 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:     %T.ref.loc10_22: type = name_ref T, %T.loc10_6.2 [symbolic = constants.%T]
 // CHECK:STDOUT:     %N.loc10_18.1: %T = param N
 // CHECK:STDOUT:     %N.loc10_18.2: %T = bind_symbolic_name N 1, %N.loc10_18.1 [symbolic = constants.%N]
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%B.2 [symbolic = constants.%B.2]
+// CHECK:STDOUT:     %.loc10: type = specific_constant constants.%B.2, (constants.%T, constants.%N) [symbolic = constants.%B.2]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc10 [symbolic = constants.%B.2]
 // CHECK:STDOUT:     %self.loc10_27.1: %B.2 = param self
 // CHECK:STDOUT:     @F.%self: %B.2 = bind_name self, %self.loc10_27.1
 // CHECK:STDOUT:     %T.ref.loc10_42: type = name_ref T, %T.loc10_6.2 [symbolic = constants.%T]
@@ -216,9 +217,10 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT: class @B
 // CHECK:STDOUT:     generic [file.%T.loc4_9.2: type, @A.%N.loc5_11.2: @A.%T.ref (%T)] {
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%B.2 [symbolic = %Self.ref (constants.%B.2)]
-// CHECK:STDOUT:     %self.loc6_10.1: @B.%Self.ref (%B.2) = param self
-// CHECK:STDOUT:     %self.loc6_10.2: @B.%Self.ref (%B.2) = bind_name self, %self.loc6_10.1
+// CHECK:STDOUT:     %.loc6: type = specific_constant constants.%B.2, (constants.%T, constants.%N) [symbolic = %.loc6 (constants.%B.2)]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc6 [symbolic = %.loc6 (constants.%B.2)]
+// CHECK:STDOUT:     %self.loc6_10.1: @B.%.loc6 (%B.2) = param self
+// CHECK:STDOUT:     %self.loc6_10.2: @B.%.loc6 (%B.2) = bind_name self, %self.loc6_10.1
 // CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc4_9.2 [symbolic = %T.ref (constants.%T)]
 // CHECK:STDOUT:     %a.loc6_22.1: @B.%T.ref (%T) = param a
 // CHECK:STDOUT:     %a.loc6_22.2: @B.%T.ref (%T) = bind_name a, %a.loc6_22.1

+ 6 - 4
toolchain/check/testdata/class/generic/self.carbon

@@ -53,7 +53,8 @@ class Class(T:! type) {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %MakeSelf.decl: %MakeSelf.type = fn_decl @MakeSelf [template = constants.%MakeSelf] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [symbolic = %Self.ref (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc14: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc14 (constants.%Class.2)]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc14 [symbolic = %.loc14 (constants.%Class.2)]
 // CHECK:STDOUT:     %return.var.loc14: ref %Class.2 = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %MakeClass.decl: %MakeClass.type = fn_decl @MakeClass [template = constants.%MakeClass] {
@@ -93,12 +94,13 @@ class Class(T:! type) {
 // CHECK:STDOUT:   %.loc17_9: ref %Class.2 = splice_block %c.var {}
 // CHECK:STDOUT:   %MakeSelf.call: init %Class.2 = call %MakeSelf.ref() to %.loc17_9
 // CHECK:STDOUT:   assign %c.var, %MakeSelf.call
-// CHECK:STDOUT:   %Self.ref: type = name_ref Self, constants.%Class.2 [symbolic = constants.%Class.2]
+// CHECK:STDOUT:   %.loc18_12: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = constants.%Class.2]
+// CHECK:STDOUT:   %Self.ref: type = name_ref Self, %.loc18_12 [symbolic = constants.%Class.2]
 // CHECK:STDOUT:   %s.var: ref %Class.2 = var s
 // CHECK:STDOUT:   %s: ref %Class.2 = bind_name s, %s.var
 // CHECK:STDOUT:   %MakeClass.ref: %MakeClass.type = name_ref MakeClass, @Class.%MakeClass.decl [template = constants.%MakeClass]
-// CHECK:STDOUT:   %.loc18: ref %Class.2 = splice_block %s.var {}
-// CHECK:STDOUT:   %MakeClass.call: init %Class.2 = call %MakeClass.ref() to %.loc18
+// CHECK:STDOUT:   %.loc18_9: ref %Class.2 = splice_block %s.var {}
+// CHECK:STDOUT:   %MakeClass.call: init %Class.2 = call %MakeClass.ref() to %.loc18_9
 // CHECK:STDOUT:   assign %s.var, %MakeClass.call
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 6 - 4
toolchain/check/testdata/class/generic_method.carbon

@@ -44,7 +44,8 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
 // CHECK:STDOUT:     %T.loc16_10.1: type = param T
 // CHECK:STDOUT:     %T.loc16_10.2: type = bind_symbolic_name T 0, %T.loc16_10.1 [symbolic = constants.%T]
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [symbolic = constants.%Class.2]
+// CHECK:STDOUT:     %.loc16: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = constants.%Class.2]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc16 [symbolic = constants.%Class.2]
 // CHECK:STDOUT:     %self.loc16_22.1: %Class.2 = param self
 // CHECK:STDOUT:     @F.%self: %Class.2 = bind_name self, %self.loc16_22.1
 // CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc16_10.2 [symbolic = constants.%T]
@@ -58,9 +59,10 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: %.2 = field_decl a, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [symbolic = %Self.ref (constants.%Class.2)]
-// CHECK:STDOUT:     %self.loc13_8.1: @Class.%Self.ref (%Class.2) = param self
-// CHECK:STDOUT:     %self.loc13_8.2: @Class.%Self.ref (%Class.2) = bind_name self, %self.loc13_8.1
+// CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc13 [symbolic = %.loc13 (constants.%Class.2)]
+// CHECK:STDOUT:     %self.loc13_8.1: @Class.%.loc13 (%Class.2) = param self
+// CHECK:STDOUT:     %self.loc13_8.2: @Class.%.loc13 (%Class.2) = bind_name self, %self.loc13_8.1
 // CHECK:STDOUT:     %T.ref.loc13: type = name_ref T, file.%T.loc11_13.2 [symbolic = %T.ref.loc13 (constants.%T)]
 // CHECK:STDOUT:     %n.loc13_20.1: @Class.%T.ref.loc13 (%T) = param n
 // CHECK:STDOUT:     %n.loc13_20.2: @Class.%T.ref.loc13 (%T) = bind_name n, %n.loc13_20.1

+ 22 - 18
toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon

@@ -49,14 +49,16 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   %.decl: %.type = fn_decl @.1 [template = constants.%.5] {
 // CHECK:STDOUT:     %T.loc22_6.1: type = param T
 // CHECK:STDOUT:     %T.loc22_6.2: type = bind_symbolic_name T 0, %T.loc22_6.1 [symbolic = %T.loc22_6.2 (constants.%T)]
-// CHECK:STDOUT:     %Self.ref.loc22_24: <unexpected instref inst+38> (%.2) = name_ref Self, @I.%Self [symbolic = %Self.ref.loc22_24 (constants.%Self)]
-// CHECK:STDOUT:     %.loc22_24.1: type = facet_type_access %Self.ref.loc22_24 [symbolic = %Self.ref.loc22_24 (constants.%Self)]
-// CHECK:STDOUT:     %.loc22_24.2: type = converted %Self.ref.loc22_24, %.loc22_24.1 [symbolic = %Self.ref.loc22_24 (constants.%Self)]
-// CHECK:STDOUT:     %self.loc22_18.1: file.%Self.ref.loc22_24 (%Self) = param self
-// CHECK:STDOUT:     @.1.%self: file.%Self.ref.loc22_24 (%Self) = bind_name self, %self.loc22_18.1
-// CHECK:STDOUT:     %Self.ref.loc22_35: <unexpected instref inst+38> (%.2) = name_ref Self, @I.%Self [symbolic = %Self.ref.loc22_24 (constants.%Self)]
-// CHECK:STDOUT:     %.loc22_35.1: type = facet_type_access %Self.ref.loc22_35 [symbolic = %Self.ref.loc22_24 (constants.%Self)]
-// CHECK:STDOUT:     %.loc22_35.2: type = converted %Self.ref.loc22_35, %.loc22_35.1 [symbolic = %Self.ref.loc22_24 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_24.1: <unexpected instref inst+42> (%.2) = specific_constant @I.%Self, (constants.%T) [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %Self.ref.loc22_24: <unexpected instref inst+42> (%.2) = name_ref Self, %.loc22_24.1 [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_24.2: type = facet_type_access %Self.ref.loc22_24 [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_24.3: type = converted %Self.ref.loc22_24, %.loc22_24.2 [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %self.loc22_18.1: file.%.loc22_24.1 (%Self) = param self
+// CHECK:STDOUT:     @.1.%self: file.%.loc22_24.1 (%Self) = bind_name self, %self.loc22_18.1
+// CHECK:STDOUT:     %.loc22_35.1: <unexpected instref inst+42> (%.2) = specific_constant @I.%Self, (constants.%T) [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %Self.ref.loc22_35: <unexpected instref inst+42> (%.2) = name_ref Self, %.loc22_35.1 [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_35.2: type = facet_type_access %Self.ref.loc22_35 [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_35.3: type = converted %Self.ref.loc22_35, %.loc22_35.2 [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     @.1.%return: ref %Self = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -65,14 +67,16 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 1 [symbolic = constants.%Self]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %Self.ref.loc13_14: %.2 = name_ref Self, %Self [symbolic = %Self.ref.loc13_14 (constants.%Self)]
-// CHECK:STDOUT:     %.loc13_14.1: type = facet_type_access %Self.ref.loc13_14 [symbolic = %Self.ref.loc13_14 (constants.%Self)]
-// CHECK:STDOUT:     %.loc13_14.2: type = converted %Self.ref.loc13_14, %.loc13_14.1 [symbolic = %Self.ref.loc13_14 (constants.%Self)]
-// CHECK:STDOUT:     %self.loc13_8.1: @I.%Self.ref.loc13_14 (%Self) = param self
-// CHECK:STDOUT:     %self.loc13_8.2: @I.%Self.ref.loc13_14 (%Self) = bind_name self, %self.loc13_8.1
-// CHECK:STDOUT:     %Self.ref.loc13_25: %.2 = name_ref Self, %Self [symbolic = %Self.ref.loc13_14 (constants.%Self)]
-// CHECK:STDOUT:     %.loc13_25.1: type = facet_type_access %Self.ref.loc13_25 [symbolic = %Self.ref.loc13_14 (constants.%Self)]
-// CHECK:STDOUT:     %.loc13_25.2: type = converted %Self.ref.loc13_25, %.loc13_25.1 [symbolic = %Self.ref.loc13_14 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_14.1: %.2 = specific_constant %Self, (constants.%T) [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %Self.ref.loc13_14: %.2 = name_ref Self, %.loc13_14.1 [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_14.2: type = facet_type_access %Self.ref.loc13_14 [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_14.3: type = converted %Self.ref.loc13_14, %.loc13_14.2 [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %self.loc13_8.1: @I.%.loc13_14.1 (%Self) = param self
+// CHECK:STDOUT:     %self.loc13_8.2: @I.%.loc13_14.1 (%Self) = bind_name self, %self.loc13_8.1
+// CHECK:STDOUT:     %.loc13_25.1: %.2 = specific_constant %Self, (constants.%T) [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %Self.ref.loc13_25: %.2 = name_ref Self, %.loc13_25.1 [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_25.2: type = facet_type_access %Self.ref.loc13_25 [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_25.3: type = converted %Self.ref.loc13_25, %.loc13_25.2 [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %return.var: ref %Self = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc13_29: %.3 = assoc_entity element0, %F.decl [template = constants.%.4]
@@ -83,10 +87,10 @@ 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.loc13_8.2: @I.%Self.ref.loc13_14 (%Self)]() -> %Self
+// CHECK:STDOUT: fn @F[@I.%self.loc13_8.2: @I.%.loc13_14.1 (%Self)]() -> %Self
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type, @I.%Self: %.2];
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1[%self: file.%Self.ref.loc22_24 (%Self)]() -> %Self
+// CHECK:STDOUT: fn @.1[%self: file.%.loc22_24.1 (%Self)]() -> %Self
 // CHECK:STDOUT:     generic [file.%T.loc22_6.2: type] {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: %Self = name_ref self, %self

+ 1 - 1
toolchain/lower/constant.cpp

@@ -5,11 +5,11 @@
 #include "toolchain/lower/constant.h"
 
 #include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/StringRef.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Value.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/lower/file_context.h"
+#include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/inst.h"
 
 namespace Carbon::Lower {

+ 6 - 0
toolchain/sem_ir/file.cpp

@@ -442,6 +442,7 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
       case ImportRefLoaded::Kind:
       case ImportRefUnloaded::Kind:
       case InitializeFrom::Kind:
+      case SpecificConstant::Kind:
       case InterfaceDecl::Kind:
       case InterfaceWitness::Kind:
       case InterfaceWitnessAccess::Kind:
@@ -548,6 +549,11 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
         continue;
       }
 
+      case CARBON_KIND(SpecificConstant inst): {
+        inst_id = inst.inst_id;
+        continue;
+      }
+
       case AddrOf::Kind:
       case AddrPattern::Kind:
       case ArrayType::Kind:

+ 3 - 3
toolchain/sem_ir/file.h

@@ -129,8 +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 generics() -> GenericStore& { return generics_; }
+  auto generics() const -> const GenericStore& { return generics_; }
   auto generic_instances() -> GenericInstanceStore& {
     return generic_instances_;
   }
@@ -218,7 +218,7 @@ class File : public Printable<File> {
   ImplStore impls_;
 
   // Storage for generics.
-  ValueStore<GenericId> generics_;
+  GenericStore generics_;
 
   // Storage for instances of generics.
   GenericInstanceStore generic_instances_;

+ 28 - 0
toolchain/sem_ir/generic.cpp

@@ -4,6 +4,8 @@
 
 #include "toolchain/sem_ir/generic.h"
 
+#include "toolchain/sem_ir/file.h"
+
 namespace Carbon::SemIR {
 
 class GenericInstanceStore::KeyContext
@@ -42,4 +44,30 @@ auto GenericInstanceStore::GetOrAdd(GenericId generic_id, InstBlockId args_id)
       .key();
 }
 
+auto GetConstantInInstance(const File& sem_ir,
+                           GenericInstanceId /*instance_id*/,
+                           ConstantId const_id) -> ConstantId {
+  if (!const_id.is_symbolic()) {
+    // Type does not depend on a generic parameter.
+    return const_id;
+  }
+
+  const auto& symbolic = sem_ir.constant_values().GetSymbolicConstant(const_id);
+  if (!symbolic.generic_id.is_valid()) {
+    // Constant is an abstract symbolic constant, not an instance-specific one.
+    return const_id;
+  }
+
+  // TODO: Look up the value in the generic instance. For now, return the
+  // canonical constant value.
+  return sem_ir.constant_values().Get(symbolic.inst_id);
+}
+
+auto GetConstantValueInInstance(const File& sem_ir,
+                                GenericInstanceId instance_id, InstId inst_id)
+    -> ConstantId {
+  return GetConstantInInstance(sem_ir, instance_id,
+                               sem_ir.constant_values().Get(inst_id));
+}
+
 }  // namespace Carbon::SemIR

+ 28 - 0
toolchain/sem_ir/generic.h

@@ -28,6 +28,10 @@ struct Generic : public Printable<Generic> {
   // The index in this block will match the `bind_index` in the name binding
   // instruction's `BindNameInfo`.
   InstBlockId bindings_id;
+  // The self instance of this generic, which is an instance where every generic
+  // parameter's argument is that same parameter. For example, the self instance
+  // of `Vector(T:! type)` is `Vector(T)`.
+  GenericInstanceId self_instance_id;
 
   // The following members are set at the end of the corresponding region of the
   // generic.
@@ -37,6 +41,17 @@ struct Generic : public Printable<Generic> {
   InstBlockId decl_block_id = InstBlockId::Invalid;
 };
 
+// Provides storage for generics.
+class GenericStore : public ValueStore<GenericId> {
+ public:
+  // Get the self-instance for a generic, or an invalid instance for an invalid
+  // generic ID.
+  auto GetSelfInstance(GenericId id) -> GenericInstanceId {
+    return id.is_valid() ? Get(id).self_instance_id
+                         : GenericInstanceId::Invalid;
+  }
+};
+
 // An instance of a generic entity, such as an instance of a generic function.
 // For each construct that depends on a compile-time parameter in the generic
 // entity, this contains the corresponding non-generic value. This includes
@@ -92,6 +107,19 @@ class GenericInstanceStore : public Yaml::Printable<GenericInstanceStore> {
   Carbon::Set<GenericInstanceId, 0, KeyContext> lookup_table_;
 };
 
+// Gets the substituted value of a constant within a specified instance of a
+// generic. Note that this does not perform substitution, and will return
+// `Invalid` if the substituted constant value is not yet known.
+auto GetConstantInInstance(const File& sem_ir, GenericInstanceId instance_id,
+                           ConstantId const_id) -> ConstantId;
+
+// Gets the substituted constant value of an instruction within a specified
+// instance of a generic. Note that this does not perform substitution, and will
+// return `Invalid` if the substituted constant value is not yet known.
+auto GetConstantValueInInstance(const File& sem_ir,
+                                GenericInstanceId instance_id, InstId inst_id)
+    -> ConstantId;
+
 }  // namespace Carbon::SemIR
 
 #endif  // CARBON_TOOLCHAIN_SEM_IR_GENERIC_H_

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -62,6 +62,7 @@ CARBON_SEM_IR_INST_KIND(ImportDecl)
 CARBON_SEM_IR_INST_KIND(ImportRefUnloaded)
 CARBON_SEM_IR_INST_KIND(ImportRefLoaded)
 CARBON_SEM_IR_INST_KIND(InitializeFrom)
+CARBON_SEM_IR_INST_KIND(SpecificConstant)
 CARBON_SEM_IR_INST_KIND(InterfaceDecl)
 CARBON_SEM_IR_INST_KIND(InterfaceType)
 CARBON_SEM_IR_INST_KIND(InterfaceWitness)

+ 16 - 0
toolchain/sem_ir/typed_insts.h

@@ -821,6 +821,22 @@ struct ReturnExpr {
   InstId dest_id;
 };
 
+// Given an instruction with a constant value that depends on a generic
+// parameter, selects an instance of that instruction with the constant value
+// corresponding to a particular specific.
+//
+// TODO: We only form these as the instruction referenced by a `NameRef`.
+// Consider merging an `SpecificConstant` + `NameRef` into a new form of
+// instruction in order to give a more compact representation.
+struct SpecificConstant {
+  static constexpr auto Kind = InstKind::SpecificConstant.Define<Parse::NodeId>(
+      {.ir_name = "specific_constant", .is_lowered = false});
+
+  TypeId type_id;
+  InstId inst_id;
+  GenericInstanceId instance_id;
+};
+
 // Splices a block into the location where this appears. This may be an
 // expression, producing a result with a given type. For example, when
 // constructing from aggregates we may figure out which conversions are required