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

Represent vtables as a top level SemIR construct (#5472)

The goal was/is to reduce the overhead for vtables in generics - the
previous representation/prior to this patch caused a new vtable to be
created in every specific which isn't generally what we want for Carbon
generics (the whole specific/generic thing is meant to avoid creating
specific versions for things that can be a generic form parameterized by
a specific instead of manifest as a unique entity per specific)

So this moves vtables to a top level object (like functions, classes,
etc). Each dynamic class will have a vtable in this list.

Classes have a `vtable_ptr` instruction in them that points to the
vtable.

The actual generic support hasn't been implemented in this patch, as
I've been struggling with just getting this part of the migration going
& wanted to get it flushed out before adding the additional
complications.

It's possible more laziness when doing cross-file importing would be
suitable - for instance if we only need to reference the vtable from
another file, but don't need to know its individual contents, it may be
beneficial for the functions in the vtable to be import_refs (or to add
another layer of indirection - so it can be a single import_ref
all-or-nothing for the functions in the vtable).

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
David Blaikie 10 месяцев назад
Родитель
Сommit
124313269a

+ 42 - 23
toolchain/check/class.cpp

@@ -131,31 +131,23 @@ 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, Parse::NodeId node_id,
-                        SemIR::InstId base_vtable_id,
+static auto BuildVtable(Context& context, SemIR::ClassId class_id,
+                        SemIR::VtableId base_vtable_id,
                         llvm::ArrayRef<SemIR::InstId> vtable_contents)
-    -> SemIR::InstId {
+    -> SemIR::VtableId {
   llvm::SmallVector<SemIR::InstId> vtable;
   if (base_vtable_id.has_value()) {
-    LoadImportRef(context, base_vtable_id);
-    auto canonical_base_vtable_id =
-        context.constant_values().GetConstantInstId(base_vtable_id);
-    if (canonical_base_vtable_id == SemIR::ErrorInst::InstId) {
-      return SemIR::ErrorInst::InstId;
-    }
     auto base_vtable_inst_block = context.inst_blocks().Get(
-        context.insts()
-            .GetAs<SemIR::Vtable>(canonical_base_vtable_id)
-            .virtual_functions_id);
+        context.vtables().Get(base_vtable_id).virtual_functions_id);
     // TODO: Avoid quadratic search. Perhaps build a map from `NameId` to the
     // elements of the top of `vtable_stack`.
     for (auto fn_decl_id : base_vtable_inst_block) {
       auto fn_decl = GetCalleeFunction(context.sem_ir(), fn_decl_id);
-      auto& fn = context.functions().Get(fn_decl.function_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);
-        const auto& override_fn =
+        auto& override_fn =
             context.functions().Get(override_fn_decl.function_id);
         if (override_fn.virtual_modifier ==
                 SemIR::FunctionFields::VirtualModifier::Impl &&
@@ -167,9 +159,11 @@ static auto BuildVtable(Context& context, Parse::NodeId node_id,
                                    /*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;
         }
       }
-      fn.virtual_index = vtable.size();
       vtable.push_back(fn_decl_id);
     }
   }
@@ -182,10 +176,10 @@ static auto BuildVtable(Context& context, Parse::NodeId node_id,
       vtable.push_back(inst_id);
     }
   }
-  return AddInst<SemIR::Vtable>(
-      context, node_id,
-      {.type_id = GetSingletonType(context, SemIR::VtableType::TypeInstId),
-       .virtual_functions_id = context.inst_blocks().Add(vtable)});
+
+  return context.vtables().Add(
+      {{.class_id = class_id,
+        .virtual_functions_id = context.inst_blocks().Add(vtable)}});
 }
 
 // Checks that the specified finished class definition is valid and builds and
@@ -235,10 +229,35 @@ static auto CheckCompleteClassType(
   }
 
   if (class_info.is_dynamic) {
-    class_info.vtable_id = BuildVtable(
-        context, node_id,
-        defining_vptr ? SemIR::InstId::None : base_class_info->vtable_id,
-        vtable_contents);
+    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 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).
+    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});
   }
 
   auto struct_type_inst_id = AddTypeInst<SemIR::StructType>(

+ 1 - 0
toolchain/check/context.h

@@ -242,6 +242,7 @@ class Context {
     return sem_ir().functions();
   }
   auto classes() -> ValueStore<SemIR::ClassId>& { return sem_ir().classes(); }
+  auto vtables() -> ValueStore<SemIR::VtableId>& { return sem_ir().vtables(); }
   auto interfaces() -> ValueStore<SemIR::InterfaceId>& {
     return sem_ir().interfaces();
   }

+ 21 - 19
toolchain/check/convert.cpp

@@ -157,7 +157,7 @@ static auto ConvertAggregateElement(
     ConversionTarget::Kind kind, SemIR::InstId target_id,
     SemIR::TypeInstId target_elem_type_inst, PendingBlock* target_block,
     size_t src_field_index, size_t target_field_index,
-    SemIR::InstId vtable_id = SemIR::InstId::None) -> SemIR::InstId {
+    SemIR::InstId vtable_ptr_inst_id = SemIR::InstId::None) -> SemIR::InstId {
   auto src_elem_type =
       context.types().GetTypeIdForTypeInstId(src_elem_type_inst);
   auto target_elem_type =
@@ -185,7 +185,7 @@ static auto ConvertAggregateElement(
   target.init_id = MakeElementAccessInst<TargetAccessInstT>(
       context, loc_id, target_id, target_elem_type, *target_block,
       target_field_index);
-  return Convert(context, loc_id, src_elem_id, target, vtable_id);
+  return Convert(context, loc_id, src_elem_id, target, vtable_ptr_inst_id);
 }
 
 // Performs a conversion from a tuple to an array type. This function only
@@ -382,7 +382,7 @@ template <typename TargetAccessInstT>
 static auto ConvertStructToStructOrClass(
     Context& context, SemIR::StructType src_type, SemIR::StructType dest_type,
     SemIR::InstId value_id, ConversionTarget target,
-    SemIR::InstId dest_vtable_id = SemIR::InstId::None) -> SemIR::InstId {
+    SemIR::InstId vtable_ptr_inst_id = SemIR::InstId::None) -> SemIR::InstId {
   static_assert(std::is_same_v<SemIR::ClassElementAccess, TargetAccessInstT> ||
                 std::is_same_v<SemIR::StructAccess, TargetAccessInstT>);
   constexpr bool ToClass =
@@ -472,13 +472,11 @@ static auto ConvertStructToStructOrClass(
                                              {.type_id = vptr_type_id,
                                               .base_id = target.init_id,
                                               .index = SemIR::ElementIndex(i)});
-      auto vtable_ptr_id = AddInst<SemIR::VtablePtr>(
-          context, value_loc_id,
-          {.type_id = vptr_type_id, .vtable_id = dest_vtable_id});
-      auto init_id = AddInst<SemIR::InitializeFrom>(context, value_loc_id,
-                                                    {.type_id = vptr_type_id,
-                                                     .src_id = vtable_ptr_id,
-                                                     .dest_id = dest_id});
+      auto init_id =
+          AddInst<SemIR::InitializeFrom>(context, value_loc_id,
+                                         {.type_id = vptr_type_id,
+                                          .src_id = vtable_ptr_inst_id,
+                                          .dest_id = dest_id});
       new_block.Set(i, init_id);
       continue;
     }
@@ -520,7 +518,7 @@ static auto ConvertStructToStructOrClass(
             context, value_loc_id, value_id, src_field.type_inst_id,
             literal_elems, inner_kind, target.init_id, dest_field.type_inst_id,
             target.init_block, src_field_index,
-            src_field_index + dest_vptr_offset, dest_vtable_id);
+            src_field_index + dest_vptr_offset, vtable_ptr_inst_id);
     if (init_id == SemIR::ErrorInst::InstId) {
       return SemIR::ErrorInst::InstId;
     }
@@ -565,7 +563,8 @@ static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
 static auto ConvertStructToClass(
     Context& context, SemIR::StructType src_type, SemIR::ClassType dest_type,
     SemIR::InstId value_id, ConversionTarget target,
-    SemIR::InstId dest_vtable_id = SemIR::InstId::None) -> SemIR::InstId {
+    SemIR::InstId dest_vtable_ptr_inst_id = SemIR::InstId::None)
+    -> SemIR::InstId {
   PendingBlock target_block(&context);
   auto& dest_class_info = context.classes().Get(dest_type.class_id);
   CARBON_CHECK(dest_class_info.inheritance_kind != SemIR::Class::Abstract);
@@ -589,7 +588,10 @@ static auto ConvertStructToClass(
 
   auto result_id = ConvertStructToStructOrClass<SemIR::ClassElementAccess>(
       context, src_type, dest_struct_type, value_id, target,
-      dest_vtable_id.has_value() ? dest_vtable_id : dest_class_info.vtable_id);
+      // TODO: Pass down the specific_id of the passed in
+      // dest_vtable_ptr_inst_id, or from the dest_type.specific_id.
+      dest_vtable_ptr_inst_id.has_value() ? dest_vtable_ptr_inst_id
+                                          : dest_class_info.vtable_ptr_id);
 
   if (need_temporary) {
     target_block.InsertHere();
@@ -790,8 +792,8 @@ static auto DiagnoseConversionFailureToConstraintValue(
 
 static auto PerformBuiltinConversion(
     Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
-    ConversionTarget target, SemIR::InstId vtable_id = SemIR::InstId::None)
-    -> SemIR::InstId {
+    ConversionTarget target,
+    SemIR::InstId vtable_ptr_inst_id = SemIR::InstId::None) -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
   auto value = sem_ir.insts().Get(value_id);
   auto value_type_id = value.type_id();
@@ -964,7 +966,7 @@ static auto PerformBuiltinConversion(
                .adapt_id.has_value()) {
         return ConvertStructToClass(context, *src_struct_type,
                                     *target_class_type, value_id, target,
-                                    vtable_id);
+                                    vtable_ptr_inst_id);
       }
     }
 
@@ -1163,7 +1165,7 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
 }
 
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
-             ConversionTarget target, SemIR::InstId vtable_id)
+             ConversionTarget target, SemIR::InstId vtable_ptr_inst_id)
     -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
   auto orig_expr_id = expr_id;
@@ -1227,8 +1229,8 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
   }
 
   // Check whether any builtin conversion applies.
-  expr_id =
-      PerformBuiltinConversion(context, loc_id, expr_id, target, vtable_id);
+  expr_id = PerformBuiltinConversion(context, loc_id, expr_id, target,
+                                     vtable_ptr_inst_id);
   if (expr_id == SemIR::ErrorInst::InstId) {
     return expr_id;
   }

+ 2 - 1
toolchain/check/convert.h

@@ -64,7 +64,8 @@ struct ConversionTarget {
 // type.
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
              ConversionTarget target,
-             SemIR::InstId vtable_id = SemIR::InstId::None) -> SemIR::InstId;
+             SemIR::InstId vtable_ptr_inst_id = SemIR::InstId::None)
+    -> SemIR::InstId;
 
 // Performs initialization of `target_id` from `value_id`. Returns the
 // possibly-converted initializing expression, which should be assigned to the

+ 73 - 29
toolchain/check/import_ref.cpp

@@ -222,6 +222,7 @@ class ImportContext {
     return import_ir().associated_constants();
   }
   auto import_classes() -> decltype(auto) { return import_ir().classes(); }
+  auto import_vtables() -> decltype(auto) { return import_ir().vtables(); }
   auto import_constant_values() -> decltype(auto) {
     return import_ir().constant_values();
   }
@@ -281,6 +282,7 @@ class ImportContext {
     return local_ir().associated_constants();
   }
   auto local_classes() -> decltype(auto) { return local_ir().classes(); }
+  auto local_vtables() -> decltype(auto) { return local_ir().vtables(); }
   auto local_constant_values() -> decltype(auto) {
     return local_ir().constant_values();
   }
@@ -1619,7 +1621,7 @@ static auto AddClassDefinition(ImportContext& context,
                                SemIR::Class& new_class,
                                SemIR::InstId complete_type_witness_id,
                                SemIR::InstId base_id, SemIR::InstId adapt_id,
-                               SemIR::InstId vtable_id) -> void {
+                               SemIR::InstId vtable_ptr_id) -> void {
   new_class.definition_id = new_class.first_owning_decl_id;
 
   new_class.complete_type_witness_id = complete_type_witness_id;
@@ -1641,8 +1643,8 @@ static auto AddClassDefinition(ImportContext& context,
   if (import_class.adapt_id.has_value()) {
     new_class.adapt_id = adapt_id;
   }
-  if (import_class.vtable_id.has_value()) {
-    new_class.vtable_id = vtable_id;
+  if (import_class.vtable_ptr_id.has_value()) {
+    new_class.vtable_ptr_id = vtable_ptr_id;
   }
 }
 
@@ -1711,15 +1713,17 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
                       ? GetLocalConstantInstId(resolver, import_class.adapt_id)
                       : SemIR::InstId::None;
   auto& new_class = resolver.local_classes().Get(class_id);
+  // TODO: Make vtable_ptr_id lazily loaded, so we pull in the vtable only when
+  // it's needed, not for every use of the class.
+  auto vtable_ptr_const_id =
+      import_class.vtable_ptr_id.has_value()
+          ? GetLocalConstantId(resolver, import_class.vtable_ptr_id)
+          : SemIR::ConstantId::None;
 
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry(class_const_id, new_class.first_decl_id());
   }
 
-  auto vtable_id = import_class.vtable_id.has_value()
-                       ? AddImportRef(resolver, import_class.vtable_id)
-                       : SemIR::InstId::None;
-
   new_class.parent_scope_id = parent_scope_id;
   new_class.implicit_param_patterns_id = GetLocalCanonicalInstBlockId(
       resolver, import_class.implicit_param_patterns_id,
@@ -1738,8 +1742,17 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
         GetSingletonType(resolver.local_context(),
                          SemIR::WitnessType::TypeInstId),
         import_class.complete_type_witness_id, complete_type_witness_const_id);
+    auto vtable_ptr_id =
+        vtable_ptr_const_id.has_value()
+            ? AddLoadedImportRef(
+                  resolver,
+                  GetSingletonType(resolver.local_context(),
+                                   SemIR::WitnessType::TypeInstId),
+                  import_class.vtable_ptr_id, vtable_ptr_const_id)
+            : SemIR::InstId::None;
     AddClassDefinition(resolver, import_class, new_class,
-                       complete_type_witness_id, base_id, adapt_id, vtable_id);
+                       complete_type_witness_id, base_id, adapt_id,
+                       vtable_ptr_id);
   }
 
   return ResolveResult::Done(class_const_id, new_class.first_decl_id());
@@ -1841,7 +1854,8 @@ static auto MakeFunctionDecl(ImportContext& context,
   function_decl.function_id = context.local_functions().Add(
       {GetIncompleteLocalEntityBase(context, function_decl_id, import_function),
        {.call_params_id = SemIR::InstBlockId::None,
-        .return_slot_pattern_id = SemIR::InstId::None}});
+        .return_slot_pattern_id = SemIR::InstId::None,
+        .virtual_index = import_function.virtual_index}});
 
   function_decl.type_id = GetFunctionType(
       context.local_context(), function_decl.function_id, specific_id);
@@ -1947,6 +1961,54 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
   return ResolveResult::Done(function_const_id, new_function.first_decl_id());
 }
 
+static auto TryResolveTypedInst(ImportRefResolver& resolver,
+                                SemIR::VtablePtr inst,
+                                SemIR::ConstantId /*vtable_const_id*/)
+    -> ResolveResult {
+  const auto& import_vtable = resolver.import_vtables().Get(inst.vtable_id);
+
+  auto class_const_id =
+      GetLocalConstantId(resolver, resolver.import_classes()
+                                       .Get(import_vtable.class_id)
+                                       .first_owning_decl_id);
+
+  // TODO: Ensure the vtable is only imported once, in eg: if there's distinct
+  // vtable constants (imported from multiple libraries using the vtable) that
+  // refer to the same vtable, the vtable should still be singular.
+  auto virtual_functions =
+      GetLocalInstBlockContents(resolver, import_vtable.virtual_functions_id);
+
+  if (resolver.HasNewWork()) {
+    return ResolveResult::Retry();
+  }
+
+  auto class_const_inst = resolver.local_insts().Get(
+      resolver.local_constant_values().GetInstId(class_const_id));
+
+  auto class_id = SemIR::ClassId::None;
+  if (class_const_inst.Is<SemIR::ClassType>()) {
+    class_id = class_const_inst.As<SemIR::ClassType>().class_id;
+  } else {
+    auto generic_class_type =
+        resolver.local_types().GetAs<SemIR::GenericClassType>(
+            class_const_inst.type_id());
+    // TODO: Add support for generic vtables here and elsewhere.
+    // auto specific_id =
+    //    GetOrAddLocalSpecific(resolver, inst.specific_id, specific_data);
+    class_id = generic_class_type.class_id;
+  }
+  auto new_vtable_id = resolver.local_vtables().Add(
+      {{.class_id = class_id,
+        .virtual_functions_id = GetLocalCanonicalInstBlockId(
+            resolver, import_vtable.virtual_functions_id, virtual_functions)}});
+
+  return ResolveAsDeduplicated<SemIR::VtablePtr>(
+      resolver, {.type_id = GetPointerType(resolver.local_context(),
+                                           SemIR::VtableType::TypeInstId),
+                 .vtable_id = new_vtable_id,
+                 .specific_id = SemIR::SpecificId::None});
+}
+
 static auto TryResolveTypedInst(ImportRefResolver& resolver,
                                 SemIR::FunctionType inst) -> ResolveResult {
   CARBON_CHECK(inst.type_id == SemIR::TypeType::TypeId);
@@ -2852,24 +2914,6 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
        .pattern_id = pattern_id});
 }
 
-static auto TryResolveTypedInst(ImportRefResolver& resolver, SemIR::Vtable inst)
-    -> ResolveResult {
-  auto type_const_id = GetLocalConstantId(resolver, inst.type_id);
-  auto virtual_functions =
-      GetLocalInstBlockContents(resolver, inst.virtual_functions_id);
-  if (resolver.HasNewWork()) {
-    return ResolveResult::Retry();
-  }
-
-  auto virtual_functions_id = GetLocalCanonicalInstBlockId(
-      resolver, inst.virtual_functions_id, virtual_functions);
-  return ResolveAsDeduplicated<SemIR::Vtable>(
-      resolver,
-      {.type_id = resolver.local_context().types().GetTypeIdForTypeConstantId(
-           type_const_id),
-       .virtual_functions_id = virtual_functions_id});
-}
-
 // Tries to resolve the InstId, returning a canonical constant when ready, or
 // `None` if more has been added to the stack. This is the same as
 // TryResolveInst, except that it may resolve symbolic constants as canonical
@@ -3058,8 +3102,8 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver,
     case CARBON_KIND(SemIR::VarStorage inst): {
       return TryResolveTypedInst(resolver, inst, inst_id);
     }
-    case CARBON_KIND(SemIR::Vtable inst): {
-      return TryResolveTypedInst(resolver, inst);
+    case CARBON_KIND(SemIR::VtablePtr inst): {
+      return TryResolveTypedInst(resolver, inst, const_id);
     }
     default: {
       auto inst_constant_id = resolver.import_constant_values().Get(inst_id);

Разница между файлами не показана из-за своего большого размера
+ 241 - 102
toolchain/check/testdata/class/virtual_modifiers.carbon


+ 3 - 0
toolchain/check/type.h

@@ -46,6 +46,9 @@ auto GetClassType(Context& context, SemIR::ClassId class_id,
 auto GetFunctionType(Context& context, SemIR::FunctionId fn_id,
                      SemIR::SpecificId specific_id) -> SemIR::TypeId;
 
+auto GetVtableType(Context& context, SemIR::VtableId vtable_id)
+    -> SemIR::TypeId;
+
 // Gets the type of an associated function with the `Self` parameter bound to
 // a particular value. The returned type will be complete.
 auto GetFunctionTypeWithSelfType(Context& context,

+ 9 - 0
toolchain/lower/constant.cpp

@@ -59,6 +59,10 @@ class ConstantContext {
     return file_context_->GetFunction(function_id);
   }
 
+  auto GetVtable(SemIR::VtableId vtable_id) -> llvm::GlobalVariable* {
+    return file_context_->GetVtable(vtable_id);
+  }
+
   // Returns a lowered type for the given type_id.
   auto GetType(SemIR::TypeId type_id) const -> llvm::Type* {
     return file_context_->GetType(type_id);
@@ -151,6 +155,11 @@ static auto EmitAsConstant(ConstantContext& context, SemIR::AddrOf inst)
   return context.GetConstant(inst.lvalue_id);
 }
 
+static auto EmitAsConstant(ConstantContext& context, SemIR::VtablePtr inst)
+    -> llvm::Constant* {
+  return context.GetVtable(inst.vtable_id);
+}
+
 static auto EmitAsConstant(ConstantContext& context,
                            SemIR::AnyAggregateAccess inst) -> llvm::Constant* {
   auto* aggr_addr = context.GetConstant(inst.aggregate_id);

+ 12 - 20
toolchain/lower/file_context.cpp

@@ -82,18 +82,20 @@ auto FileContext::PrepareToLower() -> void {
     functions_.Set(id, BuildFunctionDecl(id));
   }
 
+  // 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);
+    }
+  }
+
   // Lower constants.
   LowerConstants(*this, constants_);
 }
 
 // TODO: Move this to lower.cpp.
 auto FileContext::LowerDefinitions() -> void {
-  for (const auto& class_info : sem_ir_->classes().values()) {
-    if (auto* llvm_vtable = BuildVtable(class_info)) {
-      global_variables_.Insert(class_info.vtable_id, llvm_vtable);
-    }
-  }
-
   // Lower global variable definitions.
   // TODO: Storing both a `constants_` array and a separate `global_variables_`
   // map is redundant.
@@ -920,17 +922,13 @@ auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> Context::LocForDI {
       GetAbsoluteNodeId(sem_ir_, SemIR::LocId(inst_id)).back());
 }
 
-auto FileContext::BuildVtable(const SemIR::Class& class_info)
+auto FileContext::BuildVtable(const SemIR::Vtable& vtable)
     -> llvm::GlobalVariable* {
-  // Bail out if this class is not dynamic (this will account for classes that
-  // are declared-and-not-defined (including extern declarations) as well).
-  if (!class_info.is_dynamic) {
-    return nullptr;
-  }
+  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 != SemIR::GenericId::None) {
+  if (class_info.generic_id.has_value()) {
     return nullptr;
   }
 
@@ -953,14 +951,8 @@ auto FileContext::BuildVtable(const SemIR::Class& class_info)
     return gv;
   }
 
-  auto canonical_vtable_id =
-      sem_ir().constant_values().GetConstantInstId(class_info.vtable_id);
-
   auto vtable_inst_block =
-      sem_ir().inst_blocks().Get(sem_ir()
-                                     .insts()
-                                     .GetAs<SemIR::Vtable>(canonical_vtable_id)
-                                     .virtual_functions_id);
+      sem_ir().inst_blocks().Get(vtable.virtual_functions_id);
 
   auto* entry_type = llvm::IntegerType::getInt32Ty(llvm_context());
   auto* table_type = llvm::ArrayType::get(entry_type, vtable_inst_block.size());

+ 8 - 2
toolchain/lower/file_context.h

@@ -82,13 +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];
+  }
+
   // Returns the empty LLVM struct type used to represent the type `type`.
   auto GetTypeType() -> llvm::StructType* { return context().GetTypeType(); }
 
   auto context() -> Context& { return *context_; }
   auto llvm_context() -> llvm::LLVMContext& { return context().llvm_context(); }
   auto llvm_module() -> llvm::Module& { return context().llvm_module(); }
-  auto sem_ir() -> const SemIR::File& { return *sem_ir_; }
+  auto sem_ir() const -> const SemIR::File& { return *sem_ir_; }
   auto cpp_ast() -> const clang::ASTUnit* { return sem_ir().cpp_ast(); }
   auto inst_namer() -> const SemIR::InstNamer* { return inst_namer_; }
   auto global_variables() -> const Map<SemIR::InstId, llvm::GlobalVariable*>& {
@@ -176,7 +180,7 @@ class FileContext {
   // the caller.
   auto BuildType(SemIR::InstId inst_id) -> llvm::Type*;
 
-  auto BuildVtable(const SemIR::Class& class_info) -> llvm::GlobalVariable*;
+  auto BuildVtable(const SemIR::Vtable& vtable) -> llvm::GlobalVariable*;
 
   // Records a specific that was lowered for a generic. These are added one
   // by one while lowering their definitions.
@@ -234,6 +238,8 @@ class FileContext {
       lowered_specifics_;
 
   SpecificCoalescer coalescer_;
+
+  Map<SemIR::VtableId, llvm::GlobalVariable*> vtables_;
 };
 
 }  // namespace Carbon::Lower

+ 4 - 0
toolchain/lower/function_context.h

@@ -268,6 +268,10 @@ class FunctionContext {
     return format_string;
   }
 
+  auto GetVtable(SemIR::VtableId vtable_id) const -> llvm::GlobalVariable* {
+    return file_context_->GetVtable(vtable_id);
+  }
+
  private:
   // Custom instruction inserter for our IR builder. Automatically names
   // instructions.

+ 3 - 3
toolchain/lower/handle.cpp

@@ -300,9 +300,9 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
                    context.CreateAlloca(context.GetTypeOfInst(inst_id)));
 }
 
-auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
-                SemIR::VtablePtr inst) -> void {
-  context.SetLocal(inst_id, context.GetValue(inst.vtable_id));
+auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
+                SemIR::VtablePtr /*inst*/) -> void {
+  CARBON_FATAL("`VtablePtr` insts should always be constant");
 }
 
 }  // namespace Carbon::Lower

