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

Vtable support for generics (#5793)

Some specific features:

* Use `SpecificFunction` for vtable entries for generic classes.
* Create specific constants for vtable entries in classes derived from
  generic classes to reference the appropriate specific of the function
  in the context of such a derived class.
* Create specific constants for vtable_ptrs for uses of specific generic
  classes.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
David Blaikie пре 9 месеци
родитељ
комит
27be0973e7

+ 83 - 52
toolchain/check/class.cpp

@@ -14,14 +14,6 @@
 
 namespace Carbon::Check {
 
-auto TryGetAsClass(Context& context, SemIR::TypeId type_id) -> SemIR::Class* {
-  auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id);
-  if (!class_type) {
-    return nullptr;
-  }
-  return &context.classes().Get(class_type->class_id);
-}
-
 auto SetClassSelfType(Context& context, SemIR::ClassId class_id) -> void {
   auto& class_info = context.classes().Get(class_id);
   auto specific_id = context.generics().GetSelfSpecific(class_info.generic_id);
@@ -131,10 +123,51 @@ static auto AddStructTypeFields(
 
 // Builds and returns a vtable for the current class. Assumes that the virtual
 // functions for the class are listed as the top element of the `vtable_stack`.
-static auto BuildVtable(Context& context, SemIR::ClassId class_id,
-                        SemIR::VtableId base_vtable_id,
+static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id,
+                        SemIR::ClassId class_id,
+                        std::optional<SemIR::ClassType> base_class_type,
                         llvm::ArrayRef<SemIR::InstId> vtable_contents)
     -> SemIR::VtableId {
+  auto base_vtable_id = SemIR::VtableId::None;
+  auto base_class_specific_id = SemIR::SpecificId::None;
+
+  // Get some base class/type/specific info.
+  if (base_class_type) {
+    auto& base_class_info = context.classes().Get(base_class_type->class_id);
+    auto base_vtable_ptr_inst_id = base_class_info.vtable_ptr_id;
+    if (base_vtable_ptr_inst_id.has_value()) {
+      LoadImportRef(context, base_vtable_ptr_inst_id);
+      auto canonical_base_vtable_inst_id =
+          context.constant_values().GetConstantInstId(base_vtable_ptr_inst_id);
+      const auto& base_vtable_ptr_inst =
+          context.insts().GetAs<SemIR::VtablePtr>(
+              canonical_base_vtable_inst_id);
+      base_vtable_id = base_vtable_ptr_inst.vtable_id;
+      base_class_specific_id = base_class_type->specific_id;
+    }
+  }
+
+  const auto& class_info = context.classes().Get(class_id);
+  auto class_generic_id = class_info.generic_id;
+
+  // Wrap vtable entries in SpecificFunctions as needed/in generic classes.
+  auto build_specific_function =
+      [&](SemIR::InstId fn_decl_id) -> SemIR::InstId {
+    if (!class_generic_id.has_value()) {
+      return fn_decl_id;
+    }
+    const auto& fn_decl =
+        context.insts().GetAs<SemIR::FunctionDecl>(fn_decl_id);
+    const auto& function = context.functions().Get(fn_decl.function_id);
+    return GetOrAddInst<SemIR::SpecificFunction>(
+        context, node_id,
+        {.type_id =
+             GetSingletonType(context, SemIR::SpecificFunctionType::TypeInstId),
+         .callee_id = fn_decl_id,
+         .specific_id =
+             context.generics().GetSelfSpecific(function.generic_id)});
+  };
+
   llvm::SmallVector<SemIR::InstId> vtable;
   if (base_vtable_id.has_value()) {
     auto base_vtable_inst_block = context.inst_blocks().Get(
@@ -144,25 +177,36 @@ static auto BuildVtable(Context& context, SemIR::ClassId class_id,
     for (auto fn_decl_id : base_vtable_inst_block) {
       auto fn_decl = GetCalleeFunction(context.sem_ir(), fn_decl_id);
       const auto& fn = context.functions().Get(fn_decl.function_id);
-      for (auto override_fn_decl_id : vtable_contents) {
-        auto override_fn_decl =
-            context.insts().GetAs<SemIR::FunctionDecl>(override_fn_decl_id);
-        auto& override_fn =
-            context.functions().Get(override_fn_decl.function_id);
-        if (override_fn.virtual_modifier ==
-                SemIR::FunctionFields::VirtualModifier::Impl &&
-            override_fn.name_id == fn.name_id) {
-          // TODO: Support generic base classes, rather than passing
-          // `SpecificId::None`.
-          CheckFunctionTypeMatches(context, override_fn, fn,
-                                   SemIR::SpecificId::None,
-                                   /*check_syntax=*/false,
-                                   /*check_self=*/false);
-          fn_decl_id = override_fn_decl_id;
-          override_fn.virtual_index = vtable.size();
-          CARBON_CHECK(override_fn.virtual_index == fn.virtual_index);
-          break;
-        }
+      const auto* i = llvm::find_if(
+          vtable_contents, [&](SemIR::InstId override_fn_decl_id) -> bool {
+            const auto& override_fn = context.functions().Get(
+                context.insts()
+                    .GetAs<SemIR::FunctionDecl>(override_fn_decl_id)
+                    .function_id);
+            return override_fn.virtual_modifier ==
+                       SemIR::FunctionFields::VirtualModifier::Impl &&
+                   override_fn.name_id == fn.name_id;
+          });
+      if (i != vtable_contents.end()) {
+        auto& override_fn = context.functions().Get(
+            context.insts().GetAs<SemIR::FunctionDecl>(*i).function_id);
+        // TODO: Support generic base classes, rather than passing
+        // `SpecificId::None`. This'll need to `GetConstantValueInSpecific` for
+        // the base function, then extract the specific from that for use here.
+        CheckFunctionTypeMatches(context, override_fn, fn,
+                                 SemIR::SpecificId::None,
+                                 /*check_syntax=*/false,
+                                 /*check_self=*/false);
+        fn_decl_id = build_specific_function(*i);
+        override_fn.virtual_index = vtable.size();
+        CARBON_CHECK(override_fn.virtual_index == fn.virtual_index);
+      } else {
+        // Remap the base's vtable entry to the appropriate constant usable in
+        // the context of the derived class (for the specific for the base
+        // class, for instance)..
+        fn_decl_id = context.sem_ir().constant_values().GetInstId(
+            GetConstantValueInSpecific(context.sem_ir(), base_class_specific_id,
+                                       fn_decl_id));
       }
       vtable.push_back(fn_decl_id);
     }
@@ -173,7 +217,7 @@ static auto BuildVtable(Context& context, SemIR::ClassId class_id,
     auto& fn = context.functions().Get(fn_decl.function_id);
     if (fn.virtual_modifier != SemIR::FunctionFields::VirtualModifier::Impl) {
       fn.virtual_index = vtable.size();
-      vtable.push_back(inst_id);
+      vtable.push_back(build_specific_function(inst_id));
     }
   }
 
@@ -200,12 +244,13 @@ static auto CheckCompleteClassType(
       class_info.GetBaseType(context.sem_ir(), SemIR::SpecificId::None);
   // TODO: Use InstId from base declaration.
   auto base_type_inst_id = context.types().GetInstId(base_type_id);
-  SemIR::Class* base_class_info = nullptr;
+  std::optional<SemIR::ClassType> base_class_type;
   if (base_type_id.has_value()) {
     // TODO: If the base class is template dependent, we will need to decide
     // whether to add a vptr as part of instantiation.
-    base_class_info = TryGetAsClass(context, base_type_id);
-    if (base_class_info && base_class_info->is_dynamic) {
+    base_class_type = context.types().TryGetAs<SemIR::ClassType>(base_type_id);
+    if (base_class_type &&
+        context.classes().Get(base_class_type->class_id).is_dynamic) {
       defining_vptr = false;
     }
   }
@@ -229,35 +274,21 @@ static auto CheckCompleteClassType(
   }
 
   if (class_info.is_dynamic) {
-    SemIR::VtableId base_vtable_id = SemIR::VtableId::None;
-    if (base_class_info) {
-      auto base_vtable_ptr_inst_id = base_class_info->vtable_ptr_id;
-      if (base_vtable_ptr_inst_id.has_value()) {
-        LoadImportRef(context, base_vtable_ptr_inst_id);
-        auto canonical_base_vtable_inst_id =
-            context.constant_values().GetConstantInstId(
-                base_vtable_ptr_inst_id);
-        const auto& base_vtable_ptr_inst =
-            context.insts().GetAs<SemIR::VtablePtr>(
-                canonical_base_vtable_inst_id);
-        base_vtable_id = base_vtable_ptr_inst.vtable_id;
-        // TODO: Retrieve the specific_id from the base_vtable_ptr_inst here,
-        // for use in BuildVtable.
-      }
-    }
-    auto vtable_id =
-        BuildVtable(context, class_id, base_vtable_id, vtable_contents);
+    auto vtable_id = BuildVtable(context, node_id, class_id, base_class_type,
+                                 vtable_contents);
 
     auto vptr_type_id = GetPointerType(context, SemIR::VtableType::TypeInstId);
     // TODO: Handle specifics here, probably passing
     // `context.generics().GetSelfSpecific(class_info.generic_id)` as the
     // specific_id here (but more work involved to get this all plumbed in and
     // tested).
+    auto generic_id = class_info.generic_id;
+    auto self_specific_id = context.generics().GetSelfSpecific(generic_id);
     class_info.vtable_ptr_id =
         AddInst<SemIR::VtablePtr>(context, node_id,
                                   {.type_id = vptr_type_id,
                                    .vtable_id = vtable_id,
-                                   .specific_id = SemIR::SpecificId::None});
+                                   .specific_id = self_specific_id});
   }
 
   auto struct_type_inst_id = AddTypeInst<SemIR::StructType>(

+ 0 - 4
toolchain/check/class.h

@@ -9,10 +9,6 @@
 
 namespace Carbon::Check {
 
-// If `type_id` is a class type, get its corresponding `SemIR::Class` object.
-// Otherwise returns `nullptr`.
-auto TryGetAsClass(Context& context, SemIR::TypeId type_id) -> SemIR::Class*;
-
 // Sets the `Self` type for the class.
 auto SetClassSelfType(Context& context, SemIR::ClassId class_id) -> void;
 

+ 6 - 0
toolchain/check/convert.cpp

@@ -589,6 +589,12 @@ static auto ConvertStructToClass(
 
   if (!dest_vtable_ptr_inst_id.has_value()) {
     dest_vtable_ptr_inst_id = dest_class_info.vtable_ptr_id;
+    if (dest_type.specific_id.has_value() &&
+        dest_vtable_ptr_inst_id.has_value()) {
+      dest_vtable_ptr_inst_id = context.constant_values().GetInstId(
+          GetConstantValueInSpecific(context.sem_ir(), dest_type.specific_id,
+                                     dest_vtable_ptr_inst_id));
+    }
   }
 
   if (dest_vtable_ptr_inst_id.has_value()) {

+ 8 - 5
toolchain/check/handle_class.cpp

@@ -459,10 +459,10 @@ static auto CheckBaseType(Context& context, Parse::NodeId node_id,
     return BaseInfo::Error;
   }
 
-  auto* base_class_info = TryGetAsClass(context, base_type_id);
+  auto class_type = context.types().TryGetAs<SemIR::ClassType>(base_type_id);
 
   // The base must not be a final class.
-  if (!base_class_info) {
+  if (!class_type) {
     // For now, we treat all types that aren't introduced by a `class`
     // declaration as being final classes.
     // TODO: Once we have a better idea of which types are considered to be
@@ -470,14 +470,17 @@ static auto CheckBaseType(Context& context, Parse::NodeId node_id,
     DiagnoseBaseIsFinal(context, node_id, base_type_inst_id);
     return BaseInfo::Error;
   }
-  if (base_class_info->inheritance_kind == SemIR::Class::Final) {
+
+  const auto& base_class_info = context.classes().Get(class_type->class_id);
+
+  if (base_class_info.inheritance_kind == SemIR::Class::Final) {
     DiagnoseBaseIsFinal(context, node_id, base_type_inst_id);
   }
 
-  CARBON_CHECK(base_class_info->scope_id.has_value(),
+  CARBON_CHECK(base_class_info.scope_id.has_value(),
                "Complete class should have a scope");
   return {.type_id = base_type_id,
-          .scope_id = base_class_info->scope_id,
+          .scope_id = base_class_info.scope_id,
           .inst_id = base_type_inst_id};
 }
 

+ 748 - 34
toolchain/check/testdata/class/virtual_modifiers.carbon

@@ -230,6 +230,22 @@ class Derived {
   impl fn F[self: Self]() -> T2;
 }
 
+// --- fail_generic_virtual_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_generic_virtual_decl.carbon:[[@LINE+3]]:1: error: use of undefined generic function [MissingGenericFunctionDefinition]
+// CHECK:STDERR: base class Base(T:! type) {
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~
+base class Base(T:! type) {
+  // CHECK:STDERR: fail_generic_virtual_decl.carbon:[[@LINE+4]]:3: note: generic function declared here [MissingGenericFunctionDefinitionHere]
+  // CHECK:STDERR:   virtual fn F[self: Self]();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  virtual fn F[self: Self]();
+}
+
+
 // --- fail_todo_impl_generic_base.carbon
 
 library "[[@TEST_NAME]]";
@@ -238,19 +254,19 @@ class T1 {
 }
 
 base class Base(T:! type) {
-  virtual fn F[self: Self](t: T);
+  virtual fn F[self: Self](t: T) { }
 }
 
 class Derived {
   extend base: Base(T1);
   // CHECK:STDERR: fail_todo_impl_generic_base.carbon:[[@LINE+7]]:25: error: type `<pattern for T1>` of parameter 1 in redeclaration differs from previous parameter type `<pattern for T>` [RedeclParamDiffersType]
-  // CHECK:STDERR:   impl fn F[self: Self](t: T1);
+  // CHECK:STDERR:   impl fn F[self: Self](t: T1) { }
   // CHECK:STDERR:                         ^~~~~
   // CHECK:STDERR: fail_todo_impl_generic_base.carbon:[[@LINE-8]]:28: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
-  // CHECK:STDERR:   virtual fn F[self: Self](t: T);
+  // CHECK:STDERR:   virtual fn F[self: Self](t: T) { }
   // CHECK:STDERR:                            ^~~~
   // CHECK:STDERR:
-  impl fn F[self: Self](t: T1);
+  impl fn F[self: Self](t: T1) { }
 }
 
 // --- fail_virtual_without_self.carbon
@@ -328,7 +344,7 @@ base class T1(T:! type) {
 library "[[@TEST_NAME]]";
 
 base class T1(T:! type) {
-  virtual fn F[self: Self]();
+  virtual fn F[self: Self]() { }
 }
 
 // --- with_dependent_arg.carbon
@@ -336,7 +352,7 @@ base class T1(T:! type) {
 library "[[@TEST_NAME]]";
 
 base class T1(T:! type) {
-  virtual fn F[self: Self](t: T);
+  virtual fn F[self: Self](t: T) { }
 }
 
 // --- vtable_import_unneeded.carbon
@@ -347,6 +363,37 @@ import Modifiers;
 
 fn F(b: Modifiers.Base);
 
+// --- generic_derived_from_nongeneric.carbon
+
+library "[[@TEST_NAME]]";
+
+base class NonGenericBase {
+  virtual fn F1[self: Self]() { }
+  virtual fn F2[self: Self]() { }
+}
+
+base class GenericDerived(T:! type) {
+  extend base: NonGenericBase;
+  impl fn F2[self: Self]() { }
+  virtual fn F3[self: Self]() { }
+}
+
+// --- nongeneric_derived_from_generic.carbon
+
+library "[[@TEST_NAME]]";
+
+base class GenericBase(T:! type) {
+  virtual fn F1[self: Self]() { }
+  virtual fn F2[self: Self]() { }
+}
+
+class T1;
+
+base class NonGenericDerived {
+  extend base: GenericBase(T1);
+  impl fn F2[self: Self]() { }
+  virtual fn F3[self: Self]() { }
+}
 
 // CHECK:STDOUT: --- modifiers.carbon
 // CHECK:STDOUT:
@@ -1566,7 +1613,7 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @AbstractIntermediate.vtable {
-// CHECK:STDOUT:   @AbstractBase.%F.decl
+// CHECK:STDOUT:   constants.%F.6e9
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @Derived.vtable {
@@ -1689,7 +1736,7 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @VirtualIntermediate.vtable {
-// CHECK:STDOUT:   @VirtualBase.%F.decl
+// CHECK:STDOUT:   constants.%F.3e7
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @Derived.vtable {
@@ -1995,6 +2042,100 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl fn @F.2(%self.param: %Derived) -> %return.param: %T2;
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_generic_virtual_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %Base.type: type = generic_class_type @Base [concrete]
+// CHECK:STDOUT:   %Base.generic: %Base.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Base: type = class_type @Base, @Base(%T) [symbolic]
+// CHECK:STDOUT:   %pattern_type.9f7: type = pattern_type %Base [symbolic]
+// CHECK:STDOUT:   %F.type: type = fn_type @F, @Base(%T) [symbolic]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [symbolic]
+// CHECK:STDOUT:   %ptr: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F, @F(%T) [symbolic]
+// CHECK:STDOUT:   %Base.vtable_ptr: ref %ptr = vtable_ptr @Base.vtable, @Base(%T) [symbolic]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .Base = %Base.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Base.decl: %Base.type = class_decl @Base [concrete = constants.%Base.generic] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc7_17.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc7_17.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @Base(%T.loc7_17.1: type) {
+// CHECK:STDOUT:   %T.loc7_17.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc7_17.2 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.type: type = fn_type @F, @Base(%T.loc7_17.2) [symbolic = %F.type (constants.%F.type)]
+// CHECK:STDOUT:   %F: @Base.%F.type (%F.type) = struct_value () [symbolic = %F (constants.%F)]
+// CHECK:STDOUT:   %F.specific_fn.loc13_1.2: <specific function> = specific_function %F, @F(%T.loc7_17.2) [symbolic = %F.specific_fn.loc13_1.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:   %vtable_ptr.loc13_1.2: ref %ptr = vtable_ptr @Base.vtable, @Base(%T.loc7_17.2) [symbolic = %vtable_ptr.loc13_1.2 (constants.%Base.vtable_ptr)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %F.decl: @Base.%F.type (%F.type) = fn_decl @F [symbolic = @Base.%F (constants.%F)] {
+// CHECK:STDOUT:       %self.patt: @F.%pattern_type (%pattern_type.9f7) = binding_pattern self [concrete]
+// CHECK:STDOUT:       %self.param_patt: @F.%pattern_type (%pattern_type.9f7) = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %self.param: @F.%Base (%Base) = value_param call_param0
+// CHECK:STDOUT:       %.loc12_22.1: type = splice_block %Self.ref [symbolic = %Base (constants.%Base)] {
+// CHECK:STDOUT:         %.loc12_22.2: type = specific_constant constants.%Base, @Base(constants.%T) [symbolic = %Base (constants.%Base)]
+// CHECK:STDOUT:         %Self.ref: type = name_ref Self, %.loc12_22.2 [symbolic = %Base (constants.%Base)]
+// CHECK:STDOUT:       }
+// CHECK:STDOUT:       %self: @F.%Base (%Base) = bind_name self, %self.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %F.specific_fn.loc13_1.1: <specific function> = specific_function %F.decl, @F(constants.%T) [symbolic = %F.specific_fn.loc13_1.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:     %vtable_ptr.loc13_1.1: ref %ptr = vtable_ptr @Base.vtable, @Base(constants.%T) [symbolic = %vtable_ptr.loc13_1.2 (constants.%Base.vtable_ptr)]
+// CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete = constants.%struct_type.vptr]
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:     vtable_ptr = %vtable_ptr.loc13_1.1
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%Base
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: vtable @Base.vtable {
+// CHECK:STDOUT:   @Base.%F.specific_fn.loc13_1.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic virtual fn @F(@Base.%T.loc7_17.1: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %Base: type = class_type @Base, @Base(%T) [symbolic = %Base (constants.%Base)]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %Base [symbolic = %pattern_type (constants.%pattern_type.9f7)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F.%Base (%Base));
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(constants.%T) {
+// CHECK:STDOUT:   %T.loc7_17.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %Base => constants.%Base
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.9f7
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_impl_generic_base.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -2011,21 +2152,27 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %F.type.f17: type = fn_type @F.1, @Base(%T) [symbolic]
 // CHECK:STDOUT:   %F.e26: %F.type.f17 = struct_value () [symbolic]
 // CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
-// CHECK:STDOUT:   %Base.vtable_ptr: ref %ptr.454 = vtable_ptr @Base.vtable [concrete]
+// CHECK:STDOUT:   %F.specific_fn.892: <specific function> = specific_function %F.e26, @F.1(%T) [symbolic]
+// CHECK:STDOUT:   %Base.vtable_ptr.573: ref %ptr.454 = vtable_ptr @Base.vtable, @Base(%T) [symbolic]
 // CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
 // CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %require_complete.97d: <witness> = require_complete_type %Base.370 [symbolic]
+// CHECK:STDOUT:   %require_complete.4ae: <witness> = require_complete_type %T [symbolic]
 // CHECK:STDOUT:   %Derived: type = class_type @Derived [concrete]
 // CHECK:STDOUT:   %Base.ea5: type = class_type @Base, @Base(%T1) [concrete]
 // CHECK:STDOUT:   %F.type.d82: type = fn_type @F.1, @Base(%T1) [concrete]
 // CHECK:STDOUT:   %F.d25: %F.type.d82 = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.3bf: type = pattern_type %Base.ea5 [concrete]
+// CHECK:STDOUT:   %pattern_type.28b: type = pattern_type %T1 [concrete]
+// CHECK:STDOUT:   %F.specific_fn.210: <specific function> = specific_function %F.d25, @F.1(%T1) [concrete]
+// CHECK:STDOUT:   %Base.vtable_ptr.bfe: ref %ptr.454 = vtable_ptr @Base.vtable, @Base(%T1) [concrete]
 // CHECK:STDOUT:   %Derived.elem: type = unbound_element_type %Derived, %Base.ea5 [concrete]
 // CHECK:STDOUT:   %pattern_type.fb9: type = pattern_type %Derived [concrete]
-// CHECK:STDOUT:   %pattern_type.28b: type = pattern_type %T1 [concrete]
 // CHECK:STDOUT:   %F.type.5da: type = fn_type @F.2 [concrete]
 // CHECK:STDOUT:   %F.fa3: %F.type.5da = struct_value () [concrete]
 // CHECK:STDOUT:   %Derived.vtable_ptr: ref %ptr.454 = vtable_ptr @Derived.vtable [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %Base.ea5} [concrete]
-// CHECK:STDOUT:   %complete_type.65a: <witness> = complete_type_witness %struct_type.base [concrete]
+// CHECK:STDOUT:   %struct_type.base.fda: type = struct_type {.base: %Base.ea5} [concrete]
+// CHECK:STDOUT:   %complete_type.65a: <witness> = complete_type_witness %struct_type.base.fda [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -2067,6 +2214,8 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: !definition:
 // CHECK:STDOUT:   %F.type: type = fn_type @F.1, @Base(%T.loc7_17.2) [symbolic = %F.type (constants.%F.type.f17)]
 // CHECK:STDOUT:   %F: @Base.%F.type (%F.type.f17) = struct_value () [symbolic = %F (constants.%F.e26)]
+// CHECK:STDOUT:   %F.specific_fn.loc9_1.2: <specific function> = specific_function %F, @F.1(%T.loc7_17.2) [symbolic = %F.specific_fn.loc9_1.2 (constants.%F.specific_fn.892)]
+// CHECK:STDOUT:   %vtable_ptr.loc9_1.2: ref %ptr.454 = vtable_ptr @Base.vtable, @Base(%T.loc7_17.2) [symbolic = %vtable_ptr.loc9_1.2 (constants.%Base.vtable_ptr.573)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   class {
 // CHECK:STDOUT:     %F.decl: @Base.%F.type (%F.type.f17) = fn_decl @F.1 [symbolic = @Base.%F (constants.%F.e26)] {
@@ -2085,11 +2234,12 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:       %T.ref: type = name_ref T, @Base.%T.loc7_17.1 [symbolic = %T (constants.%T)]
 // CHECK:STDOUT:       %t: @F.1.%T (%T) = bind_name t, %t.param
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %vtable_ptr: ref %ptr.454 = vtable_ptr @Base.vtable [concrete = constants.%Base.vtable_ptr]
+// CHECK:STDOUT:     %F.specific_fn.loc9_1.1: <specific function> = specific_function %F.decl, @F.1(constants.%T) [symbolic = %F.specific_fn.loc9_1.2 (constants.%F.specific_fn.892)]
+// CHECK:STDOUT:     %vtable_ptr.loc9_1.1: ref %ptr.454 = vtable_ptr @Base.vtable, @Base(constants.%T) [symbolic = %vtable_ptr.loc9_1.2 (constants.%Base.vtable_ptr.573)]
 // CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete = constants.%struct_type.vptr]
 // CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:     complete_type_witness = %complete_type
-// CHECK:STDOUT:     vtable_ptr = %vtable_ptr
+// CHECK:STDOUT:     vtable_ptr = %vtable_ptr.loc9_1.1
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
 // CHECK:STDOUT:     .Self = constants.%Base.370
@@ -2118,7 +2268,7 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:     %t: %T1 = bind_name t, %t.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %vtable_ptr: ref %ptr.454 = vtable_ptr @Derived.vtable [concrete = constants.%Derived.vtable_ptr]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %Base.ea5} [concrete = constants.%struct_type.base]
+// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %Base.ea5} [concrete = constants.%struct_type.base.fda]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.65a]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   vtable_ptr = %vtable_ptr
@@ -2133,7 +2283,7 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @Base.vtable {
-// CHECK:STDOUT:   @Base.%F.decl
+// CHECK:STDOUT:   @Base.%F.specific_fn.loc9_1.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @Derived.vtable {
@@ -2146,13 +2296,29 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %pattern_type.loc8_16: type = pattern_type %Base [symbolic = %pattern_type.loc8_16 (constants.%pattern_type.9f7)]
 // CHECK:STDOUT:   %pattern_type.loc8_28: type = pattern_type %T [symbolic = %pattern_type.loc8_28 (constants.%pattern_type.7dc)]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   virtual fn(%self.param: @F.1.%Base (%Base.370), %t.param: @F.1.%T (%T));
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete.loc8_20: <witness> = require_complete_type %Base [symbolic = %require_complete.loc8_20 (constants.%require_complete.97d)]
+// CHECK:STDOUT:   %require_complete.loc8_29: <witness> = require_complete_type %T [symbolic = %require_complete.loc8_29 (constants.%require_complete.4ae)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F.1.%Base (%Base.370), %t.param: @F.1.%T (%T)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2(%self.param: %Derived, %t.param: %T1);
+// CHECK:STDOUT: impl fn @F.2(%self.param: %Derived, %t.param: %T1) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Base(constants.%T) {
 // CHECK:STDOUT:   %T.loc7_17.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.type => constants.%F.type.f17
+// CHECK:STDOUT:   %F => constants.%F.e26
+// CHECK:STDOUT:   %F.specific_fn.loc9_1.2 => constants.%F.specific_fn.892
+// CHECK:STDOUT:   %vtable_ptr.loc9_1.2 => constants.%Base.vtable_ptr.573
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @F.1(constants.%T) {
@@ -2160,6 +2326,10 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %Base => constants.%Base.370
 // CHECK:STDOUT:   %pattern_type.loc8_16 => constants.%pattern_type.9f7
 // CHECK:STDOUT:   %pattern_type.loc8_28 => constants.%pattern_type.7dc
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete.loc8_20 => constants.%require_complete.97d
+// CHECK:STDOUT:   %require_complete.loc8_29 => constants.%require_complete.4ae
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Base(constants.%T1) {
@@ -2168,6 +2338,19 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: !definition:
 // CHECK:STDOUT:   %F.type => constants.%F.type.d82
 // CHECK:STDOUT:   %F => constants.%F.d25
+// CHECK:STDOUT:   %F.specific_fn.loc9_1.2 => constants.%F.specific_fn.210
+// CHECK:STDOUT:   %vtable_ptr.loc9_1.2 => constants.%Base.vtable_ptr.bfe
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T1) {
+// CHECK:STDOUT:   %T => constants.%T1
+// CHECK:STDOUT:   %Base => constants.%Base.ea5
+// CHECK:STDOUT:   %pattern_type.loc8_16 => constants.%pattern_type.3bf
+// CHECK:STDOUT:   %pattern_type.loc8_28 => constants.%pattern_type.28b
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete.loc8_20 => constants.%complete_type.513
+// CHECK:STDOUT:   %require_complete.loc8_29 => constants.%complete_type.357
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_virtual_without_self.carbon
@@ -2504,10 +2687,12 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %pattern_type.48e: type = pattern_type %T1 [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F, @T1(%T) [symbolic]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [symbolic]
-// CHECK:STDOUT:   %ptr: type = ptr_type <vtable> [concrete]
-// CHECK:STDOUT:   %T1.vtable_ptr: ref %ptr = vtable_ptr @T1.vtable [concrete]
-// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F, @F(%T) [symbolic]
+// CHECK:STDOUT:   %T1.vtable_ptr: ref %ptr.454 = vtable_ptr @T1.vtable, @T1(%T) [symbolic]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %T1 [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -2536,6 +2721,8 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: !definition:
 // CHECK:STDOUT:   %F.type: type = fn_type @F, @T1(%T.loc4_15.2) [symbolic = %F.type (constants.%F.type)]
 // CHECK:STDOUT:   %F: @T1.%F.type (%F.type) = struct_value () [symbolic = %F (constants.%F)]
+// CHECK:STDOUT:   %F.specific_fn.loc6_1.2: <specific function> = specific_function %F, @F(%T.loc4_15.2) [symbolic = %F.specific_fn.loc6_1.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:   %vtable_ptr.loc6_1.2: ref %ptr.454 = vtable_ptr @T1.vtable, @T1(%T.loc4_15.2) [symbolic = %vtable_ptr.loc6_1.2 (constants.%T1.vtable_ptr)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   class {
 // CHECK:STDOUT:     %F.decl: @T1.%F.type (%F.type) = fn_decl @F [symbolic = @T1.%F (constants.%F)] {
@@ -2549,11 +2736,12 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:       }
 // CHECK:STDOUT:       %self: @F.%T1 (%T1) = bind_name self, %self.param
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %vtable_ptr: ref %ptr = vtable_ptr @T1.vtable [concrete = constants.%T1.vtable_ptr]
-// CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete = constants.%struct_type.vptr]
+// CHECK:STDOUT:     %F.specific_fn.loc6_1.1: <specific function> = specific_function %F.decl, @F(constants.%T) [symbolic = %F.specific_fn.loc6_1.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:     %vtable_ptr.loc6_1.1: ref %ptr.454 = vtable_ptr @T1.vtable, @T1(constants.%T) [symbolic = %vtable_ptr.loc6_1.2 (constants.%T1.vtable_ptr)]
+// CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete = constants.%struct_type.vptr]
 // CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:     complete_type_witness = %complete_type
-// CHECK:STDOUT:     vtable_ptr = %vtable_ptr
+// CHECK:STDOUT:     vtable_ptr = %vtable_ptr.loc6_1.1
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
 // CHECK:STDOUT:     .Self = constants.%T1
@@ -2562,7 +2750,7 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @T1.vtable {
-// CHECK:STDOUT:   @T1.%F.decl
+// CHECK:STDOUT:   @T1.%F.specific_fn.loc6_1.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic virtual fn @F(@T1.%T.loc4_15.1: type) {
@@ -2570,17 +2758,32 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %T1: type = class_type @T1, @T1(%T) [symbolic = %T1 (constants.%T1)]
 // CHECK:STDOUT:   %pattern_type: type = pattern_type %T1 [symbolic = %pattern_type (constants.%pattern_type.48e)]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   virtual fn(%self.param: @F.%T1 (%T1));
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %T1 [symbolic = %require_complete (constants.%require_complete)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F.%T1 (%T1)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @T1(constants.%T) {
 // CHECK:STDOUT:   %T.loc4_15.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.type => constants.%F.type
+// CHECK:STDOUT:   %F => constants.%F
+// CHECK:STDOUT:   %F.specific_fn.loc6_1.2 => constants.%F.specific_fn
+// CHECK:STDOUT:   %vtable_ptr.loc6_1.2 => constants.%T1.vtable_ptr
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @F(constants.%T) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT:   %T1 => constants.%T1
 // CHECK:STDOUT:   %pattern_type => constants.%pattern_type.48e
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%require_complete
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- with_dependent_arg.carbon
@@ -2595,10 +2798,13 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %pattern_type.7dc: type = pattern_type %T [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F, @T1(%T) [symbolic]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [symbolic]
-// CHECK:STDOUT:   %ptr: type = ptr_type <vtable> [concrete]
-// CHECK:STDOUT:   %T1.vtable_ptr: ref %ptr = vtable_ptr @T1.vtable [concrete]
-// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F, @F(%T) [symbolic]
+// CHECK:STDOUT:   %T1.vtable_ptr: ref %ptr.454 = vtable_ptr @T1.vtable, @T1(%T) [symbolic]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %require_complete.86d: <witness> = require_complete_type %T1 [symbolic]
+// CHECK:STDOUT:   %require_complete.4ae: <witness> = require_complete_type %T [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -2627,6 +2833,8 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: !definition:
 // CHECK:STDOUT:   %F.type: type = fn_type @F, @T1(%T.loc4_15.2) [symbolic = %F.type (constants.%F.type)]
 // CHECK:STDOUT:   %F: @T1.%F.type (%F.type) = struct_value () [symbolic = %F (constants.%F)]
+// CHECK:STDOUT:   %F.specific_fn.loc6_1.2: <specific function> = specific_function %F, @F(%T.loc4_15.2) [symbolic = %F.specific_fn.loc6_1.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:   %vtable_ptr.loc6_1.2: ref %ptr.454 = vtable_ptr @T1.vtable, @T1(%T.loc4_15.2) [symbolic = %vtable_ptr.loc6_1.2 (constants.%T1.vtable_ptr)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   class {
 // CHECK:STDOUT:     %F.decl: @T1.%F.type (%F.type) = fn_decl @F [symbolic = @T1.%F (constants.%F)] {
@@ -2645,11 +2853,12 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:       %T.ref: type = name_ref T, @T1.%T.loc4_15.1 [symbolic = %T (constants.%T)]
 // CHECK:STDOUT:       %t: @F.%T (%T) = bind_name t, %t.param
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %vtable_ptr: ref %ptr = vtable_ptr @T1.vtable [concrete = constants.%T1.vtable_ptr]
-// CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete = constants.%struct_type.vptr]
+// CHECK:STDOUT:     %F.specific_fn.loc6_1.1: <specific function> = specific_function %F.decl, @F(constants.%T) [symbolic = %F.specific_fn.loc6_1.2 (constants.%F.specific_fn)]
+// CHECK:STDOUT:     %vtable_ptr.loc6_1.1: ref %ptr.454 = vtable_ptr @T1.vtable, @T1(constants.%T) [symbolic = %vtable_ptr.loc6_1.2 (constants.%T1.vtable_ptr)]
+// CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete = constants.%struct_type.vptr]
 // CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:     complete_type_witness = %complete_type
-// CHECK:STDOUT:     vtable_ptr = %vtable_ptr
+// CHECK:STDOUT:     vtable_ptr = %vtable_ptr.loc6_1.1
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
 // CHECK:STDOUT:     .Self = constants.%T1
@@ -2659,7 +2868,7 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: vtable @T1.vtable {
-// CHECK:STDOUT:   @T1.%F.decl
+// CHECK:STDOUT:   @T1.%F.specific_fn.loc6_1.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic virtual fn @F(@T1.%T.loc4_15.1: type) {
@@ -2668,11 +2877,24 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %pattern_type.loc5_16: type = pattern_type %T1 [symbolic = %pattern_type.loc5_16 (constants.%pattern_type.48e)]
 // CHECK:STDOUT:   %pattern_type.loc5_28: type = pattern_type %T [symbolic = %pattern_type.loc5_28 (constants.%pattern_type.7dc)]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   virtual fn(%self.param: @F.%T1 (%T1), %t.param: @F.%T (%T));
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete.loc5_20: <witness> = require_complete_type %T1 [symbolic = %require_complete.loc5_20 (constants.%require_complete.86d)]
+// CHECK:STDOUT:   %require_complete.loc5_29: <witness> = require_complete_type %T [symbolic = %require_complete.loc5_29 (constants.%require_complete.4ae)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F.%T1 (%T1), %t.param: @F.%T (%T)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @T1(constants.%T) {
 // CHECK:STDOUT:   %T.loc4_15.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F.type => constants.%F.type
+// CHECK:STDOUT:   %F => constants.%F
+// CHECK:STDOUT:   %F.specific_fn.loc6_1.2 => constants.%F.specific_fn
+// CHECK:STDOUT:   %vtable_ptr.loc6_1.2 => constants.%T1.vtable_ptr
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @F(constants.%T) {
@@ -2680,6 +2902,10 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:   %T1 => constants.%T1
 // CHECK:STDOUT:   %pattern_type.loc5_16 => constants.%pattern_type.48e
 // CHECK:STDOUT:   %pattern_type.loc5_28 => constants.%pattern_type.7dc
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete.loc5_20 => constants.%require_complete.86d
+// CHECK:STDOUT:   %require_complete.loc5_29 => constants.%require_complete.4ae
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- vtable_import_unneeded.carbon
@@ -2742,3 +2968,491 @@ fn F(b: Modifiers.Base);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%b.param: %Base);
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- generic_derived_from_nongeneric.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %NonGenericBase: type = class_type @NonGenericBase [concrete]
+// CHECK:STDOUT:   %pattern_type.0b8: type = pattern_type %NonGenericBase [concrete]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [concrete]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [concrete]
+// CHECK:STDOUT:   %F2.type.809: type = fn_type @F2.1 [concrete]
+// CHECK:STDOUT:   %F2.053: %F2.type.809 = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %NonGenericBase.vtable_ptr: ref %ptr.454 = vtable_ptr @NonGenericBase.vtable [concrete]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
+// CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %GenericDerived.type: type = generic_class_type @GenericDerived [concrete]
+// CHECK:STDOUT:   %GenericDerived.generic: %GenericDerived.type = struct_value () [concrete]
+// CHECK:STDOUT:   %GenericDerived: type = class_type @GenericDerived, @GenericDerived(%T) [symbolic]
+// CHECK:STDOUT:   %GenericDerived.elem: type = unbound_element_type %GenericDerived, %NonGenericBase [symbolic]
+// CHECK:STDOUT:   %pattern_type.061: type = pattern_type %GenericDerived [symbolic]
+// CHECK:STDOUT:   %F2.type.e6e: type = fn_type @F2.2, @GenericDerived(%T) [symbolic]
+// CHECK:STDOUT:   %F2.5da: %F2.type.e6e = struct_value () [symbolic]
+// CHECK:STDOUT:   %F3.type: type = fn_type @F3, @GenericDerived(%T) [symbolic]
+// CHECK:STDOUT:   %F3: %F3.type = struct_value () [symbolic]
+// CHECK:STDOUT:   %F2.specific_fn: <specific function> = specific_function %F2.5da, @F2.2(%T) [symbolic]
+// CHECK:STDOUT:   %F3.specific_fn: <specific function> = specific_function %F3, @F3(%T) [symbolic]
+// CHECK:STDOUT:   %GenericDerived.vtable_ptr: ref %ptr.454 = vtable_ptr @GenericDerived.vtable, @GenericDerived(%T) [symbolic]
+// CHECK:STDOUT:   %struct_type.base.432: type = struct_type {.base: %NonGenericBase} [concrete]
+// CHECK:STDOUT:   %complete_type.099: <witness> = complete_type_witness %struct_type.base.432 [concrete]
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %GenericDerived [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .NonGenericBase = %NonGenericBase.decl
+// CHECK:STDOUT:     .GenericDerived = %GenericDerived.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %NonGenericBase.decl: type = class_decl @NonGenericBase [concrete = constants.%NonGenericBase] {} {}
+// CHECK:STDOUT:   %GenericDerived.decl: %GenericDerived.type = class_decl @GenericDerived [concrete = constants.%GenericDerived.generic] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc9_27.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_27.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NonGenericBase {
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [concrete = constants.%F1] {
+// CHECK:STDOUT:     %self.patt: %pattern_type.0b8 = binding_pattern self [concrete]
+// CHECK:STDOUT:     %self.param_patt: %pattern_type.0b8 = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %NonGenericBase = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%NonGenericBase [concrete = constants.%NonGenericBase]
+// CHECK:STDOUT:     %self: %NonGenericBase = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F2.decl: %F2.type.809 = fn_decl @F2.1 [concrete = constants.%F2.053] {
+// CHECK:STDOUT:     %self.patt: %pattern_type.0b8 = binding_pattern self [concrete]
+// CHECK:STDOUT:     %self.param_patt: %pattern_type.0b8 = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %NonGenericBase = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%NonGenericBase [concrete = constants.%NonGenericBase]
+// CHECK:STDOUT:     %self: %NonGenericBase = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %vtable_ptr: ref %ptr.454 = vtable_ptr @NonGenericBase.vtable [concrete = constants.%NonGenericBase.vtable_ptr]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete = constants.%struct_type.vptr]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:   vtable_ptr = %vtable_ptr
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%NonGenericBase
+// CHECK:STDOUT:   .F1 = %F1.decl
+// CHECK:STDOUT:   .F2 = %F2.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @GenericDerived(%T.loc9_27.1: type) {
+// CHECK:STDOUT:   %T.loc9_27.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_27.2 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %GenericDerived: type = class_type @GenericDerived, @GenericDerived(%T.loc9_27.2) [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:   %GenericDerived.elem: type = unbound_element_type %GenericDerived, constants.%NonGenericBase [symbolic = %GenericDerived.elem (constants.%GenericDerived.elem)]
+// CHECK:STDOUT:   %F2.type: type = fn_type @F2.2, @GenericDerived(%T.loc9_27.2) [symbolic = %F2.type (constants.%F2.type.e6e)]
+// CHECK:STDOUT:   %F2: @GenericDerived.%F2.type (%F2.type.e6e) = struct_value () [symbolic = %F2 (constants.%F2.5da)]
+// CHECK:STDOUT:   %F3.type: type = fn_type @F3, @GenericDerived(%T.loc9_27.2) [symbolic = %F3.type (constants.%F3.type)]
+// CHECK:STDOUT:   %F3: @GenericDerived.%F3.type (%F3.type) = struct_value () [symbolic = %F3 (constants.%F3)]
+// CHECK:STDOUT:   %F2.specific_fn.loc13_1.2: <specific function> = specific_function %F2, @F2.2(%T.loc9_27.2) [symbolic = %F2.specific_fn.loc13_1.2 (constants.%F2.specific_fn)]
+// CHECK:STDOUT:   %F3.specific_fn.loc13_1.2: <specific function> = specific_function %F3, @F3(%T.loc9_27.2) [symbolic = %F3.specific_fn.loc13_1.2 (constants.%F3.specific_fn)]
+// CHECK:STDOUT:   %vtable_ptr.loc13_1.2: ref %ptr.454 = vtable_ptr @GenericDerived.vtable, @GenericDerived(%T.loc9_27.2) [symbolic = %vtable_ptr.loc13_1.2 (constants.%GenericDerived.vtable_ptr)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %NonGenericBase.ref: type = name_ref NonGenericBase, file.%NonGenericBase.decl [concrete = constants.%NonGenericBase]
+// CHECK:STDOUT:     %.loc10: @GenericDerived.%GenericDerived.elem (%GenericDerived.elem) = base_decl %NonGenericBase.ref, element0 [concrete]
+// CHECK:STDOUT:     %F2.decl: @GenericDerived.%F2.type (%F2.type.e6e) = fn_decl @F2.2 [symbolic = @GenericDerived.%F2 (constants.%F2.5da)] {
+// CHECK:STDOUT:       %self.patt: @F2.2.%pattern_type (%pattern_type.061) = binding_pattern self [concrete]
+// CHECK:STDOUT:       %self.param_patt: @F2.2.%pattern_type (%pattern_type.061) = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %self.param: @F2.2.%GenericDerived (%GenericDerived) = value_param call_param0
+// CHECK:STDOUT:       %.loc11_20.1: type = splice_block %Self.ref [symbolic = %GenericDerived (constants.%GenericDerived)] {
+// CHECK:STDOUT:         %.loc11_20.2: type = specific_constant constants.%GenericDerived, @GenericDerived(constants.%T) [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:         %Self.ref: type = name_ref Self, %.loc11_20.2 [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:       }
+// CHECK:STDOUT:       %self: @F2.2.%GenericDerived (%GenericDerived) = bind_name self, %self.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %F3.decl: @GenericDerived.%F3.type (%F3.type) = fn_decl @F3 [symbolic = @GenericDerived.%F3 (constants.%F3)] {
+// CHECK:STDOUT:       %self.patt: @F3.%pattern_type (%pattern_type.061) = binding_pattern self [concrete]
+// CHECK:STDOUT:       %self.param_patt: @F3.%pattern_type (%pattern_type.061) = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %self.param: @F3.%GenericDerived (%GenericDerived) = value_param call_param0
+// CHECK:STDOUT:       %.loc12_23.1: type = splice_block %Self.ref [symbolic = %GenericDerived (constants.%GenericDerived)] {
+// CHECK:STDOUT:         %.loc12_23.2: type = specific_constant constants.%GenericDerived, @GenericDerived(constants.%T) [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:         %Self.ref: type = name_ref Self, %.loc12_23.2 [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:       }
+// CHECK:STDOUT:       %self: @F3.%GenericDerived (%GenericDerived) = bind_name self, %self.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %F2.specific_fn.loc13_1.1: <specific function> = specific_function %F2.decl, @F2.2(constants.%T) [symbolic = %F2.specific_fn.loc13_1.2 (constants.%F2.specific_fn)]
+// CHECK:STDOUT:     %F3.specific_fn.loc13_1.1: <specific function> = specific_function %F3.decl, @F3(constants.%T) [symbolic = %F3.specific_fn.loc13_1.2 (constants.%F3.specific_fn)]
+// CHECK:STDOUT:     %vtable_ptr.loc13_1.1: ref %ptr.454 = vtable_ptr @GenericDerived.vtable, @GenericDerived(constants.%T) [symbolic = %vtable_ptr.loc13_1.2 (constants.%GenericDerived.vtable_ptr)]
+// CHECK:STDOUT:     %struct_type.base: type = struct_type {.base: %NonGenericBase} [concrete = constants.%struct_type.base.432]
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.099]
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:     vtable_ptr = %vtable_ptr.loc13_1.1
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%GenericDerived
+// CHECK:STDOUT:     .NonGenericBase = <poisoned>
+// CHECK:STDOUT:     .base = %.loc10
+// CHECK:STDOUT:     .F2 = %F2.decl
+// CHECK:STDOUT:     .F3 = %F3.decl
+// CHECK:STDOUT:     extend %NonGenericBase.ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: vtable @NonGenericBase.vtable {
+// CHECK:STDOUT:   @NonGenericBase.%F1.decl
+// CHECK:STDOUT:   @NonGenericBase.%F2.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: vtable @GenericDerived.vtable {
+// CHECK:STDOUT:   constants.%F1
+// CHECK:STDOUT:   @GenericDerived.%F2.specific_fn.loc13_1.1
+// CHECK:STDOUT:   @GenericDerived.%F3.specific_fn.loc13_1.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: virtual fn @F1(%self.param: %NonGenericBase) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: virtual fn @F2.1(%self.param: %NonGenericBase) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl fn @F2.2(@GenericDerived.%T.loc9_27.1: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %GenericDerived: type = class_type @GenericDerived, @GenericDerived(%T) [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %GenericDerived [symbolic = %pattern_type (constants.%pattern_type.061)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %GenericDerived [symbolic = %require_complete (constants.%require_complete)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl fn(%self.param: @F2.2.%GenericDerived (%GenericDerived)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic virtual fn @F3(@GenericDerived.%T.loc9_27.1: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %GenericDerived: type = class_type @GenericDerived, @GenericDerived(%T) [symbolic = %GenericDerived (constants.%GenericDerived)]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %GenericDerived [symbolic = %pattern_type (constants.%pattern_type.061)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %GenericDerived [symbolic = %require_complete (constants.%require_complete)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F3.%GenericDerived (%GenericDerived)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GenericDerived(constants.%T) {
+// CHECK:STDOUT:   %T.loc9_27.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %GenericDerived => constants.%GenericDerived
+// CHECK:STDOUT:   %GenericDerived.elem => constants.%GenericDerived.elem
+// CHECK:STDOUT:   %F2.type => constants.%F2.type.e6e
+// CHECK:STDOUT:   %F2 => constants.%F2.5da
+// CHECK:STDOUT:   %F3.type => constants.%F3.type
+// CHECK:STDOUT:   %F3 => constants.%F3
+// CHECK:STDOUT:   %F2.specific_fn.loc13_1.2 => constants.%F2.specific_fn
+// CHECK:STDOUT:   %F3.specific_fn.loc13_1.2 => constants.%F3.specific_fn
+// CHECK:STDOUT:   %vtable_ptr.loc13_1.2 => constants.%GenericDerived.vtable_ptr
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F2.2(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %GenericDerived => constants.%GenericDerived
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.061
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%require_complete
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F3(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %GenericDerived => constants.%GenericDerived
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.061
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%require_complete
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- nongeneric_derived_from_generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %GenericBase.type: type = generic_class_type @GenericBase [concrete]
+// CHECK:STDOUT:   %GenericBase.generic: %GenericBase.type = struct_value () [concrete]
+// CHECK:STDOUT:   %GenericBase.018: type = class_type @GenericBase, @GenericBase(%T) [symbolic]
+// CHECK:STDOUT:   %pattern_type.34e: type = pattern_type %GenericBase.018 [symbolic]
+// CHECK:STDOUT:   %F1.type.2af: type = fn_type @F1, @GenericBase(%T) [symbolic]
+// CHECK:STDOUT:   %F1.80a: %F1.type.2af = struct_value () [symbolic]
+// CHECK:STDOUT:   %F2.type.2ca: type = fn_type @F2.1, @GenericBase(%T) [symbolic]
+// CHECK:STDOUT:   %F2.414: %F2.type.2ca = struct_value () [symbolic]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %F1.specific_fn.aea: <specific function> = specific_function %F1.80a, @F1(%T) [symbolic]
+// CHECK:STDOUT:   %F2.specific_fn.758: <specific function> = specific_function %F2.414, @F2.1(%T) [symbolic]
+// CHECK:STDOUT:   %GenericBase.vtable_ptr.e41: ref %ptr.454 = vtable_ptr @GenericBase.vtable, @GenericBase(%T) [symbolic]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
+// CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %GenericBase.018 [symbolic]
+// CHECK:STDOUT:   %T1: type = class_type @T1 [concrete]
+// CHECK:STDOUT:   %NonGenericDerived: type = class_type @NonGenericDerived [concrete]
+// CHECK:STDOUT:   %GenericBase.f84: type = class_type @GenericBase, @GenericBase(%T1) [concrete]
+// CHECK:STDOUT:   %F1.type.648: type = fn_type @F1, @GenericBase(%T1) [concrete]
+// CHECK:STDOUT:   %F1.4d3: %F1.type.648 = struct_value () [concrete]
+// CHECK:STDOUT:   %F2.type.d79: type = fn_type @F2.1, @GenericBase(%T1) [concrete]
+// CHECK:STDOUT:   %F2.d59: %F2.type.d79 = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.7a9: type = pattern_type %GenericBase.f84 [concrete]
+// CHECK:STDOUT:   %F1.specific_fn.094: <specific function> = specific_function %F1.4d3, @F1(%T1) [concrete]
+// CHECK:STDOUT:   %F2.specific_fn.b8b: <specific function> = specific_function %F2.d59, @F2.1(%T1) [concrete]
+// CHECK:STDOUT:   %GenericBase.vtable_ptr.d41: ref %ptr.454 = vtable_ptr @GenericBase.vtable, @GenericBase(%T1) [concrete]
+// CHECK:STDOUT:   %NonGenericDerived.elem: type = unbound_element_type %NonGenericDerived, %GenericBase.f84 [concrete]
+// CHECK:STDOUT:   %pattern_type.5fc: type = pattern_type %NonGenericDerived [concrete]
+// CHECK:STDOUT:   %F2.type.c10: type = fn_type @F2.2 [concrete]
+// CHECK:STDOUT:   %F2.deb: %F2.type.c10 = struct_value () [concrete]
+// CHECK:STDOUT:   %F3.type: type = fn_type @F3 [concrete]
+// CHECK:STDOUT:   %F3: %F3.type = struct_value () [concrete]
+// CHECK:STDOUT:   %NonGenericDerived.vtable_ptr: ref %ptr.454 = vtable_ptr @NonGenericDerived.vtable [concrete]
+// CHECK:STDOUT:   %struct_type.base.29a: type = struct_type {.base: %GenericBase.f84} [concrete]
+// CHECK:STDOUT:   %complete_type.b6e: <witness> = complete_type_witness %struct_type.base.29a [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .GenericBase = %GenericBase.decl
+// CHECK:STDOUT:     .T1 = %T1.decl
+// CHECK:STDOUT:     .NonGenericDerived = %NonGenericDerived.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %GenericBase.decl: %GenericBase.type = class_decl @GenericBase [concrete = constants.%GenericBase.generic] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc4_24.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_24.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %T1.decl: type = class_decl @T1 [concrete = constants.%T1] {} {}
+// CHECK:STDOUT:   %NonGenericDerived.decl: type = class_decl @NonGenericDerived [concrete = constants.%NonGenericDerived] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @GenericBase(%T.loc4_24.1: type) {
+// CHECK:STDOUT:   %T.loc4_24.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_24.2 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1, @GenericBase(%T.loc4_24.2) [symbolic = %F1.type (constants.%F1.type.2af)]
+// CHECK:STDOUT:   %F1: @GenericBase.%F1.type (%F1.type.2af) = struct_value () [symbolic = %F1 (constants.%F1.80a)]
+// CHECK:STDOUT:   %F2.type: type = fn_type @F2.1, @GenericBase(%T.loc4_24.2) [symbolic = %F2.type (constants.%F2.type.2ca)]
+// CHECK:STDOUT:   %F2: @GenericBase.%F2.type (%F2.type.2ca) = struct_value () [symbolic = %F2 (constants.%F2.414)]
+// CHECK:STDOUT:   %F1.specific_fn.loc7_1.2: <specific function> = specific_function %F1, @F1(%T.loc4_24.2) [symbolic = %F1.specific_fn.loc7_1.2 (constants.%F1.specific_fn.aea)]
+// CHECK:STDOUT:   %F2.specific_fn.loc7_1.2: <specific function> = specific_function %F2, @F2.1(%T.loc4_24.2) [symbolic = %F2.specific_fn.loc7_1.2 (constants.%F2.specific_fn.758)]
+// CHECK:STDOUT:   %vtable_ptr.loc7_1.2: ref %ptr.454 = vtable_ptr @GenericBase.vtable, @GenericBase(%T.loc4_24.2) [symbolic = %vtable_ptr.loc7_1.2 (constants.%GenericBase.vtable_ptr.e41)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %F1.decl: @GenericBase.%F1.type (%F1.type.2af) = fn_decl @F1 [symbolic = @GenericBase.%F1 (constants.%F1.80a)] {
+// CHECK:STDOUT:       %self.patt: @F1.%pattern_type (%pattern_type.34e) = binding_pattern self [concrete]
+// CHECK:STDOUT:       %self.param_patt: @F1.%pattern_type (%pattern_type.34e) = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %self.param: @F1.%GenericBase (%GenericBase.018) = value_param call_param0
+// CHECK:STDOUT:       %.loc5_23.1: type = splice_block %Self.ref [symbolic = %GenericBase (constants.%GenericBase.018)] {
+// CHECK:STDOUT:         %.loc5_23.2: type = specific_constant constants.%GenericBase.018, @GenericBase(constants.%T) [symbolic = %GenericBase (constants.%GenericBase.018)]
+// CHECK:STDOUT:         %Self.ref: type = name_ref Self, %.loc5_23.2 [symbolic = %GenericBase (constants.%GenericBase.018)]
+// CHECK:STDOUT:       }
+// CHECK:STDOUT:       %self: @F1.%GenericBase (%GenericBase.018) = bind_name self, %self.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %F2.decl: @GenericBase.%F2.type (%F2.type.2ca) = fn_decl @F2.1 [symbolic = @GenericBase.%F2 (constants.%F2.414)] {
+// CHECK:STDOUT:       %self.patt: @F2.1.%pattern_type (%pattern_type.34e) = binding_pattern self [concrete]
+// CHECK:STDOUT:       %self.param_patt: @F2.1.%pattern_type (%pattern_type.34e) = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %self.param: @F2.1.%GenericBase (%GenericBase.018) = value_param call_param0
+// CHECK:STDOUT:       %.loc6_23.1: type = splice_block %Self.ref [symbolic = %GenericBase (constants.%GenericBase.018)] {
+// CHECK:STDOUT:         %.loc6_23.2: type = specific_constant constants.%GenericBase.018, @GenericBase(constants.%T) [symbolic = %GenericBase (constants.%GenericBase.018)]
+// CHECK:STDOUT:         %Self.ref: type = name_ref Self, %.loc6_23.2 [symbolic = %GenericBase (constants.%GenericBase.018)]
+// CHECK:STDOUT:       }
+// CHECK:STDOUT:       %self: @F2.1.%GenericBase (%GenericBase.018) = bind_name self, %self.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %F1.specific_fn.loc7_1.1: <specific function> = specific_function %F1.decl, @F1(constants.%T) [symbolic = %F1.specific_fn.loc7_1.2 (constants.%F1.specific_fn.aea)]
+// CHECK:STDOUT:     %F2.specific_fn.loc7_1.1: <specific function> = specific_function %F2.decl, @F2.1(constants.%T) [symbolic = %F2.specific_fn.loc7_1.2 (constants.%F2.specific_fn.758)]
+// CHECK:STDOUT:     %vtable_ptr.loc7_1.1: ref %ptr.454 = vtable_ptr @GenericBase.vtable, @GenericBase(constants.%T) [symbolic = %vtable_ptr.loc7_1.2 (constants.%GenericBase.vtable_ptr.e41)]
+// CHECK:STDOUT:     %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete = constants.%struct_type.vptr]
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:     vtable_ptr = %vtable_ptr.loc7_1.1
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%GenericBase.018
+// CHECK:STDOUT:     .F1 = %F1.decl
+// CHECK:STDOUT:     .F2 = %F2.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @T1;
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NonGenericDerived {
+// CHECK:STDOUT:   %GenericBase.ref: %GenericBase.type = name_ref GenericBase, file.%GenericBase.decl [concrete = constants.%GenericBase.generic]
+// CHECK:STDOUT:   %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
+// CHECK:STDOUT:   %GenericBase: type = class_type @GenericBase, @GenericBase(constants.%T1) [concrete = constants.%GenericBase.f84]
+// CHECK:STDOUT:   %.loc12: %NonGenericDerived.elem = base_decl %GenericBase, element0 [concrete]
+// CHECK:STDOUT:   %F2.decl: %F2.type.c10 = fn_decl @F2.2 [concrete = constants.%F2.deb] {
+// CHECK:STDOUT:     %self.patt: %pattern_type.5fc = binding_pattern self [concrete]
+// CHECK:STDOUT:     %self.param_patt: %pattern_type.5fc = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %NonGenericDerived = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%NonGenericDerived [concrete = constants.%NonGenericDerived]
+// CHECK:STDOUT:     %self: %NonGenericDerived = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F3.decl: %F3.type = fn_decl @F3 [concrete = constants.%F3] {
+// CHECK:STDOUT:     %self.patt: %pattern_type.5fc = binding_pattern self [concrete]
+// CHECK:STDOUT:     %self.param_patt: %pattern_type.5fc = value_param_pattern %self.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %NonGenericDerived = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%NonGenericDerived [concrete = constants.%NonGenericDerived]
+// CHECK:STDOUT:     %self: %NonGenericDerived = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %vtable_ptr: ref %ptr.454 = vtable_ptr @NonGenericDerived.vtable [concrete = constants.%NonGenericDerived.vtable_ptr]
+// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %GenericBase.f84} [concrete = constants.%struct_type.base.29a]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.b6e]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:   vtable_ptr = %vtable_ptr
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%NonGenericDerived
+// CHECK:STDOUT:   .GenericBase = <poisoned>
+// CHECK:STDOUT:   .T1 = <poisoned>
+// CHECK:STDOUT:   .base = %.loc12
+// CHECK:STDOUT:   .F2 = %F2.decl
+// CHECK:STDOUT:   .F3 = %F3.decl
+// CHECK:STDOUT:   extend %GenericBase
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: vtable @GenericBase.vtable {
+// CHECK:STDOUT:   @GenericBase.%F1.specific_fn.loc7_1.1
+// CHECK:STDOUT:   @GenericBase.%F2.specific_fn.loc7_1.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: vtable @NonGenericDerived.vtable {
+// CHECK:STDOUT:   constants.%F1.specific_fn.094
+// CHECK:STDOUT:   @NonGenericDerived.%F2.decl
+// CHECK:STDOUT:   @NonGenericDerived.%F3.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic virtual fn @F1(@GenericBase.%T.loc4_24.1: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %GenericBase: type = class_type @GenericBase, @GenericBase(%T) [symbolic = %GenericBase (constants.%GenericBase.018)]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %GenericBase [symbolic = %pattern_type (constants.%pattern_type.34e)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %GenericBase [symbolic = %require_complete (constants.%require_complete)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F1.%GenericBase (%GenericBase.018)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic virtual fn @F2.1(@GenericBase.%T.loc4_24.1: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %GenericBase: type = class_type @GenericBase, @GenericBase(%T) [symbolic = %GenericBase (constants.%GenericBase.018)]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %GenericBase [symbolic = %pattern_type (constants.%pattern_type.34e)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %GenericBase [symbolic = %require_complete (constants.%require_complete)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   virtual fn(%self.param: @F2.1.%GenericBase (%GenericBase.018)) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl fn @F2.2(%self.param: %NonGenericDerived) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: virtual fn @F3(%self.param: %NonGenericDerived) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GenericBase(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_24.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F1.type => constants.%F1.type.2af
+// CHECK:STDOUT:   %F1 => constants.%F1.80a
+// CHECK:STDOUT:   %F2.type => constants.%F2.type.2ca
+// CHECK:STDOUT:   %F2 => constants.%F2.414
+// CHECK:STDOUT:   %F1.specific_fn.loc7_1.2 => constants.%F1.specific_fn.aea
+// CHECK:STDOUT:   %F2.specific_fn.loc7_1.2 => constants.%F2.specific_fn.758
+// CHECK:STDOUT:   %vtable_ptr.loc7_1.2 => constants.%GenericBase.vtable_ptr.e41
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F1(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %GenericBase => constants.%GenericBase.018
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.34e
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%require_complete
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F2.1(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %GenericBase => constants.%GenericBase.018
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.34e
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%require_complete
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GenericBase(constants.%T1) {
+// CHECK:STDOUT:   %T.loc4_24.2 => constants.%T1
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %F1.type => constants.%F1.type.648
+// CHECK:STDOUT:   %F1 => constants.%F1.4d3
+// CHECK:STDOUT:   %F2.type => constants.%F2.type.d79
+// CHECK:STDOUT:   %F2 => constants.%F2.d59
+// CHECK:STDOUT:   %F1.specific_fn.loc7_1.2 => constants.%F1.specific_fn.094
+// CHECK:STDOUT:   %F2.specific_fn.loc7_1.2 => constants.%F2.specific_fn.b8b
+// CHECK:STDOUT:   %vtable_ptr.loc7_1.2 => constants.%GenericBase.vtable_ptr.d41
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F1(constants.%T1) {
+// CHECK:STDOUT:   %T => constants.%T1
+// CHECK:STDOUT:   %GenericBase => constants.%GenericBase.f84
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.7a9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%complete_type.513
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F2.1(constants.%T1) {
+// CHECK:STDOUT:   %T => constants.%T1
+// CHECK:STDOUT:   %GenericBase => constants.%GenericBase.f84
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.7a9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%complete_type.513
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 3
toolchain/lower/constant.cpp

@@ -59,8 +59,9 @@ class ConstantContext {
     return file_context_->GetFunction(function_id);
   }
 
-  auto GetVtable(SemIR::VtableId vtable_id) -> llvm::GlobalVariable* {
-    return file_context_->GetVtable(vtable_id);
+  auto GetVtable(SemIR::VtableId vtable_id, SemIR::SpecificId specific_id)
+      -> llvm::GlobalVariable* {
+    return file_context_->GetVtable(vtable_id, specific_id);
   }
 
   // Returns a lowered type for the given type_id.
@@ -157,7 +158,7 @@ static auto EmitAsConstant(ConstantContext& context, SemIR::AddrOf inst)
 
 static auto EmitAsConstant(ConstantContext& context, SemIR::VtablePtr inst)
     -> llvm::Constant* {
-  return context.GetVtable(inst.vtable_id);
+  return context.GetVtable(inst.vtable_id, inst.specific_id);
 }
 
 static auto EmitAsConstant(ConstantContext& context,

+ 14 - 14
toolchain/lower/file_context.cpp

@@ -54,7 +54,9 @@ FileContext::FileContext(Context& context, const SemIR::File& sem_ir,
       constants_(LoweredConstantStore::MakeWithExplicitSize(
           sem_ir.insts().size(), nullptr)),
       lowered_specifics_(sem_ir.generics(), {}),
-      coalescer_(vlog_stream_, sem_ir.specifics()) {
+      coalescer_(vlog_stream_, sem_ir.specifics()),
+      vtables_(decltype(vtables_)::MakeForOverwrite(sem_ir.vtables())),
+      specific_vtables_(sem_ir.specifics(), nullptr) {
   // Initialization that relies on invariants of the class.
   cpp_code_generator_ = CreateCppCodeGenerator();
   CARBON_CHECK(!sem_ir.has_errors(),
@@ -84,9 +86,12 @@ auto FileContext::PrepareToLower() -> void {
 
   // TODO: Split vtable declaration creation from definition creation to avoid
   // redundant vtable definitions for imported vtables.
-  for (const auto& [id, class_info] : sem_ir_->vtables().enumerate()) {
-    if (auto* vtable = BuildVtable(class_info)) {
-      vtables_.Insert(id, vtable);
+  for (const auto& [id, vtable] : sem_ir_->vtables().enumerate()) {
+    const auto& class_info = sem_ir().classes().Get(vtable.class_id);
+    // Vtables can't be generated for generics, only for their specifics - and
+    // must be done lazily based on the use of those specifics.
+    if (!class_info.generic_id.has_value()) {
+      vtables_.Set(id, BuildVtable(vtable, SemIR::SpecificId::None));
     }
   }
 
@@ -922,18 +927,13 @@ auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> Context::LocForDI {
       GetAbsoluteNodeId(sem_ir_, SemIR::LocId(inst_id)).back());
 }
 
-auto FileContext::BuildVtable(const SemIR::Vtable& vtable)
+auto FileContext::BuildVtable(const SemIR::Vtable& vtable,
+                              SemIR::SpecificId specific_id)
     -> llvm::GlobalVariable* {
   const auto& class_info = sem_ir().classes().Get(vtable.class_id);
 
-  // Vtables can't be generated for generics, only for their specifics - and
-  // must be done lazily based on the use of those specifics.
-  if (class_info.generic_id.has_value()) {
-    return nullptr;
-  }
-
   Mangler m(*this);
-  std::string mangled_name = m.MangleVTable(class_info);
+  std::string mangled_name = m.MangleVTable(class_info, specific_id);
 
   if (sem_ir()
           .insts()
@@ -970,12 +970,12 @@ auto FileContext::BuildVtable(const SemIR::Vtable& vtable)
   vfuncs.reserve(vtable_inst_block.size());
 
   for (auto fn_decl_id : vtable_inst_block) {
-    auto fn_decl = GetCalleeFunction(sem_ir(), fn_decl_id);
+    auto fn_decl = GetCalleeFunction(sem_ir(), fn_decl_id, specific_id);
     vfuncs.push_back(llvm::ConstantExpr::getTrunc(
         llvm::ConstantExpr::getSub(
             llvm::ConstantExpr::getPtrToInt(
                 GetOrCreateFunction(fn_decl.function_id,
-                                    SemIR::SpecificId::None),
+                                    fn_decl.resolved_specific_id),
                 i64_type),
             vtable_const_int),
         i32_type));

+ 16 - 4
toolchain/lower/file_context.h

@@ -82,8 +82,17 @@ class FileContext {
   auto GetConstant(SemIR::ConstantId const_id, SemIR::InstId use_inst_id)
       -> llvm::Value*;
 
-  auto GetVtable(SemIR::VtableId vtable_id) const -> llvm::GlobalVariable* {
-    return *vtables_[vtable_id];
+  auto GetVtable(SemIR::VtableId vtable_id, SemIR::SpecificId specific_id)
+      -> llvm::GlobalVariable* {
+    if (!specific_id.has_value()) {
+      return vtables_.Get(vtable_id);
+    }
+    auto*& specific_vtable = specific_vtables_.Get(specific_id);
+    if (!specific_vtable) {
+      specific_vtable =
+          BuildVtable(sem_ir().vtables().Get(vtable_id), specific_id);
+    }
+    return specific_vtable;
   }
 
   // Returns the empty LLVM struct type used to represent the type `type`.
@@ -184,7 +193,8 @@ class FileContext {
   // the caller.
   auto BuildType(SemIR::InstId inst_id) -> llvm::Type*;
 
-  auto BuildVtable(const SemIR::Vtable& vtable) -> llvm::GlobalVariable*;
+  auto BuildVtable(const SemIR::Vtable& vtable, SemIR::SpecificId specific_id)
+      -> llvm::GlobalVariable*;
 
   // Records a specific that was lowered for a generic. These are added one
   // by one while lowering their definitions.
@@ -243,7 +253,9 @@ class FileContext {
 
   SpecificCoalescer coalescer_;
 
-  Map<SemIR::VtableId, llvm::GlobalVariable*> vtables_;
+  FixedSizeValueStore<SemIR::VtableId, llvm::GlobalVariable*> vtables_;
+  FixedSizeValueStore<SemIR::SpecificId, llvm::GlobalVariable*>
+      specific_vtables_;
 };
 
 }  // namespace Carbon::Lower

+ 3 - 2
toolchain/lower/function_context.h

@@ -268,8 +268,9 @@ class FunctionContext {
     return format_string;
   }
 
-  auto GetVtable(SemIR::VtableId vtable_id) const -> llvm::GlobalVariable* {
-    return file_context_->GetVtable(vtable_id);
+  auto GetVtable(SemIR::VtableId vtable_id, SemIR::SpecificId specific_id) const
+      -> llvm::GlobalVariable* {
+    return file_context_->GetVtable(vtable_id, specific_id);
   }
 
  private:

+ 11 - 3
toolchain/lower/mangler.cpp

@@ -175,6 +175,13 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
   // the mangling.
   MangleInverseQualifiedNameScope(os, function.parent_scope_id);
 
+  MangleSpecificId(os, specific_id);
+
+  return os.TakeStr();
+}
+
+auto Mangler::MangleSpecificId(llvm::raw_ostream& os,
+                               SemIR::SpecificId specific_id) -> void {
   // TODO: Add proper support for mangling generic entities. For now we use a
   // fingerprint of the specific arguments, which should be stable across files,
   // but isn't necessarily stable across toolchain changes.
@@ -186,8 +193,6 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
             &sem_ir(), sem_ir().specifics().Get(specific_id).args_id),
         llvm::HexPrintStyle::Lower, 16);
   }
-
-  return os.TakeStr();
 }
 
 auto Mangler::MangleGlobalVariable(SemIR::InstId pattern_id) -> std::string {
@@ -215,7 +220,8 @@ auto Mangler::MangleCppClang(const clang::NamedDecl* decl) -> std::string {
       .str();
 }
 
-auto Mangler::MangleVTable(const SemIR::Class& class_info) -> std::string {
+auto Mangler::MangleVTable(const SemIR::Class& class_info,
+                           SemIR::SpecificId specific_id) -> std::string {
   RawStringOstream os;
   os << "_C";
 
@@ -226,6 +232,8 @@ auto Mangler::MangleVTable(const SemIR::Class& class_info) -> std::string {
 
   os << ".$vtable";
 
+  MangleSpecificId(os, specific_id);
+
   return os.TakeStr();
 }
 

+ 6 - 1
toolchain/lower/mangler.h

@@ -37,12 +37,17 @@ class Mangler {
 
   // Produce a deterministically unique mangled name for the specified class's
   // vtable.
-  auto MangleVTable(const SemIR::Class& class_info) -> std::string;
+  auto MangleVTable(const SemIR::Class& class_info,
+                    SemIR::SpecificId specific_id) -> std::string;
 
  private:
   // Mangle this `NameId` as an individual name component.
   auto MangleNameId(llvm::raw_ostream& os, SemIR::NameId name_id) -> void;
 
+  // Mangle this `SpecificId`, or nothing if it is `SpecificId::None`.
+  auto MangleSpecificId(llvm::raw_ostream& os, SemIR::SpecificId specific_id)
+      -> void;
+
   // Mangle this qualified name with inner scope first, working outwards. This
   // may reduce the incidence of common prefixes in the name mangling. (i.e.:
   // every standard library name won't have a common prefix that has to be

+ 145 - 1
toolchain/lower/testdata/class/virtual.carbon

@@ -112,7 +112,45 @@ fn Use() {
 library "[[@TEST_NAME]]";
 
 base class Base(T:! type) {
-  virtual fn F[self: Self]();
+  virtual fn F[self: Self]() { }
+}
+
+// --- generic_use.carbon
+
+library "[[@TEST_NAME]]";
+
+base class Base(T:! type) {
+  virtual fn F[self: Self]() {
+    var v: T;
+  }
+}
+
+class T1 { }
+class T2 {
+  var v: T1;
+}
+
+fn F() {
+  var t1: Base(T1) = {};
+  var t2: Base(T2) = {};
+}
+
+// --- generic_base.carbon
+
+library "[[@TEST_NAME]]";
+
+base class Base(T:! type) {
+  virtual fn F[self: Self]() { }
+}
+
+class T1;
+
+class Derived {
+  extend base: Base(T1);
+}
+
+fn Make() {
+  var v: Derived;
 }
 
 // CHECK:STDOUT: ; ModuleID = 'classes.carbon'
@@ -425,3 +463,109 @@ base class Base(T:! type) {
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !3 = !DIFile(filename: "generic_noop.carbon", directory: "")
+// CHECK:STDOUT: ; ModuleID = 'generic_use.carbon'
+// CHECK:STDOUT: source_filename = "generic_use.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @"_CBase.Main.$vtable.4d2ffa01ebfb7a1d" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main.4d2ffa01ebfb7a1d to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable.4d2ffa01ebfb7a1d" to i64)) to i32)]
+// CHECK:STDOUT: @"_CBase.Main.$vtable.4f4310253a4de9ab" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main.4f4310253a4de9ab to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable.4f4310253a4de9ab" to i64)) to i32)]
+// CHECK:STDOUT: @Base.val.20a.loc16_3 = internal constant { ptr } { ptr @"_CBase.Main.$vtable.4d2ffa01ebfb7a1d" }
+// CHECK:STDOUT: @Base.val.f56.loc17_3 = internal constant { ptr } { ptr @"_CBase.Main.$vtable.4f4310253a4de9ab" }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CF.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %t1.var = alloca { ptr }, align 8, !dbg !7
+// CHECK:STDOUT:   %t2.var = alloca { ptr }, align 8, !dbg !8
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %t1.var), !dbg !7
+// CHECK:STDOUT:   %.loc16_23.2.vptr = getelementptr inbounds nuw { ptr }, ptr %t1.var, i32 0, i32 0, !dbg !9
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %t1.var, ptr align 8 @Base.val.20a.loc16_3, i64 8, i1 false), !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %t2.var), !dbg !8
+// CHECK:STDOUT:   %.loc17_23.2.vptr = getelementptr inbounds nuw { ptr }, ptr %t2.var, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %t2.var, ptr align 8 @Base.val.f56.loc17_3, i64 8, i1 false), !dbg !8
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CF.Base.Main.4d2ffa01ebfb7a1d(ptr %self) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %v.var = alloca {}, align 8, !dbg !13
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %v.var), !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CF.Base.Main.4f4310253a4de9ab(ptr %self) !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %v.var = alloca { {} }, align 8, !dbg !16
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %v.var), !dbg !16
+// CHECK:STDOUT:   ret void, !dbg !17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 3, 2, 1, 0 }
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "generic_use.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 15, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 16, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 17, column: 3, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 16, column: 22, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 17, column: 22, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 15, column: 1, scope: !4)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "F", linkageName: "_CF.Base.Main.4d2ffa01ebfb7a1d", scope: null, file: !3, line: 5, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !13 = !DILocation(line: 6, column: 5, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 5, column: 3, scope: !12)
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "F", linkageName: "_CF.Base.Main.4f4310253a4de9ab", scope: null, file: !3, line: 5, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !16 = !DILocation(line: 6, column: 5, scope: !15)
+// CHECK:STDOUT: !17 = !DILocation(line: 5, column: 3, scope: !15)
+// CHECK:STDOUT: ; ModuleID = 'generic_base.carbon'
+// CHECK:STDOUT: source_filename = "generic_base.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main.4d2ffa01ebfb7a1d to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @"_CBase.Main.$vtable.4d2ffa01ebfb7a1d" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main.4d2ffa01ebfb7a1d to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable.4d2ffa01ebfb7a1d" to i64)) to i32)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMake.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %v.var = alloca { { ptr } }, align 8, !dbg !7
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %v.var), !dbg !7
+// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define linkonce_odr void @_CF.Base.Main.4d2ffa01ebfb7a1d(ptr %self) !dbg !9 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "generic_base.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Make", linkageName: "_CMake.Main", scope: null, file: !3, line: 14, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 15, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 14, column: 1, scope: !4)
+// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "F", linkageName: "_CF.Base.Main.4d2ffa01ebfb7a1d", scope: null, file: !3, line: 5, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !10 = !DILocation(line: 5, column: 3, scope: !9)