Procházet zdrojové kódy

Initialize vptrs to point to vtables (#5244)

Adds a mapping to keep track of vtable LLVM IR decls/defs for use.
Adds the vtable_id to the vtable_ptr initialize instruction for lookup.
Adds emission of vtable declarations for use outside the file that
defines the vtable. (this isn't done lazily, it's done for any imported
class - it could be done lazily & maybe eventually has to be lazy to
handle generics)
David Blaikie před 1 rokem
rodič
revize
8e7bb2f953

+ 27 - 21
toolchain/check/convert.cpp

@@ -156,7 +156,8 @@ static auto ConvertAggregateElement(
     llvm::ArrayRef<SemIR::InstId> src_literal_elems,
     ConversionTarget::Kind kind, SemIR::InstId target_id,
     SemIR::TypeId target_elem_type, PendingBlock* target_block,
-    size_t src_field_index, size_t target_field_index) -> SemIR::InstId {
+    size_t src_field_index, size_t target_field_index,
+    SemIR::InstId vtable_id = SemIR::InstId::None) -> SemIR::InstId {
   // Compute the location of the source element. This goes into the current code
   // block, not into the target block.
   // TODO: Ideally we would discard this instruction if it's unused.
@@ -179,7 +180,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);
+  return Convert(context, loc_id, src_elem_id, target, vtable_id);
 }
 
 // Performs a conversion from a tuple to an array type. This function only
@@ -372,12 +373,10 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
 
 // Common implementation for ConvertStructToStruct and ConvertStructToClass.
 template <typename TargetAccessInstT>
-static auto ConvertStructToStructOrClass(Context& context,
-                                         SemIR::StructType src_type,
-                                         SemIR::StructType dest_type,
-                                         SemIR::InstId value_id,
-                                         ConversionTarget target)
-    -> SemIR::InstId {
+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 {
   static_assert(std::is_same_v<SemIR::ClassElementAccess, TargetAccessInstT> ||
                 std::is_same_v<SemIR::StructAccess, TargetAccessInstT>);
   constexpr bool ToClass =
@@ -466,7 +465,8 @@ static auto ConvertStructToStructOrClass(Context& context,
                                               .base_id = target.init_id,
                                               .index = SemIR::ElementIndex(i)});
       auto vtable_ptr_id = AddInst<SemIR::VtablePtr>(
-          context, value_loc_id, {.type_id = dest_field.type_id});
+          context, value_loc_id,
+          {.type_id = dest_field.type_id, .vtable_id = dest_vtable_id});
       auto init_id =
           AddInst<SemIR::InitializeFrom>(context, value_loc_id,
                                          {.type_id = dest_field.type_id,
@@ -512,7 +512,8 @@ static auto ConvertStructToStructOrClass(Context& context,
         ConvertAggregateElement<SemIR::StructAccess, TargetAccessInstT>(
             context, value_loc_id, value_id, src_field.type_id, literal_elems,
             inner_kind, target.init_id, dest_field.type_id, target.init_block,
-            src_field_index, src_field_index + dest_vptr_offset);
+            src_field_index, src_field_index + dest_vptr_offset,
+            dest_vtable_id);
     if (init_id == SemIR::ErrorInst::SingletonInstId) {
       return SemIR::ErrorInst::SingletonInstId;
     }
@@ -554,10 +555,10 @@ static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
 // Performs a conversion from a struct to a class type. This function only
 // converts the type, and does not perform a final conversion to the requested
 // expression category.
-static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
-                                 SemIR::ClassType dest_type,
-                                 SemIR::InstId value_id,
-                                 ConversionTarget target) -> SemIR::InstId {
+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 {
   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);
@@ -580,7 +581,8 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
   }
 
   auto result_id = ConvertStructToStructOrClass<SemIR::ClassElementAccess>(
-      context, src_type, dest_struct_type, value_id, target);
+      context, src_type, dest_struct_type, value_id, target,
+      dest_vtable_id.has_value() ? dest_vtable_id : dest_class_info.vtable_id);
 
   if (need_temporary) {
     target_block.InsertHere();
@@ -778,9 +780,10 @@ static auto DiagnoseConversionFailureToConstraintValue(
   }
 }
 