+ 107 - 20
toolchain/lower/testdata/class/virtual.carbon

@@ -89,7 +89,25 @@ fn Use(b: Base) {
   b.F();
 }
 
-// --- generic.carbon
+// --- call_impl.carbon
+
+library "[[@TEST_NAME]]";
+
+base class Base {
+  virtual fn F[self: Self]();
+}
+
+class Derived {
+  extend base: Base;
+  impl fn F[self: Self]();
+}
+
+fn Use() {
+  var v : Derived = {.base = {}};
+  v.F();
+}
+
+// --- generic_noop.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -132,6 +150,8 @@ base class Base(T:! type) {
 // CHECK:STDOUT: @"_CIntermediate.Classes.$vtable" = external unnamed_addr constant ptr
 // CHECK:STDOUT: @"_CDerived.Classes.$vtable" = external unnamed_addr constant ptr
 // CHECK:STDOUT: @Base.val.loc7_3 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @Intermediate.val.ec2.loc8_3 = internal constant { ptr, {} } { ptr @"_CIntermediate.Classes.$vtable", {} zeroinitializer }
+// CHECK:STDOUT: @Derived.val.loc9_3 = internal constant { { ptr, {} } } { { ptr, {} } { ptr @"_CDerived.Classes.$vtable", {} zeroinitializer } }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CCreate.Create() !dbg !4 {
 // CHECK:STDOUT: entry:
