diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 98c9e339795a..e0aaf02472f8 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -156,7 +156,8 @@ static auto ConvertAggregateElement( llvm::ArrayRef 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( 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 -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 || std::is_same_v); 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( - 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(context, value_loc_id, {.type_id = dest_field.type_id, @@ -512,7 +512,8 @@ static auto ConvertStructToStructOrClass(Context& context, ConvertAggregateElement( 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( - 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; } diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 14c77ef78933..cc94f998d29d 100644 --- a/toolchain/check/convert.h +++ b/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 diff --git a/toolchain/check/testdata/class/virtual_modifiers.carbon b/toolchain/check/testdata/class/virtual_modifiers.carbon index 978eae9ec58f..09030fcacbd1 100644 --- a/toolchain/check/testdata/class/virtual_modifiers.carbon +++ b/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 (%H.bce) [concrete] -// CHECK:STDOUT: %struct_type.base: type = struct_type {.base: %Base} [concrete] -// CHECK:STDOUT: %complete_type.0e2: = complete_type_witness %struct_type.base [concrete] +// CHECK:STDOUT: %struct_type.base.96c: type = struct_type {.base: %Base} [concrete] +// CHECK:STDOUT: %complete_type.0e2: = 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 (%H.decl) [concrete = constants.%.dce] -// CHECK:STDOUT: %complete_type: = complete_type_witness %struct_type.base [concrete = constants.%complete_type.0e2] +// CHECK:STDOUT: %complete_type: = 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: = 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 (%F.437) [concrete] // CHECK:STDOUT: %struct_type.base.421: type = struct_type {.base: %B2} [concrete] // CHECK:STDOUT: %complete_type.066: = 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 %int_5, %impl.elem0.loc15_35.1 [concrete = constants.%Convert.bound.4e6] diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 0145957e94fc..331d50b94f01 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -69,7 +69,9 @@ auto FileContext::Run() -> std::unique_ptr { } 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 diff --git a/toolchain/lower/file_context.h b/toolchain/lower/file_context.h index 2963c2f8001c..32128e299239 100644 --- a/toolchain/lower/file_context.h +++ b/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_; diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index f03d1824ad27..469c961b7b14 100644 --- a/toolchain/lower/handle.cpp +++ b/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 diff --git a/toolchain/lower/testdata/class/virtual.carbon b/toolchain/lower/testdata/class/virtual.carbon index 6658d8b663f9..11c761d7efd5 100644 --- a/toolchain/lower/testdata/class/virtual.carbon +++ b/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: diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 50782af41ce6..60d7d5f5a389 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/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({.ir_name = "vtable_ptr"}); + static constexpr auto Kind = InstKind::VtablePtr.Define( + {.ir_name = "vtable_ptr", .constant_kind = InstConstantKind::Never}); TypeId type_id; + InstId vtable_id; }; // Definition of ABI-neutral vtable information for a dynamic class.