-static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
-                                     SemIR::InstId value_id,
-                                     ConversionTarget target) -> SemIR::InstId {
+static auto PerformBuiltinConversion(
+    Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
+    ConversionTarget target, SemIR::InstId vtable_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();
@@ -952,7 +955,8 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
                .Get(target_class_type->class_id)
                .adapt_id.has_value()) {
         return ConvertStructToClass(context, *src_struct_type,
-                                    *target_class_type, value_id, target);
+                                    *target_class_type, value_id, target,
+                                    vtable_id);
       }
     }
 
@@ -1133,7 +1137,8 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
 }
 
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
-             ConversionTarget target) -> SemIR::InstId {
+             ConversionTarget target, SemIR::InstId vtable_id)
+    -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
   auto orig_expr_id = expr_id;
 
@@ -1197,7 +1202,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);
+  expr_id =
+      PerformBuiltinConversion(context, loc_id, expr_id, target, vtable_id);
   if (expr_id == SemIR::ErrorInst::SingletonInstId) {
     return expr_id;
   }

+ 7 - 1
toolchain/check/convert.h

@@ -55,8 +55,14 @@ struct ConversionTarget {
 };
 
 // Convert a value to another type and expression category.
+// TODO: The `vtable_id` parameter is too much of a special case here, and
+// should be removed - once partial classes are implemented, the vtable pointer
+// initialization will be done not in this conversion, but during initialization
+// of the object of non-partial class time from the object of partial class
+// type.
 auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
-             ConversionTarget target) -> SemIR::InstId;
+             ConversionTarget target,
+             SemIR::InstId vtable_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

+ 112 - 6
toolchain/check/testdata/class/virtual_modifiers.carbon

@@ -33,6 +33,10 @@ class Derived {
   impl fn H[self: Self]();
 }
 
+fn Use() {
+  var d: Derived = {.base = {}};
+}
+
 // --- todo_fail_later_base.carbon
 
 library "[[@TEST_NAME]]";
@@ -85,6 +89,12 @@ class C {
   impl fn F[self: Self]();
 }
 
+fn Use() {
+  var b1: B1 = {};
+  var b2: B2 = {.base = {}};
+  var c: C = {.base = {.base = {}}};
+}
+
 // --- fail_modifiers.carbon
 
 library "[[@TEST_NAME]]";