@@ -143,39 +163,45 @@ base class Base(T:! type) {
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %b.var, ptr align 1 @Base.val.loc7_3, i64 0, i1 false), !dbg !7
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %i.var), !dbg !8
 // CHECK:STDOUT:   %.loc8_44.2.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %i.var, i32 0, i32 0, !dbg !11
-// CHECK:STDOUT:   store ptr @"_CIntermediate.Classes.$vtable", ptr %.loc8_44.2.vptr, align 8, !dbg !11
-// CHECK:STDOUT:   %.loc8_44.5.base = getelementptr inbounds nuw { ptr, {} }, ptr %i.var, i32 0, i32 1, !dbg !11
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc8_44.5.base, ptr align 1 @Base.val.loc7_3, i64 0, i1 false), !dbg !11
+// CHECK:STDOUT:   %.loc8_44.4.base = getelementptr inbounds nuw { ptr, {} }, ptr %i.var, i32 0, i32 1, !dbg !11
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %i.var, ptr align 8 @Intermediate.val.ec2.loc8_3, i64 8, i1 false), !dbg !8
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %d.var), !dbg !9
 // CHECK:STDOUT:   %.loc9_49.2.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %d.var, i32 0, i32 0, !dbg !12
 // CHECK:STDOUT:   %.loc9_48.2.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 0, !dbg !13
-// CHECK:STDOUT:   store ptr @"_CDerived.Classes.$vtable", ptr %.loc9_48.2.vptr, align 8, !dbg !13
-// CHECK:STDOUT:   %.loc9_48.5.base = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 1, !dbg !13
-// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc9_48.5.base, ptr align 1 @Base.val.loc7_3, i64 0, i1 false), !dbg !13
+// CHECK:STDOUT:   %.loc9_48.4.base = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 1, !dbg !13
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %d.var, ptr align 8 @Derived.val.loc9_3, i64 8, i1 false), !dbg !9
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %d2.var), !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !14
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFn.Derived.Classes(ptr)
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !15 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_CFn.Intermediate.Classes(ptr %v), !dbg !16
+// CHECK:STDOUT:   %Fn.call.vtable = load ptr, ptr %v, align 8, !dbg !16
+// CHECK:STDOUT:   %Fn.call = call ptr @llvm.load.relative.i32(ptr %Fn.call.vtable, i32 0), !dbg !16
+// CHECK:STDOUT:   call void %Fn.call(ptr %v), !dbg !16
 // CHECK:STDOUT:   ret void, !dbg !17
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes(ptr)
-// 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: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+// CHECK:STDOUT: declare ptr @llvm.load.relative.i32(ptr, i32) #2
+// 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, { 2, 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: attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
@@ -202,6 +228,7 @@ base class Base(T:! type) {
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: @"_CBase.MemberInit.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CFn.Base.MemberInit to i64), i64 ptrtoint (ptr @"_CBase.MemberInit.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @Base.val.loc13_3 = internal constant { ptr, i32 } { ptr @"_CBase.MemberInit.$vtable", i32 3 }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CFn.Base.MemberInit(ptr %self) !dbg !4 {
 // CHECK:STDOUT: entry:
@@ -217,27 +244,30 @@ base class Base(T:! type) {
 // CHECK:STDOUT:   store i32 3, ptr %i.var, align 4, !dbg !9
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %v.var), !dbg !10
 // CHECK:STDOUT:   %.loc11_24.2.vptr = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 0, !dbg !12
-// CHECK:STDOUT:   store ptr @"_CBase.MemberInit.$vtable", ptr %.loc11_24.2.vptr, align 8, !dbg !12
 // CHECK:STDOUT:   %.loc11_23 = load i32, ptr %i.var, align 4, !dbg !13
-// CHECK:STDOUT:   %.loc11_24.5.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !12
-// CHECK:STDOUT:   store i32 %.loc11_23, ptr %.loc11_24.5.m, align 4, !dbg !12
+// CHECK:STDOUT:   %.loc11_24.4.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !12
+// CHECK:STDOUT:   store i32 %.loc11_23, ptr %.loc11_24.4.m, align 4, !dbg !12
+// CHECK:STDOUT:   store ptr @"_CBase.MemberInit.$vtable", ptr %.loc11_24.2.vptr, align 8, !dbg !12
 // CHECK:STDOUT:   %.loc12_4.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !14
 // CHECK:STDOUT:   store i32 5, ptr %.loc12_4.m, align 4, !dbg !14
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %u.var), !dbg !11
 // CHECK:STDOUT:   %.loc13_24.2.vptr = getelementptr inbounds nuw { ptr, i32 }, ptr %u.var, i32 0, i32 0, !dbg !15
-// CHECK:STDOUT:   store ptr @"_CBase.MemberInit.$vtable", ptr %.loc13_24.2.vptr, align 8, !dbg !15
-// CHECK:STDOUT:   %.loc13_24.6.m = getelementptr inbounds nuw { ptr, i32 }, ptr %u.var, i32 0, i32 1, !dbg !15
-// CHECK:STDOUT:   store i32 3, ptr %.loc13_24.6.m, align 4, !dbg !15
+// CHECK:STDOUT:   %.loc13_24.5.m = getelementptr inbounds nuw { ptr, i32 }, ptr %u.var, i32 0, i32 1, !dbg !15
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %u.var, ptr align 8 @Base.val.loc13_3, i64 16, i1 false), !dbg !11
 // CHECK:STDOUT:   ret void, !dbg !16
 // 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, { 2, 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}