@@ -384,8 +394,12 @@ class T2 {
 // CHECK:STDOUT:   %H.type.dba: type = fn_type @H.1 [concrete]
 // CHECK:STDOUT:   %H.bce: %H.type.dba = struct_value () [concrete]
 // CHECK:STDOUT:   %.dce: <vtable> = vtable (%H.bce) [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %Base} [concrete]
-// CHECK:STDOUT:   %complete_type.0e2: <witness> = complete_type_witness %struct_type.base [concrete]
+// CHECK:STDOUT:   %struct_type.base.96c: type = struct_type {.base: %Base} [concrete]
+// CHECK:STDOUT:   %complete_type.0e2: <witness> = complete_type_witness %struct_type.base.96c [concrete]
+// CHECK:STDOUT:   %Use.type: type = fn_type @Use [concrete]
+// CHECK:STDOUT:   %Use: %Use.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -408,10 +422,12 @@ class T2 {
 // CHECK:STDOUT:     .Core = imports.%Core
 // CHECK:STDOUT:     .Modifiers = imports.%Modifiers
 // CHECK:STDOUT:     .Derived = %Derived.decl
+// CHECK:STDOUT:     .Use = %Use.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %Modifiers.import = import Modifiers
 // CHECK:STDOUT:   %Derived.decl: type = class_decl @Derived [concrete = constants.%Derived] {} {}
+// CHECK:STDOUT:   %Use.decl: %Use.type = fn_decl @Use [concrete = constants.%Use] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
@@ -427,7 +443,7 @@ class T2 {
 // CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc9: <vtable> = vtable (%H.decl) [concrete = constants.%.dce]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.0e2]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.96c [concrete = constants.%complete_type.0e2]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -450,6 +466,29 @@ class T2 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @H.2[%self.param_patt: %Base]() [from "modifiers.carbon"];
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %d.patt: %Derived = binding_pattern d
+// CHECK:STDOUT:     %.loc12_3.1: %Derived = var_pattern %d.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %d.var: ref %Derived = var d
+// CHECK:STDOUT:   %.loc12_30.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc12_31.1: %struct_type.base.f5e = struct_literal (%.loc12_30.1)
+// CHECK:STDOUT:   %.loc12_31.2: ref %Base = class_element_access %d.var, element0
+// CHECK:STDOUT:   %.loc12_30.2: ref %ptr.454 = class_element_access %.loc12_31.2, element0
+// CHECK:STDOUT:   %.loc12_30.3: ref %ptr.454 = vtable_ptr @Derived.%.loc9
+// CHECK:STDOUT:   %.loc12_30.4: init %ptr.454 = initialize_from %.loc12_30.3 to %.loc12_30.2
+// CHECK:STDOUT:   %.loc12_30.5: init %Base = class_init (%.loc12_30.4), %.loc12_31.2
+// CHECK:STDOUT:   %.loc12_31.3: init %Base = converted %.loc12_30.1, %.loc12_30.5
+// CHECK:STDOUT:   %.loc12_31.4: init %Derived = class_init (%.loc12_31.3), %d.var
+// CHECK:STDOUT:   %.loc12_3.2: init %Derived = converted %.loc12_31.1, %.loc12_31.4
+// CHECK:STDOUT:   assign %d.var, %.loc12_3.2
+// CHECK:STDOUT:   %Derived.ref: type = name_ref Derived, file.%Derived.decl [concrete = constants.%Derived]
+// CHECK:STDOUT:   %d: ref %Derived = bind_name d, %d.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- todo_fail_later_base.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -552,6 +591,7 @@ class T2 {
 // CHECK:STDOUT:     import Modifiers//default
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
+// CHECK:STDOUT:   %Modifiers.import_ref.ace = import_ref Modifiers//default, loc6_1, unloaded
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type]
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst17 [no loc], unloaded
 // CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_29, unloaded
@@ -585,7 +625,7 @@ class T2 {
 // CHECK:STDOUT:   %v.var: ref %Base = var v
 // CHECK:STDOUT:   %.loc7_28.1: %empty_struct_type = struct_literal ()
 // CHECK:STDOUT:   %.loc7_28.2: ref %ptr.454 = class_element_access %v.var, element0
-// CHECK:STDOUT:   %.loc7_28.3: ref %ptr.454 = vtable_ptr
+// CHECK:STDOUT:   %.loc7_28.3: ref %ptr.454 = vtable_ptr imports.%Modifiers.import_ref.ace
 // CHECK:STDOUT:   %.loc7_28.4: init %ptr.454 = initialize_from %.loc7_28.3 to %.loc7_28.2
 // CHECK:STDOUT:   %.loc7_28.5: init %Base = class_init (%.loc7_28.4), %v.var
 // CHECK:STDOUT:   %.loc7_3.2: init %Base = converted %.loc7_28.1, %.loc7_28.5
@@ -704,6 +744,11 @@ class T2 {
 // CHECK:STDOUT:   %.5f6: <vtable> = vtable (%F.437) [concrete]
 // CHECK:STDOUT:   %struct_type.base.421: type = struct_type {.base: %B2} [concrete]
 // CHECK:STDOUT:   %complete_type.066: <witness> = complete_type_witness %struct_type.base.421 [concrete]
+// CHECK:STDOUT:   %Use.type: type = fn_type @Use [concrete]
+// CHECK:STDOUT:   %Use: %Use.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %struct_type.base.f5e: type = struct_type {.base: %empty_struct_type} [concrete]
+// CHECK:STDOUT:   %struct_type.base.a0c: type = struct_type {.base: %struct_type.base.f5e} [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -719,11 +764,13 @@ class T2 {
 // CHECK:STDOUT:     .B1 = %B1.decl
 // CHECK:STDOUT:     .B2 = %B2.decl
 // CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .Use = %Use.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %B1.decl: type = class_decl @B1 [concrete = constants.%B1] {} {}
 // CHECK:STDOUT:   %B2.decl: type = class_decl @B2 [concrete = constants.%B2] {} {}
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %Use.decl: %Use.type = fn_decl @Use [concrete = constants.%Use] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @B1 {
@@ -796,6 +843,65 @@ class T2 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl fn @F.3[%self.param_patt: %C]();
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b1.patt: %B1 = binding_pattern b1
+// CHECK:STDOUT:     %.loc19_3.1: %B1 = var_pattern %b1.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b1.var: ref %B1 = var b1
+// CHECK:STDOUT:   %.loc19_17.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc19_17.2: ref %ptr.454 = class_element_access %b1.var, element0
+// CHECK:STDOUT:   %.loc19_17.3: ref %ptr.454 = vtable_ptr @B1.%.loc6
+// CHECK:STDOUT:   %.loc19_17.4: init %ptr.454 = initialize_from %.loc19_17.3 to %.loc19_17.2
+// CHECK:STDOUT:   %.loc19_17.5: init %B1 = class_init (%.loc19_17.4), %b1.var
+// CHECK:STDOUT:   %.loc19_3.2: init %B1 = converted %.loc19_17.1, %.loc19_17.5
+// CHECK:STDOUT:   assign %b1.var, %.loc19_3.2
+// CHECK:STDOUT:   %B1.ref: type = name_ref B1, file.%B1.decl [concrete = constants.%B1]
+// CHECK:STDOUT:   %b1: ref %B1 = bind_name b1, %b1.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b2.patt: %B2 = binding_pattern b2
+// CHECK:STDOUT:     %.loc20_3.1: %B2 = var_pattern %b2.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b2.var: ref %B2 = var b2
+// CHECK:STDOUT:   %.loc20_26.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc20_27.1: %struct_type.base.f5e = struct_literal (%.loc20_26.1)
+// CHECK:STDOUT:   %.loc20_27.2: ref %B1 = class_element_access %b2.var, element0
+// CHECK:STDOUT:   %.loc20_26.2: ref %ptr.454 = class_element_access %.loc20_27.2, element0
+// CHECK:STDOUT:   %.loc20_26.3: ref %ptr.454 = vtable_ptr @B2.%.loc11
+// CHECK:STDOUT:   %.loc20_26.4: init %ptr.454 = initialize_from %.loc20_26.3 to %.loc20_26.2
+// CHECK:STDOUT:   %.loc20_26.5: init %B1 = class_init (%.loc20_26.4), %.loc20_27.2
+// CHECK:STDOUT:   %.loc20_27.3: init %B1 = converted %.loc20_26.1, %.loc20_26.5
+// CHECK:STDOUT:   %.loc20_27.4: init %B2 = class_init (%.loc20_27.3), %b2.var
+// CHECK:STDOUT:   %.loc20_3.2: init %B2 = converted %.loc20_27.1, %.loc20_27.4
+// CHECK:STDOUT:   assign %b2.var, %.loc20_3.2
+// CHECK:STDOUT:   %B2.ref: type = name_ref B2, file.%B2.decl [concrete = constants.%B2]
+// CHECK:STDOUT:   %b2: ref %B2 = bind_name b2, %b2.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %c.patt: %C = binding_pattern c
+// CHECK:STDOUT:     %.loc21_3.1: %C = var_pattern %c.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %c.var: ref %C = var c
+// CHECK:STDOUT:   %.loc21_33.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc21_34.1: %struct_type.base.f5e = struct_literal (%.loc21_33.1)
+// CHECK:STDOUT:   %.loc21_35.1: %struct_type.base.a0c = struct_literal (%.loc21_34.1)
+// CHECK:STDOUT:   %.loc21_35.2: ref %B2 = class_element_access %c.var, element0
+// CHECK:STDOUT:   %.loc21_34.2: ref %B1 = class_element_access %.loc21_35.2, element0
+// CHECK:STDOUT:   %.loc21_33.2: ref %ptr.454 = class_element_access %.loc21_34.2, element0
+// CHECK:STDOUT:   %.loc21_33.3: ref %ptr.454 = vtable_ptr @C.%.loc16
+// CHECK:STDOUT:   %.loc21_33.4: init %ptr.454 = initialize_from %.loc21_33.3 to %.loc21_33.2
+// CHECK:STDOUT:   %.loc21_33.5: init %B1 = class_init (%.loc21_33.4), %.loc21_34.2
+// CHECK:STDOUT:   %.loc21_34.3: init %B1 = converted %.loc21_33.1, %.loc21_33.5
+// CHECK:STDOUT:   %.loc21_34.4: init %B2 = class_init (%.loc21_34.3), %.loc21_35.2
+// CHECK:STDOUT:   %.loc21_35.3: init %B2 = converted %.loc21_34.1, %.loc21_34.4
+// CHECK:STDOUT:   %.loc21_35.4: init %C = class_init (%.loc21_35.3), %c.var
+// CHECK:STDOUT:   %.loc21_3.2: init %C = converted %.loc21_35.1, %.loc21_35.4
+// CHECK:STDOUT:   assign %c.var, %.loc21_3.2
+// CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %c: ref %C = bind_name c, %c.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_modifiers.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -964,7 +1070,7 @@ class T2 {
 // CHECK:STDOUT:   %i.ref.loc14_34: ref %i32 = name_ref i, %i
 // CHECK:STDOUT:   %.loc14_35.1: %struct_type.m2.m1.68c = struct_literal (%i.ref.loc14_25, %i.ref.loc14_34)
 // CHECK:STDOUT:   %.loc14_35.2: ref %ptr.454 = class_element_access %b1.var, element0
-// CHECK:STDOUT:   %.loc14_35.3: ref %ptr.454 = vtable_ptr
+// CHECK:STDOUT:   %.loc14_35.3: ref %ptr.454 = vtable_ptr @Base.%.loc9
 // CHECK:STDOUT:   %.loc14_35.4: init %ptr.454 = initialize_from %.loc14_35.3 to %.loc14_35.2
 // CHECK:STDOUT:   %.loc14_34: %i32 = bind_value %i.ref.loc14_34
 // CHECK:STDOUT:   %.loc14_35.5: ref %i32 = class_element_access %b1.var, element2
@@ -986,7 +1092,7 @@ class T2 {
 // CHECK:STDOUT:   %int_5: Core.IntLiteral = int_value 5 [concrete = constants.%int_5.64b]
 // CHECK:STDOUT:   %.loc15_35.1: %struct_type.m2.m1.5f2 = struct_literal (%int_3.loc15, %int_5)
 // CHECK:STDOUT:   %.loc15_35.2: ref %ptr.454 = class_element_access %b2.var, element0
-// CHECK:STDOUT:   %.loc15_35.3: ref %ptr.454 = vtable_ptr
+// CHECK:STDOUT:   %.loc15_35.3: ref %ptr.454 = vtable_ptr @Base.%.loc9
 // CHECK:STDOUT:   %.loc15_35.4: init %ptr.454 = initialize_from %.loc15_35.3 to %.loc15_35.2
 // CHECK:STDOUT:   %impl.elem0.loc15_35.1: %.be7 = impl_witness_access constants.%impl_witness.d39, element0 [concrete = constants.%Convert.956]
 // CHECK:STDOUT:   %bound_method.loc15_35.1: <bound method> = bound_method %int_5, %impl.elem0.loc15_35.1 [concrete = constants.%Convert.bound.4e6]

+ 22 - 10
toolchain/lower/file_context.cpp

@@ -69,7 +69,9 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
   }
 
   for (const auto& class_info : sem_ir_->classes().array_ref()) {
-    BuildVtable(class_info);
+    if (auto* llvm_vtable = BuildVtable(class_info)) {
+      global_variables_.Insert(class_info.vtable_id, llvm_vtable);
+    }
   }
 
   // Specific functions are lowered when we emit a reference to them.
@@ -707,24 +709,35 @@ auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> LocForDI {
   }
 }
 
-auto FileContext::BuildVtable(const SemIR::Class& class_info) -> void {
+auto FileContext::BuildVtable(const SemIR::Class& class_info)
+    -> 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;
+    return nullptr;
   }
 
+  Mangler m(*this);
+  std::string mangled_name = m.MangleVTable(class_info);
+
   auto first_owning_decl_loc =
       sem_ir().insts().GetLocId(class_info.first_owning_decl_id);
   if (first_owning_decl_loc.is_import_ir_inst_id()) {
-    return;
+    // Emit a declaration of an imported vtable using a(n opaque) pointer type.
+    // This doesn't have to match the definition that appears elsewhere, it'll
+    // still get merged correctly.
+    auto* gv = new llvm::GlobalVariable(
+        llvm_module(),
+        llvm::PointerType::get(llvm_context(), /*AddressSpace=*/0),
+        /*isConstant=*/true, llvm::GlobalValue::ExternalLinkage, nullptr,
+        mangled_name);
+    gv->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+    return gv;
   }
 
   auto canonical_vtable_id =
       sem_ir().constant_values().GetConstantInstId(class_info.vtable_id);
-  if (canonical_vtable_id == SemIR::ErrorInst::SingletonInstId) {
-    return;
-  }
+
   auto vtable_inst_block =
       sem_ir().inst_blocks().Get(sem_ir()
                                      .insts()
@@ -734,9 +747,6 @@ auto FileContext::BuildVtable(const SemIR::Class& class_info) -> void {
   auto* entry_type = llvm::IntegerType::getInt32Ty(llvm_context());
   auto* table_type = llvm::ArrayType::get(entry_type, vtable_inst_block.size());
 
-  Mangler m(*this);
-  std::string mangled_name = m.MangleVTable(class_info);
-
   auto* llvm_vtable = new llvm::GlobalVariable(
       llvm_module(), table_type, /*isConstant=*/true,
       llvm::GlobalValue::ExternalLinkage, nullptr, mangled_name);
@@ -763,6 +773,8 @@ auto FileContext::BuildVtable(const SemIR::Class& class_info) -> void {
 
   llvm_vtable->setInitializer(llvm::ConstantArray::get(table_type, vfuncs));
   llvm_vtable->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+
+  return llvm_vtable;
 }
 
 }  // namespace Carbon::Lower

+ 1 - 1
toolchain/lower/file_context.h

@@ -147,7 +147,7 @@ class FileContext {
   auto BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
       -> llvm::GlobalVariable*;
 
-  auto BuildVtable(const SemIR::Class& class_info) -> void;
+  auto BuildVtable(const SemIR::Class& class_info) -> llvm::GlobalVariable*;
 
   // State for building the LLVM IR.
   llvm::LLVMContext* llvm_context_;

+ 2 - 6
toolchain/lower/handle.cpp

@@ -324,12 +324,8 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
 }
 
 auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
-                SemIR::VtablePtr /*inst*/) -> void {
-  // TODO: Initialize the virtual pointer to actually point to a virtual
-  // function table.
-  context.SetLocal(inst_id,
-                   llvm::ConstantPointerNull::get(
-                       llvm::PointerType::get(context.llvm_context(), 0)));
+                SemIR::VtablePtr inst) -> void {
+  context.SetLocal(inst_id, context.GetValue(inst.vtable_id));
 }
 
 }  // namespace Carbon::Lower

+ 44 - 16
toolchain/lower/testdata/class/virtual.carbon

@@ -32,9 +32,11 @@ package Create;
 import Classes;
 
 fn Create() {
-  var b: Classes.Base;
-  var i: Classes.Intermediate;
-  var d: Classes.Derived;
+  var b: Classes.Base = {};
+  var i: Classes.Intermediate = {.base = {}};
+  var d: Classes.Derived = {.base = {.base = {}}};
+  // TODO: Support vptr initialization without explicit source initializers.
+  var d2: Classes.Derived;
 }
 
 fn Use(v: Classes.Intermediate*) {
@@ -73,7 +75,6 @@ fn Use() {
   var v : Derived = {.base = {}};
 }
 
-
 // CHECK:STDOUT: ; ModuleID = 'classes.carbon'
 // CHECK:STDOUT: source_filename = "classes.carbon"
 // CHECK:STDOUT:
@@ -106,21 +107,39 @@ fn Use() {
 // CHECK:STDOUT: ; ModuleID = 'create.carbon'
 // CHECK:STDOUT: source_filename = "create.carbon"
 // CHECK:STDOUT:
+// 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.2 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @Base.val.loc8_44.6 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @Base.val.loc9_48.6 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CCreate.Create() !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %b.var = alloca {}, align 8, !dbg !7
 // CHECK:STDOUT:   %i.var = alloca { ptr, {} }, align 8, !dbg !7
 // CHECK:STDOUT:   %d.var = alloca { { ptr, {} } }, align 8, !dbg !7
+// CHECK:STDOUT:   %d2.var = alloca { { ptr, {} } }, align 8, !dbg !7
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 0, ptr %b.var), !dbg !7
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %b.var, ptr align 1 @Base.val.loc7_3.2, i64 0, i1 false), !dbg !7
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %i.var), !dbg !7
+// CHECK:STDOUT:   %.loc8_44.2.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %i.var, i32 0, i32 0, !dbg !8
+// CHECK:STDOUT:   store ptr @"_CIntermediate.Classes.$vtable", ptr %.loc8_44.2.vptr, align 8, !dbg !8
+// CHECK:STDOUT:   %.loc8_44.5.base = getelementptr inbounds nuw { ptr, {} }, ptr %i.var, i32 0, i32 1, !dbg !8
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc8_44.5.base, ptr align 1 @Base.val.loc8_44.6, i64 0, i1 false), !dbg !8
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %d.var), !dbg !7
-// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT:   %.loc9_49.2.base = getelementptr inbounds nuw { { ptr, {} } }, ptr %d.var, i32 0, i32 0, !dbg !9
+// CHECK:STDOUT:   %.loc9_48.2.vptr = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   store ptr @"_CDerived.Classes.$vtable", ptr %.loc9_48.2.vptr, align 8, !dbg !10
+// CHECK:STDOUT:   %.loc9_48.5.base = getelementptr inbounds nuw { ptr, {} }, ptr %.loc9_49.2.base, i32 0, i32 1, !dbg !10
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc9_48.5.base, ptr align 1 @Base.val.loc9_48.6, i64 0, i1 false), !dbg !10
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 8, ptr %d2.var), !dbg !7
+// CHECK:STDOUT:   ret void, !dbg !11
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !9 {
+// CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !12 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_CFn.Intermediate.Classes(ptr %v), !dbg !10
-// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT:   call void @_CFn.Intermediate.Classes(ptr %v), !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: declare void @_CFn.Intermediate.Classes(ptr)
@@ -128,10 +147,16 @@ fn Use() {
 // 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: uselistorder i32 1, { 1, 3, 0, 2, 4, 5 }
+// 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:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
@@ -144,10 +169,13 @@ fn Use() {
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{}
 // CHECK:STDOUT: !7 = !DILocation(line: 7, column: 3, scope: !4)
-// CHECK:STDOUT: !8 = !DILocation(line: 6, column: 1, scope: !4)
-// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Create", scope: null, file: !3, line: 12, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !10 = !DILocation(line: 13, column: 3, scope: !9)
-// CHECK:STDOUT: !11 = !DILocation(line: 12, column: 1, scope: !9)
+// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 33, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 9, column: 28, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 9, column: 37, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 6, column: 1, scope: !4)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Create", scope: null, file: !3, line: 14, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !13 = !DILocation(line: 15, column: 3, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 14, column: 1, scope: !12)
 // CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT:
@@ -167,7 +195,7 @@ fn Use() {
 // 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 !9
 // CHECK:STDOUT:   %.loc11_24.2.vptr = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 0, !dbg !10
-// CHECK:STDOUT:   store ptr null, ptr %.loc11_24.2.vptr, align 8, !dbg !10
+// CHECK:STDOUT:   store ptr @"_CBase.MemberInit.$vtable", ptr %.loc11_24.2.vptr, align 8, !dbg !10
 // CHECK:STDOUT:   %.loc11_23 = load i32, ptr %i.var, align 4, !dbg !11
 // CHECK:STDOUT:   %.loc11_24.5.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !10
 // CHECK:STDOUT:   store i32 %.loc11_23, ptr %.loc11_24.5.m, align 4, !dbg !10
@@ -175,7 +203,7 @@ fn Use() {
 // CHECK:STDOUT:   store i32 5, ptr %.loc12_4.m, align 4, !dbg !12
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %u.var), !dbg !9
 // CHECK:STDOUT:   %.loc13_24.2.vptr = getelementptr inbounds nuw { ptr, i32 }, ptr %u.var, i32 0, i32 0, !dbg !13
-// CHECK:STDOUT:   store ptr null, ptr %.loc13_24.2.vptr, align 8, !dbg !13
+// CHECK:STDOUT:   store ptr @"_CBase.MemberInit.$vtable", ptr %.loc13_24.2.vptr, align 8, !dbg !13
 // CHECK:STDOUT:   %.loc13_24.6.m = getelementptr inbounds nuw { ptr, i32 }, ptr %u.var, i32 0, i32 1, !dbg !13
 // CHECK:STDOUT:   store i32 3, ptr %.loc13_24.6.m, align 4, !dbg !13
 // CHECK:STDOUT:   ret void, !dbg !14
@@ -222,7 +250,7 @@ fn Use() {
 // 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 null, ptr %.loc13_31.2.vptr, align 8, !dbg !9
+// CHECK:STDOUT:   store ptr @"_CDerived.Main.$vtable", ptr %.loc13_31.2.vptr, align 8, !dbg !9
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 2
toolchain/sem_ir/typed_insts.h

@@ -1792,9 +1792,10 @@ struct VtableType {
 
 // Initializer for virtual function table pointers in object initialization.
 struct VtablePtr {
-  static constexpr auto Kind =
-      InstKind::VtablePtr.Define<Parse::NodeId>({.ir_name = "vtable_ptr"});
+  static constexpr auto Kind = InstKind::VtablePtr.Define<Parse::NodeId>(
+      {.ir_name = "vtable_ptr", .constant_kind = InstConstantKind::Never});
   TypeId type_id;
+  InstId vtable_id;
 };
 
 // Definition of ABI-neutral vtable information for a dynamic class.