@@ -264,6 +294,7 @@ base class Base(T:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
 // CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @Derived.val.loc13_3 = internal constant { { ptr } } { { ptr } { ptr @"_CDerived.Main.$vtable" } }
 // CHECK:STDOUT:
 // CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
 // CHECK:STDOUT:
@@ -273,14 +304,18 @@ base class Base(T:! type) {
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %v.var), !dbg !7
 // CHECK:STDOUT:   %.loc13_32.2.base = getelementptr inbounds nuw { { ptr } }, ptr %v.var, i32 0, i32 0, !dbg !8
 // CHECK:STDOUT:   %.loc13_31.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc13_32.2.base, i32 0, i32 0, !dbg !9
-// CHECK:STDOUT:   store ptr @"_CDerived.Main.$vtable", ptr %.loc13_31.2.vptr, align 8, !dbg !9
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %v.var, ptr align 8 @Derived.val.loc13_3, i64 8, i1 false), !dbg !7
 // 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: ; 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: 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}
@@ -328,8 +363,60 @@ base class Base(T:! type) {
 // CHECK:STDOUT: !6 = !{}
 // CHECK:STDOUT: !7 = !DILocation(line: 9, column: 3, scope: !4)
 // CHECK:STDOUT: !8 = !DILocation(line: 8, column: 1, scope: !4)
-// CHECK:STDOUT: ; ModuleID = 'generic.carbon'
-// CHECK:STDOUT: source_filename = "generic.carbon"
+// CHECK:STDOUT: ; ModuleID = 'call_impl.carbon'
+// CHECK:STDOUT: source_filename = "call_impl.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Derived.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @Derived.val.loc14_3 = internal constant { { ptr } } { { ptr } { ptr @"_CDerived.Main.$vtable" } }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Derived.Main(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CUse.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:   %.loc14_32.2.base = getelementptr inbounds nuw { { ptr } }, ptr %v.var, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   %.loc14_31.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc14_32.2.base, i32 0, i32 0, !dbg !9
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 8 %v.var, ptr align 8 @Derived.val.loc14_3, i64 8, i1 false), !dbg !7
+// CHECK:STDOUT:   %F.call.vtable = load ptr, ptr %v.var, align 8, !dbg !10
+// CHECK:STDOUT:   %F.call = call ptr @llvm.load.relative.i32(ptr %F.call.vtable, i32 0), !dbg !10
+// CHECK:STDOUT:   call void %F.call(ptr %v.var), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// 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: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+// CHECK:STDOUT: declare ptr @llvm.load.relative.i32(ptr, i32) #2
+// 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: attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
+// 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: "call_impl.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 14, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 14, column: 21, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 14, column: 30, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 15, column: 3, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 13, column: 1, scope: !4)
+// CHECK:STDOUT: ; ModuleID = 'generic_noop.carbon'
+// CHECK:STDOUT: source_filename = "generic_noop.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
@@ -337,4 +424,4 @@ base class Base(T:! type) {
 // 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.carbon", directory: "")
+// CHECK:STDOUT: !3 = !DIFile(filename: "generic_noop.carbon", directory: "")

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -113,6 +113,7 @@ cc_library(
         "type.h",
         "type_info.h",
         "type_iterator.h",
+        "vtable.h",
     ],
     textual_hdrs = [
         "builtin_function_kind.def",

+ 2 - 2
toolchain/sem_ir/class.h

@@ -62,7 +62,7 @@ struct ClassFields {
 
   // The virtual function table. `None` if the class has no (direct or
   // inherited) virtual functions.
-  InstId vtable_id = InstId::None;
+  InstId vtable_ptr_id = InstId::None;
 
   auto PrintClassFields(llvm::raw_ostream& out) const -> void {
     out << "self_type_id: " << self_type_id << ", inheritance_kind: ";
@@ -81,7 +81,7 @@ struct ClassFields {
         << ", body_block_id: " << body_block_id << ", adapt_id: " << adapt_id
         << ", base_id: " << base_id
         << ", complete_type_witness_id: " << complete_type_witness_id
-        << ", vtable_id: " << vtable_id << "}";
+        << ", vtable_ptr_id: " << vtable_ptr_id << "}";
   }
 };
 

+ 0 - 1
toolchain/sem_ir/expr_info.cpp

@@ -45,7 +45,6 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case TuplePattern::Kind:
       case ValueParamPattern::Kind:
       case VarPattern::Kind:
-      case Vtable::Kind:
         return ExprCategory::NotExpr;
 
       case ImportRefUnloaded::Kind:

+ 5 - 0
toolchain/sem_ir/file.h

@@ -35,6 +35,7 @@
 #include "toolchain/sem_ir/struct_type_field.h"
 #include "toolchain/sem_ir/type.h"
 #include "toolchain/sem_ir/type_info.h"
+#include "toolchain/sem_ir/vtable.h"
 
 namespace Carbon::SemIR {
 
@@ -224,6 +225,8 @@ class File : public Printable<File> {
   auto types() const -> const TypeStore& { return types_; }
   auto insts() -> InstStore& { return insts_; }
   auto insts() const -> const InstStore& { return insts_; }
+  auto vtables() -> ValueStore<VtableId>& { return vtables_; }
+  auto vtables() const -> const ValueStore<VtableId>& { return vtables_; }
   auto constant_values() -> ConstantValueStore& { return constant_values_; }
   auto constant_values() const -> const ConstantValueStore& {
     return constant_values_;
@@ -344,6 +347,8 @@ class File : public Printable<File> {
   // instructions.
   InstStore insts_ = InstStore(this);
 
+  ValueStore<VtableId> vtables_;
+
   // Storage for name scopes.
   NameScopeStore name_scopes_ = NameScopeStore(this);
 

+ 29 - 0
toolchain/sem_ir/formatter.cpp

@@ -25,6 +25,7 @@
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/name_scope.h"
 #include "toolchain/sem_ir/typed_insts.h"
+#include "toolchain/sem_ir/vtable.h"
 
 // TODO: Consider addressing recursion here, although it's not critical because
 // the formatter isn't required to work on arbitrary code. Still, it may help
@@ -90,6 +91,10 @@ auto Formatter::Format() -> void {
     FormatClass(id);
   }
 
+  for (auto [id, _] : sem_ir_->vtables().enumerate()) {
+    FormatVtable(id);
+  }
+
   for (auto [id, _] : sem_ir_->functions().enumerate()) {
     FormatFunction(id);
   }
@@ -342,6 +347,12 @@ auto Formatter::FormatClass(ClassId id) -> void {
     out_ << "complete_type_witness = ";
     FormatName(class_info.complete_type_witness_id);
     out_ << "\n";
+    if (class_info.vtable_ptr_id.has_value()) {
+      Indent();
+      out_ << "vtable_ptr = ";
+      FormatName(class_info.vtable_ptr_id);
+      out_ << "\n";
+    }
 
     FormatNameScope(class_info.scope_id, "!members:\n");
     CloseBrace();
@@ -353,6 +364,24 @@ auto Formatter::FormatClass(ClassId id) -> void {
   FormatEntityEnd(class_info.generic_id);
 }
 
+auto Formatter::FormatVtable(VtableId id) -> void {
+  const Vtable& vtable_info = sem_ir_->vtables().Get(id);
+  out_ << '\n';
+  Indent();
+  out_ << "vtable ";
+  FormatName(id);
+  out_ << ' ';
+  OpenBrace();
+  for (auto function_id :
+       sem_ir_->inst_blocks().Get(vtable_info.virtual_functions_id)) {
+    Indent();
+    FormatArg(function_id);
+    out_ << '\n';
+  }
+  CloseBrace();
+  out_ << '\n';
+}
+
 auto Formatter::FormatInterface(InterfaceId id) -> void {
   const Interface& interface_info = sem_ir_->interfaces().Get(id);
   if (!ShouldFormatEntity(interface_info)) {

+ 3 - 0
toolchain/sem_ir/formatter.h

@@ -150,6 +150,9 @@ class Formatter {
   // Formats a full class.
   auto FormatClass(ClassId id) -> void;
 
+  // Formats a full vtable.
+  auto FormatVtable(VtableId id) -> void;
+
   // Formats a full interface.
   auto FormatInterface(InterfaceId id) -> void;
 

+ 2 - 1
toolchain/sem_ir/id_kind.h

@@ -60,7 +60,8 @@ using IdKind = TypeEnum<
     SpecificId,
     SpecificInterfaceId,
     StructTypeFieldsId,
-    TypeInstId>;
+    TypeInstId,
+    VtableId>;
 // clang-format on
 
 }  // namespace Carbon::SemIR

+ 8 - 0
toolchain/sem_ir/ids.h

@@ -45,6 +45,7 @@ struct Impl;
 struct Interface;
 struct StructTypeField;
 struct TypeInfo;
+struct Vtable;
 
 // The ID of an instruction.
 struct InstId : public IdBase<InstId> {
@@ -296,6 +297,13 @@ struct ClassId : public IdBase<ClassId> {
   using IdBase::IdBase;
 };
 
+struct VtableId : public IdBase<VtableId> {
+  static constexpr llvm::StringLiteral Label = "vtable";
+  using ValueType = Vtable;
+
+  using IdBase::IdBase;
+};
+
 // The ID of an interface.
 struct InterfaceId : public IdBase<InterfaceId> {
   static constexpr llvm::StringLiteral Label = "interface";

+ 8 - 0
toolchain/sem_ir/inst_fingerprinter.cpp

@@ -179,6 +179,14 @@ struct Worklist {
     AddEntity(sem_ir->classes().Get(class_id));
   }
 
+  auto Add(VtableId vtable_id) -> void {
+    const auto& vtable = sem_ir->vtables().Get(vtable_id);
+    if (vtable.class_id.has_value()) {
+      Add(vtable.class_id);
+    }
+    Add(vtable.virtual_functions_id);
+  }
+
   auto Add(InterfaceId interface_id) -> void {
     AddEntity(sem_ir->interfaces().Get(interface_id));
   }

+ 0 - 1
toolchain/sem_ir/inst_kind.def

@@ -142,7 +142,6 @@ CARBON_SEM_IR_INST_KIND(VarPattern)
 CARBON_SEM_IR_INST_KIND(VarStorage)
 CARBON_SEM_IR_INST_KIND(VtableType)
 CARBON_SEM_IR_INST_KIND(VtablePtr)
-CARBON_SEM_IR_INST_KIND(Vtable)
 CARBON_SEM_IR_INST_KIND(WhereExpr)
 CARBON_SEM_IR_INST_KIND(WitnessType)
 

+ 26 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -129,6 +129,19 @@ InstNamer::InstNamer(const File* sem_ir) : sem_ir_(sem_ir) {
     CollectNamesInGeneric(class_scope, class_info.generic_id);
   }
 
+  // Build each vtable scope.
+  for (auto [vtable_id, vtable_info] : sem_ir->vtables().enumerate()) {
+    auto vtable_scope = GetScopeFor(vtable_id);
+    // TODO: Provide a location for the vtable for use as a disambiguator.
+    auto vtable_loc = Parse::NodeId::None;
+    auto class_info = sem_ir->classes().Get(vtable_info.class_id);
+    GetScopeInfo(vtable_scope).name = globals_.AllocateName(
+        *this, vtable_loc,
+        sem_ir->names().GetIRBaseName(class_info.name_id).str() + ".vtable");
+    // TODO: Add support for generic vtables here and elsewhere.
+    // CollectNamesInGeneric(vtable_scope, vtable_info.generic_id);
+  }
+
   // Build each interface scope.
   for (auto [interface_id, interface_info] : sem_ir->interfaces().enumerate()) {
     auto interface_scope = GetScopeFor(interface_id);
@@ -184,6 +197,9 @@ auto InstNamer::GetScopeIdOffset(ScopeIdTypeEnum id_enum) const -> int {
       offset += sem_ir_->classes().size();
       [[fallthrough]];
     case ScopeIdTypeEnum::For<ClassId>:
+      offset += sem_ir_->vtables().size();
+      [[fallthrough]];
+    case ScopeIdTypeEnum::For<VtableId>:
       offset += sem_ir_->functions().size();
       [[fallthrough]];
     case ScopeIdTypeEnum::For<FunctionId>:
@@ -705,6 +721,16 @@ auto InstNamer::NamingContext::NameInst() -> void {
       AddInstName("complete_type");
       return;
     }
+    case CARBON_KIND(VtablePtr inst): {
+      const auto& vtable = sem_ir().vtables().Get(inst.vtable_id);
+      if (inst_namer_->GetScopeFor(vtable.class_id) == scope_id_) {
+        AddInstName("vtable_ptr");
+      } else {
+        const auto& class_info = sem_ir().classes().Get(vtable.class_id);
+        AddInstNameId(class_info.name_id, ".vtable_ptr");
+      }
+      return;
+    }
     case ConstType::Kind: {
       // TODO: Can we figure out the name of the type argument?
       AddInstName("const");

+ 3 - 2
toolchain/sem_ir/inst_namer.h

@@ -32,8 +32,9 @@ class InstNamer {
   static_assert(sizeof(ScopeId) == sizeof(AnyIdBase));
 
   // Entities whose scopes get entries from `ScopeId`.
-  using ScopeIdTypeEnum = TypeEnum<AssociatedConstantId, ClassId, FunctionId,
-                                   ImplId, InterfaceId, SpecificInterfaceId>;
+  using ScopeIdTypeEnum =
+      TypeEnum<AssociatedConstantId, ClassId, VtableId, FunctionId, ImplId,
+               InterfaceId, SpecificInterfaceId>;
 
   // Construct the instruction namer, and assign names to all instructions in
   // the provided file.

+ 10 - 18
toolchain/sem_ir/typed_insts.h

@@ -1762,24 +1762,6 @@ struct VarStorage {
   AbsoluteInstId pattern_id;
 };
 
-// Definition of ABI-neutral vtable information for a dynamic class.
-struct Vtable {
-  static constexpr auto Kind = InstKind::Vtable.Define<Parse::NodeId>(
-      {.ir_name = "vtable",
-       .constant_kind = InstConstantKind::Always,
-       .is_lowered = false});
-  TypeId type_id;
-  InstBlockId virtual_functions_id;
-};
-
-// Initializer for virtual function table pointers in object initialization.
-struct VtablePtr {
-  static constexpr auto Kind = InstKind::VtablePtr.Define<Parse::NodeId>(
-      {.ir_name = "vtable_ptr", .constant_kind = InstConstantKind::Never});
-  TypeId type_id;
-  InstId vtable_id;
-};
-
 // The type of virtual function tables.
 struct VtableType {
   static constexpr auto Kind = InstKind::VtableType.Define<Parse::NoneNodeId>(
@@ -1793,6 +1775,16 @@ struct VtableType {
   TypeId type_id;
 };
 
+// Initializer for virtual function table pointers in object initialization.
+struct VtablePtr {
+  static constexpr auto Kind = InstKind::VtablePtr.Define<Parse::NodeId>(
+      {.ir_name = "vtable_ptr",
+       .constant_kind = InstConstantKind::WheneverPossible});
+  TypeId type_id;
+  VtableId vtable_id;
+  SpecificId specific_id;
+};
+
 // An `expr where requirements` expression.
 struct WhereExpr {
   static constexpr auto Kind = InstKind::WhereExpr.Define<Parse::WhereExprId>(

+ 33 - 0
toolchain/sem_ir/vtable.h

@@ -0,0 +1,33 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_VTABLE_H_
+#define CARBON_TOOLCHAIN_SEM_IR_VTABLE_H_
+
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+// Vtable-specific fields.
+struct VtableFields {
+  // The class this is the vtable for.
+  ClassId class_id;
+
+  // A block containing all the virtual functions in this class and
+  // non-overriden functions in base classes, forming the complete vtable for
+  // the class.
+  InstBlockId virtual_functions_id;
+};
+
+struct Vtable : public VtableFields, public Printable<Vtable> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{class: " << class_id << ", {";
+    virtual_functions_id.Print(out);
+    out << "}}";
+  }
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_VTABLE_H_

Некоторые файлы не были показаны из-за большого количества измененных файлов