From c33fb9fc481f04aabc3651cbf2ecd141da6b87db Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 13 May 2026 10:40:45 -0700 Subject: [PATCH] Support signature mismatch between `virtual fn` and `override fn`. (#7198) For now, hide `override fn`s from name lookup, so that the base class version is always used, as the derived-class version does not have its own vptr entry and so would not do the right thing if a further-derived class adds a new override. This is implemented via a new access kind of `Hidden`. When checking the overriding function, pass in the expected `Self` type and check the `self` parameter against that; the signature that we generate for the thunk in the derived class is the base class signature with the `self` parameter's type changed to the derived class. When we generate a thunk for a virtual function, the thunk is assigned a `virtual_index`, and the virtual function itself is not. When the thunk makes a direct call to the virtual function, recognize this situation by checking for a `virtual_index`, and perform a non-virtual call if there isn't one. Assisted-by: Gemini via Antigravity --- toolchain/check/class.cpp | 27 ++- toolchain/check/cpp/access.cpp | 1 + toolchain/check/function.cpp | 7 +- toolchain/check/function.h | 20 +- toolchain/check/impl.cpp | 8 +- toolchain/check/keyword_modifier_set.h | 6 + toolchain/check/merge.cpp | 42 ++-- toolchain/check/merge.h | 17 +- toolchain/check/name_lookup.cpp | 2 + .../testdata/class/method/virtual.carbon | 141 ++++++++---- toolchain/check/thunk.cpp | 42 ++-- toolchain/check/thunk.h | 1 + toolchain/lower/handle_call.cpp | 2 +- toolchain/lower/testdata/class/virtual.carbon | 207 +++++++++++++++++- toolchain/sem_ir/dump.cpp | 3 + toolchain/sem_ir/formatter.cpp | 3 + toolchain/sem_ir/function.h | 3 +- toolchain/sem_ir/name_scope.h | 5 + 18 files changed, 427 insertions(+), 110 deletions(-) diff --git a/toolchain/check/class.cpp b/toolchain/check/class.cpp index 220f4213983c..17aff5d4720d 100644 --- a/toolchain/check/class.cpp +++ b/toolchain/check/class.cpp @@ -16,6 +16,7 @@ #include "toolchain/check/name_ref.h" #include "toolchain/check/pattern.h" #include "toolchain/check/pattern_match.h" +#include "toolchain/check/thunk.h" #include "toolchain/check/type.h" #include "toolchain/parse/node_ids.h" #include "toolchain/sem_ir/builtin_function_kind.h" @@ -207,13 +208,25 @@ static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id, auto override_fn_id = context.insts().GetAs(*i).function_id; implemented_impls.Insert(override_fn_id); - auto& override_fn = context.functions().Get(override_fn_id); - CheckFunctionTypeMatches(context, override_fn, fn, specific_id, - /*check_syntax=*/false, - /*check_self=*/false); - derived_vtable_entry_id = build_specific_function(*i); - override_fn.virtual_index = vtable.size(); - CARBON_CHECK(override_fn.virtual_index == fn.virtual_index); + + // TODO: When the base class is a C++ class, we could have multiple + // potential functions to override. Check against each of them rather + // than trying to override them all. + auto override_or_thunk_id = + BuildThunk(context, fn_id, specific_id, class_info.self_type_id, *i, + /*defer_definition=*/true); + if (override_or_thunk_id != SemIR::ErrorInst::InstId) { + auto override_or_thunk_fn_id = + context.insts() + .GetAs(override_or_thunk_id) + .function_id; + auto& override_or_thunk_fn = + context.functions().Get(override_or_thunk_fn_id); + derived_vtable_entry_id = + build_specific_function(override_or_thunk_id); + override_or_thunk_fn.virtual_index = vtable.size(); + CARBON_CHECK(override_or_thunk_fn.virtual_index == fn.virtual_index); + } } else if (auto base_vtable_specific_function = context.insts().TryGetAs( derived_vtable_entry_id)) { diff --git a/toolchain/check/cpp/access.cpp b/toolchain/check/cpp/access.cpp index f641b402b33b..8250e4af63b4 100644 --- a/toolchain/check/cpp/access.cpp +++ b/toolchain/check/cpp/access.cpp @@ -51,6 +51,7 @@ auto MapToCppAccess(SemIR::AccessKind access) -> clang::AccessSpecifier { case SemIR::AccessKind::Protected: return clang::AS_protected; case SemIR::AccessKind::Private: + case SemIR::AccessKind::Hidden: return clang::AS_private; } } diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index baca04fa2620..2138663e3cc5 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -304,11 +304,12 @@ auto CheckFunctionTypeMatches(Context& context, const SemIR::Function& new_function, const SemIR::Function& prev_function, SemIR::SpecificId prev_specific_id, - bool check_syntax, bool check_self, bool diagnose) - -> bool { + bool check_syntax, + SemIR::TypeId self_type_override_id, + bool diagnose) -> bool { if (!CheckRedeclParamsMatch(context, DeclParams(new_function), DeclParams(prev_function), prev_specific_id, - diagnose, check_syntax, check_self)) { + diagnose, check_syntax, self_type_override_id)) { return false; } if (!CheckFunctionReturnTypeMatches(context, new_function, prev_function, diff --git a/toolchain/check/function.h b/toolchain/check/function.h index db986099a575..f6ed7c75d114 100644 --- a/toolchain/check/function.h +++ b/toolchain/check/function.h @@ -71,14 +71,16 @@ auto CheckFunctionReturnTypeMatches(Context& context, // // `check_syntax` is false if the redeclaration can be called via a thunk with // implicit conversions from the original declaration. -// `check_self` is false if the self declaration does not have to match (for -// instance in impls of virtual functions). -auto CheckFunctionTypeMatches(Context& context, - const SemIR::Function& new_function, - const SemIR::Function& prev_function, - SemIR::SpecificId prev_specific_id, - bool check_syntax, bool check_self, - bool diagnose = true) -> bool; +// +// If `self_type_override_id` is specified, the self type is checked against +// that type instead of the type from `prev_function`. This is used to check +// virtual function overrides. +auto CheckFunctionTypeMatches( + Context& context, const SemIR::Function& new_function, + const SemIR::Function& prev_function, SemIR::SpecificId prev_specific_id, + bool check_syntax, + SemIR::TypeId self_type_override_id = SemIR::TypeId::None, + bool diagnose = true) -> bool; inline auto CheckFunctionTypeMatches(Context& context, const SemIR::Function& new_function, @@ -86,7 +88,7 @@ inline auto CheckFunctionTypeMatches(Context& context, -> bool { return CheckFunctionTypeMatches(context, new_function, prev_function, SemIR::SpecificId::None, - /*check_syntax=*/true, /*check_self=*/true); + /*check_syntax=*/true); } // Checks that the scrutinee type of `return_pattern_id` in `specific_id` is diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index afdad3ff74d2..ef87b9a1b1cf 100644 --- a/toolchain/check/impl.cpp +++ b/toolchain/check/impl.cpp @@ -78,8 +78,9 @@ auto CheckAssociatedFunctionImplementation( enclosing_specific_id); return BuildThunk(context, interface_function_type.function_id, - interface_function_specific_id, impl_decl_id, - defer_thunk_definition); + interface_function_specific_id, + /*signature_self_type_override_id=*/SemIR::TypeId::None, + impl_decl_id, defer_thunk_definition); } static auto GetScopeInstId(Context& context, SemIR::InstId scope_inst_id) @@ -160,8 +161,7 @@ static auto VerifyImplRedecl(Context& context, const SemIR::Impl& new_impl, // `impl`. Keep looking for a prior declaration without issuing a diagnostic. if (!CheckRedeclParamsMatch(context, DeclParams(new_impl), DeclParams(prev_impl), SemIR::SpecificId::None, - /*diagnose=*/false, /*check_syntax=*/true, - /*check_self=*/true)) { + /*diagnose=*/false, /*check_syntax=*/true)) { return ImplRedeclType::Mismatch; } diff --git a/toolchain/check/keyword_modifier_set.h b/toolchain/check/keyword_modifier_set.h index 156b999512a0..c83f4bb9d89a 100644 --- a/toolchain/check/keyword_modifier_set.h +++ b/toolchain/check/keyword_modifier_set.h @@ -109,6 +109,12 @@ class KeywordModifierSet : public CARBON_ENUM_MASK_BASE(KeywordModifierSet) { // Returns the access kind from modifiers. auto GetAccessKind() const -> SemIR::AccessKind { + if (HasAnyOf(KeywordModifierSet::Override)) { + // TODO: Instead of hiding `override fn`s, we should expose them but make + // calls that we cannot statically devirtualize call the base class + // version (the one whose signature is in the vtable). + return SemIR::AccessKind::Hidden; + } if (HasAnyOf(KeywordModifierSet::Protected)) { return SemIR::AccessKind::Protected; } diff --git a/toolchain/check/merge.cpp b/toolchain/check/merge.cpp index cf3f92b84ba7..704c051b2931 100644 --- a/toolchain/check/merge.cpp +++ b/toolchain/check/merge.cpp @@ -206,7 +206,8 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, SemIR::InstId orig_new_param_pattern_id, SemIR::InstId orig_prev_param_pattern_id, SemIR::SpecificId prev_specific_id, bool diagnose, - bool check_syntax, bool check_self) -> bool { + bool check_syntax, + SemIR::TypeId self_type_override_id) -> bool { CARBON_DIAGNOSTIC( RedeclParamPrevious, Note, "previous declaration's corresponding {0:implicit |}parameter here", @@ -236,9 +237,9 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, pattern_stack.push_back({.prev_id = orig_prev_param_pattern_id, .new_id = orig_new_param_pattern_id}); - // When `check_self` is false, we need to disable type checking as soon as we - // determine this is a `self` parameter, and that decision needs to persist - // across the handling of any subpatterns. + // When `self_type_override_id` is specified, we need to disable type checking + // as soon as we determine this is a `self` parameter, and that decision needs + // to persist across the handling of any subpatterns. bool check_type = true; do { auto patterns = pattern_stack.pop_back_val(); @@ -251,9 +252,7 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, // Conditionally checks for and diagnoses a type mismatch between the old // and new parameter patterns. Returns false if a mismatch was found. - auto check_for_type_mismatch = [&]() { - auto prev_param_type_id = SemIR::GetTypeOfInstInSpecific( - context.sem_ir(), prev_specific_id, patterns.prev_id); + auto check_for_type_mismatch_with = [&](SemIR::TypeId prev_param_type_id) { if (check_type && !context.types().AreEqualAcrossDeclarations( new_param_pattern.type_id(), prev_param_type_id)) { if (diagnose) { @@ -275,6 +274,11 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, return true; }; + auto check_for_type_mismatch = [&]() { + return check_for_type_mismatch_with(SemIR::GetTypeOfInstInSpecific( + context.sem_ir(), prev_specific_id, patterns.prev_id)); + }; + CARBON_KIND_SWITCH(new_param_pattern) { case CARBON_KIND_ANY(SemIR::AnyLeafParamPattern, _): { if (!check_for_type_mismatch()) { @@ -300,9 +304,13 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, .Get(prev_any_binding_pattern.entity_name_id) .name_id; - if (!check_self && new_name_id == SemIR::NameId::SelfValue && - prev_name_id == SemIR::NameId::SelfValue) { + // If this is the self parameter, and we have a type override for it, + // check against that type instead. + if (new_name_id == SemIR::NameId::SelfValue && + prev_name_id == SemIR::NameId::SelfValue && + self_type_override_id.has_value()) { check_type = false; + check_for_type_mismatch_with(self_type_override_id); } if (new_any_binding_pattern.kind == @@ -339,7 +347,8 @@ static auto CheckRedeclParams(Context& context, SemIR::LocId new_decl_loc_id, SemIR::InstBlockId prev_param_patterns_id, bool is_implicit_param, SemIR::SpecificId prev_specific_id, bool diagnose, - bool check_syntax, bool check_self) -> bool { + bool check_syntax, + SemIR::TypeId self_type_override_id) -> bool { // This will often occur for empty params. if (new_param_patterns_id == prev_param_patterns_id) { return true; @@ -398,7 +407,7 @@ static auto CheckRedeclParams(Context& context, SemIR::LocId new_decl_loc_id, if (!CheckRedeclParam(context, is_implicit_param, index, new_param_pattern_id, prev_param_pattern_id, prev_specific_id, diagnose, check_syntax, - check_self)) { + self_type_override_id)) { return false; } } @@ -521,7 +530,8 @@ static auto CheckRedeclParamSyntax(Context& context, auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity, const DeclParams& prev_entity, SemIR::SpecificId prev_specific_id, bool diagnose, - bool check_syntax, bool check_self) -> bool { + bool check_syntax, + SemIR::TypeId self_type_override_id) -> bool { if (EntityHasParamError(context, new_entity) || EntityHasParamError(context, prev_entity)) { return false; @@ -530,16 +540,16 @@ auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity, context, new_entity.loc_id, new_entity.implicit_param_patterns_id, prev_entity.loc_id, prev_entity.implicit_param_patterns_id, /*is_implicit_param=*/true, prev_specific_id, diagnose, check_syntax, - check_self)) { + self_type_override_id)) { return false; } - // Don't forward `check_self` here because it's extra cost, and `self` is only - // allowed in implicit params. + // Don't forward `self_type_override_id` here because it's extra cost, and + // `self` is only allowed in implicit params. if (!CheckRedeclParams(context, new_entity.loc_id, new_entity.param_patterns_id, prev_entity.loc_id, prev_entity.param_patterns_id, /*is_implicit_param=*/false, prev_specific_id, - diagnose, check_syntax, /*check_self=*/true)) { + diagnose, check_syntax, SemIR::TypeId::None)) { return false; } if (check_syntax && diff --git a/toolchain/check/merge.h b/toolchain/check/merge.h index 81c03d099578..d2809ca08f77 100644 --- a/toolchain/check/merge.h +++ b/toolchain/check/merge.h @@ -95,20 +95,21 @@ struct DeclParams { // Checks that the parameters in a redeclaration of an entity match the // parameters in the prior declaration. If not, produces a diagnostic if -// `diagnose` is true, and returns false. If `check_self` is false, -// type and name mismatches will not be diagnosed for the `self` parameter -// (if any), but form mismatches will still be diagnosed. -auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity, - const DeclParams& prev_entity, - SemIR::SpecificId prev_specific_id, bool diagnose, - bool check_syntax, bool check_self) -> bool; +// `diagnose` is true, and returns false. If `self_type_override_id` is +// specified, the type of `self` will be compared against that type instead of +// the `self` type from `prev_entity`. +auto CheckRedeclParamsMatch( + Context& context, const DeclParams& new_entity, + const DeclParams& prev_entity, SemIR::SpecificId prev_specific_id, + bool diagnose, bool check_syntax, + SemIR::TypeId self_type_override_id = SemIR::TypeId::None) -> bool; inline auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity, const DeclParams& prev_entity) -> bool { return CheckRedeclParamsMatch(context, new_entity, prev_entity, SemIR::SpecificId::None, /*diagnose=*/true, - /*check_syntax=*/true, /*check_self=*/true); + /*check_syntax=*/true); } } // namespace Carbon::Check diff --git a/toolchain/check/name_lookup.cpp b/toolchain/check/name_lookup.cpp index 2a1e1570f2c8..36691c578e54 100644 --- a/toolchain/check/name_lookup.cpp +++ b/toolchain/check/name_lookup.cpp @@ -268,6 +268,8 @@ static auto IsAccessProhibited(std::optional access_info, return access_info->highest_allowed_access != SemIR::AccessKind::Private || is_parent_access; + case SemIR::AccessKind::Hidden: + return true; } } diff --git a/toolchain/check/testdata/class/method/virtual.carbon b/toolchain/check/testdata/class/method/virtual.carbon index 993ebe59b630..e57eacfa3baa 100644 --- a/toolchain/check/testdata/class/method/virtual.carbon +++ b/toolchain/check/testdata/class/method/virtual.carbon @@ -202,17 +202,20 @@ base class Base { class Derived { extend base: Base; - // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+7]]:3: error: redeclaration differs because of parameter count of 1 [RedeclParamCountDiffers] + // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+10]]:3: error: 0 arguments passed to function expecting 1 argument [CallArgCountMismatch] // CHECK:STDERR: override fn F[self: Self](v: i32); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE-8]]:3: note: previously declared with parameter count of 0 [RedeclParamCountPrevious] + // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+7]]:3: note: calling function declared here [InCallToEntity] + // CHECK:STDERR: override fn F[self: Self](v: i32); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE-11]]:3: note: while building thunk to match the signature of this function [ThunkSignature] // CHECK:STDERR: virtual fn F[self: Self](); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: override fn F[self: Self](v: i32); } -// --- fail_todo_impl_conversion.carbon +// --- impl_conversion.carbon library "[[@TEST_NAME]]"; @@ -235,17 +238,14 @@ base class Base { class Derived { extend base: Base; //@dump-sem-ir-begin - // CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE+7]]:3: error: function redeclaration differs because return type is `T2` [FunctionRedeclReturnTypeDiffers] - // CHECK:STDERR: override fn F[self: Self]() -> T2; - // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE-9]]:3: note: previously declared with return type `T1` [FunctionRedeclReturnTypePrevious] - // CHECK:STDERR: virtual fn F[self: Self]() -> T1; - // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: override fn F[self: Self]() -> T2; //@dump-sem-ir-end } +fn Derived.F[unused self: Self]() -> T2 { + return {}; +} + // --- fail_generic_virtual_decl.carbon library "[[@TEST_NAME]]"; @@ -309,17 +309,20 @@ class T2 { library "[[@TEST_NAME]]"; base class T1 { + // CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE+3]]:17: error: value expression passed to reference parameter [ValueForRefParam] + // CHECK:STDERR: virtual fn F1[self: Self](); + // CHECK:STDERR: ^~~~~~~~~~ virtual fn F1[self: Self](); } class T2 { extend base: T1; - // CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE+7]]:18: error: redeclaration differs at implicit parameter 1 [RedeclParamDiffers] + // CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE+7]]:18: note: initializing function parameter [InCallToFunctionParam] // CHECK:STDERR: override fn F1[ref self: Self](); // CHECK:STDERR: ^~~~~~~~~~~~~~ - // CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE-8]]:17: note: previous declaration's corresponding implicit parameter here [RedeclParamPrevious] + // CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE-8]]:3: note: while building thunk to match the signature of this function [ThunkSignature] // CHECK:STDERR: virtual fn F1[self: Self](); - // CHECK:STDERR: ^~~~~~~~~~ + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: override fn F1[ref self: Self](); } @@ -431,17 +434,23 @@ class T1; class T2; base class Base(T:! type) { + // CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+6]]:42: error: cannot implicitly convert expression of type `T1*` to `T2*` [ConversionFailure] + // CHECK:STDERR: virtual fn F[unused self: Self](unused t: T1*) { } + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+3]]:42: note: type `T1*` does not implement interface `Core.ImplicitAs(T2*)` [MissingImplInMemberAccessInContext] + // CHECK:STDERR: virtual fn F[unused self: Self](unused t: T1*) { } + // CHECK:STDERR: ^~~~~~ virtual fn F[unused self: Self](unused t: T1*) { } } class D1 { extend base: Base(T1); - // CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+7]]:43: error: type `` of parameter 1 in redeclaration differs from previous parameter type `` [RedeclParamDiffersType] + // CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+7]]:43: note: initializing function parameter [InCallToFunctionParam] // CHECK:STDERR: override fn F[unused self: Self](unused t: T2*) { } // CHECK:STDERR: ^~~~~~ - // CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE-8]]:42: note: previous declaration's corresponding parameter here [RedeclParamPrevious] + // CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE-8]]:3: note: while building thunk to match the signature of this function [ThunkSignature] // CHECK:STDERR: virtual fn F[unused self: Self](unused t: T1*) { } - // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: override fn F[unused self: Self](unused t: T2*) { } } @@ -451,16 +460,22 @@ class D1 { library "[[@TEST_NAME]]"; abstract class Base(T:! type) { + // CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+6]]:42: error: cannot implicitly convert expression of type `T*` to `T` [ConversionFailure] + // CHECK:STDERR: virtual fn F[unused self: Self](unused t: T*) { } + // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+3]]:42: note: type `T*` does not implement interface `Core.ImplicitAs(T)` [MissingImplInMemberAccessInContext] + // CHECK:STDERR: virtual fn F[unused self: Self](unused t: T*) { } + // CHECK:STDERR: ^~~~~ virtual fn F[unused self: Self](unused t: T*) { } } class Derived(T:! type) { extend base: Base(T); - // CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+7]]:43: error: type `` of parameter 1 in redeclaration differs from previous parameter type `` [RedeclParamDiffersType] + // CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+7]]:43: note: initializing function parameter [InCallToFunctionParam] // CHECK:STDERR: override fn F[unused self: Self](unused t: T) { } // CHECK:STDERR: ^~~~ - // CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE-7]]:42: note: previous declaration's corresponding parameter here [RedeclParamPrevious] + // CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE-7]]:3: note: while building thunk to match the signature of this function [ThunkSignature] // CHECK:STDERR: virtual fn F[unused self: Self](unused t: T*) { } - // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: override fn F[unused self: Self](unused t: T) { } } @@ -688,7 +703,7 @@ class T2(G2:! type) { // CHECK:STDOUT: .Self = constants.%Derived // CHECK:STDOUT: .Modifiers = // CHECK:STDOUT: .base = %.loc8 -// CHECK:STDOUT: .H = %Derived.H.decl +// CHECK:STDOUT: .H [hidden] = %Derived.H.decl // CHECK:STDOUT: extend %Base.ref // CHECK:STDOUT: } // CHECK:STDOUT: @@ -1005,7 +1020,7 @@ class T2(G2:! type) { // CHECK:STDOUT: .Self = constants.%Derived // CHECK:STDOUT: .AbstractIntermediate = // CHECK:STDOUT: .base = %.loc13 -// CHECK:STDOUT: .F = %Derived.F.decl +// CHECK:STDOUT: .F [hidden] = %Derived.F.decl // CHECK:STDOUT: extend %AbstractIntermediate.ref // CHECK:STDOUT: } // CHECK:STDOUT: @@ -1052,7 +1067,7 @@ class T2(G2:! type) { // CHECK:STDOUT: .Self = constants.%Derived // CHECK:STDOUT: .VirtualIntermediate = // CHECK:STDOUT: .base = %.loc13 -// CHECK:STDOUT: .F = %Derived.F.decl +// CHECK:STDOUT: .F [hidden] = %Derived.F.decl // CHECK:STDOUT: extend %VirtualIntermediate.ref // CHECK:STDOUT: } // CHECK:STDOUT: @@ -1070,33 +1085,48 @@ class T2(G2:! type) { // CHECK:STDOUT: // CHECK:STDOUT: override fn @Derived.F(%self.param: %Derived); // CHECK:STDOUT: -// CHECK:STDOUT: --- fail_todo_impl_conversion.carbon +// CHECK:STDOUT: --- impl_conversion.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { +// CHECK:STDOUT: %T1: type = class_type @T1 [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] // CHECK:STDOUT: %T2: type = class_type @T2 [concrete] -// CHECK:STDOUT: %pattern_type.b8b: type = pattern_type %T2 [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %ImplicitAs.type.8c9: type = facet_type <@ImplicitAs, @ImplicitAs(%T1)> [concrete] +// CHECK:STDOUT: %ImplicitAs.impl_witness: = impl_witness @T2.as.ImplicitAs.impl.%ImplicitAs.impl_witness_table [concrete] +// CHECK:STDOUT: %T2.as.ImplicitAs.impl.Convert.type: type = fn_type @T2.as.ImplicitAs.impl.Convert [concrete] +// CHECK:STDOUT: %T2.as.ImplicitAs.impl.Convert: %T2.as.ImplicitAs.impl.Convert.type = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.8c9 = facet_value %T2, (%ImplicitAs.impl_witness) [concrete] +// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.597: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%T1, %ImplicitAs.facet) [concrete] // CHECK:STDOUT: %Derived: type = class_type @Derived [concrete] -// CHECK:STDOUT: %pattern_type.9f6: type = pattern_type %Derived [concrete] // CHECK:STDOUT: %.c13: Core.Form = init_form %T2 [concrete] -// CHECK:STDOUT: %Derived.F.type: type = fn_type @Derived.F [concrete] -// CHECK:STDOUT: %Derived.F: %Derived.F.type = struct_value () [concrete] +// CHECK:STDOUT: %Derived.F.type.5bfd52.1: type = fn_type @Derived.F.loc27 [concrete] +// CHECK:STDOUT: %Derived.F.f9b6d2.1: %Derived.F.type.5bfd52.1 = struct_value () [concrete] +// CHECK:STDOUT: %Derived.F.type.5bfd52.2: type = fn_type @Derived.F.loc23 [concrete] +// CHECK:STDOUT: %Derived.F.f9b6d2.2: %Derived.F.type.5bfd52.2 = struct_value () [concrete] +// CHECK:STDOUT: %.968: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.597, %ImplicitAs.facet [concrete] +// CHECK:STDOUT: %Destroy.Op.type.bae255.2: type = fn_type @Destroy.Op.loc23_36.2 [concrete] +// CHECK:STDOUT: %Destroy.Op.651ba6.2: %Destroy.Op.type.bae255.2 = struct_value () [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Derived { // CHECK:STDOUT: -// CHECK:STDOUT: %Derived.F.decl: %Derived.F.type = fn_decl @Derived.F [concrete = constants.%Derived.F] { -// CHECK:STDOUT: %self.param_patt: %pattern_type.9f6 = value_param_pattern [concrete] -// CHECK:STDOUT: %self.patt: %pattern_type.9f6 = at_binding_pattern self, %self.param_patt [concrete] -// CHECK:STDOUT: %return.param_patt: %pattern_type.b8b = out_param_pattern [concrete] -// CHECK:STDOUT: %return.patt: %pattern_type.b8b = return_slot_pattern %return.param_patt, %T2.ref [concrete] +// CHECK:STDOUT: %Derived.F.decl.loc23_36.1: %Derived.F.type.5bfd52.1 = fn_decl @Derived.F.loc27 [concrete = constants.%Derived.F.f9b6d2.1] { +// CHECK:STDOUT: // CHECK:STDOUT: } { -// CHECK:STDOUT: %T2.ref: type = name_ref T2, file.%T2.decl [concrete = constants.%T2] -// CHECK:STDOUT: %.loc30: Core.Form = init_form %T2.ref [concrete = constants.%.c13] -// CHECK:STDOUT: %self.param: %Derived = value_param call_param0 -// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived] -// CHECK:STDOUT: %self: %Derived = value_binding self, %self.param -// CHECK:STDOUT: %return.param: ref %T2 = out_param call_param1 -// CHECK:STDOUT: %return: ref %T2 = return_slot %return.param +// CHECK:STDOUT: %T2.ref.loc23: type = name_ref T2, file.%T2.decl [concrete = constants.%T2] +// CHECK:STDOUT: %.loc23: Core.Form = init_form %T2.ref.loc23 [concrete = constants.%.c13] +// CHECK:STDOUT: %self.param.loc23: %Derived = value_param call_param0 +// CHECK:STDOUT: %Self.ref.loc23: type = name_ref Self, constants.%Derived [concrete = constants.%Derived] +// CHECK:STDOUT: %self.loc23: %Derived = value_binding self, %self.param.loc23 +// CHECK:STDOUT: %return.param.loc23: ref %T2 = out_param call_param1 +// CHECK:STDOUT: %return.loc23: ref %T2 = return_slot %return.param.loc23 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: %Derived.F.decl.loc23_36.2: %Derived.F.type.5bfd52.2 = fn_decl @Derived.F.loc23 [concrete = constants.%Derived.F.f9b6d2.2] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: complete_type_witness = %complete_type @@ -1107,7 +1137,7 @@ class T2(G2:! type) { // CHECK:STDOUT: .Base = // CHECK:STDOUT: .base = %.loc21 // CHECK:STDOUT: .T2 = -// CHECK:STDOUT: .F = %Derived.F.decl +// CHECK:STDOUT: .F [hidden] = %Derived.F.decl.loc23_36.1 // CHECK:STDOUT: extend %Base.ref // CHECK:STDOUT: } // CHECK:STDOUT: @@ -1116,10 +1146,33 @@ class T2(G2:! type) { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: vtable @Derived.vtable { -// CHECK:STDOUT: @Derived.%Derived.F.decl +// CHECK:STDOUT: @Derived.%Derived.F.decl.loc23_36.2 // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: override fn @Derived.F(%self.param: %Derived) -> out %return.param: %T2; +// CHECK:STDOUT: override fn @Derived.F.loc23(%self.param: %Derived) -> out %return.param: %T1 [thunk @Derived.%Derived.F.decl.loc23_36.1 for @Base.%Base.F.decl] { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %Derived.F.type.5bfd52.1 = name_ref F, @Derived.%Derived.F.decl.loc23_36.1 [concrete = constants.%Derived.F.f9b6d2.1] +// CHECK:STDOUT: %Derived.F.bound: = bound_method %self.param, %F.ref +// CHECK:STDOUT: %.loc23_36.1: ref %T2 = temporary_storage +// CHECK:STDOUT: %Derived.F.call: init %T2 to %.loc23_36.1 = call %Derived.F.bound(%self.param) +// CHECK:STDOUT: %impl.elem0: %.968 = impl_witness_access constants.%ImplicitAs.impl_witness, element0 [concrete = constants.%T2.as.ImplicitAs.impl.Convert] +// CHECK:STDOUT: %bound_method: = bound_method %Derived.F.call, %impl.elem0 +// CHECK:STDOUT: +// CHECK:STDOUT: %.loc23_36.2: ref %T2 = temporary %.loc23_36.1, %Derived.F.call +// CHECK:STDOUT: %.loc23_36.3: %T2 = acquire_value %.loc23_36.2 +// CHECK:STDOUT: %T2.as.ImplicitAs.impl.Convert.call: init %T1 to %.loc17 = call %bound_method(%.loc23_36.3) +// CHECK:STDOUT: %.loc23_36.4: init %T1 = converted %Derived.F.call, %T2.as.ImplicitAs.impl.Convert.call +// CHECK:STDOUT: %Destroy.Op.bound: = bound_method %.loc23_36.2, constants.%Destroy.Op.651ba6.2 +// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%.loc23_36.2) +// CHECK:STDOUT: return %.loc23_36.4 to %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.Op.loc23_36.1(%self.param: ref %empty_struct_type) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.Op.loc23_36.2(%self.param: ref %T2) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- generic_with_virtual.carbon // CHECK:STDOUT: @@ -1333,7 +1386,7 @@ class T2(G2:! type) { // CHECK:STDOUT: .Base = // CHECK:STDOUT: .T1 = // CHECK:STDOUT: .base = %.loc9 -// CHECK:STDOUT: .F = %D1.F.decl +// CHECK:STDOUT: .F [hidden] = %D1.F.decl // CHECK:STDOUT: extend %Base // CHECK:STDOUT: } // CHECK:STDOUT: @@ -1409,7 +1462,7 @@ class T2(G2:! type) { // CHECK:STDOUT: .Base = // CHECK:STDOUT: .T = // CHECK:STDOUT: .base = %.loc8 -// CHECK:STDOUT: .F = %Derived.F.decl +// CHECK:STDOUT: .F [hidden] = %Derived.F.decl // CHECK:STDOUT: extend %Base.loc8_23.1 // CHECK:STDOUT: } // CHECK:STDOUT: } diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 0f3cd074a2c5..91eac80ea07b 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -100,8 +100,10 @@ static auto CloneBindingPattern(Context& context, SemIR::InstId pattern_id, // Makes a copy of the given pattern instruction, substituting values from a // specific as needed. The resulting pattern behaves like a newly-created // pattern, so is suitable for running `CalleePatternMatch` against. -static auto ClonePattern(Context& context, SemIR::SpecificId specific_id, - SemIR::InstId pattern_id) -> SemIR::InstId { +static auto ClonePattern( + Context& context, SemIR::SpecificId specific_id, SemIR::InstId pattern_id, + SemIR::TypeId self_type_override_id = SemIR::TypeId::None) + -> SemIR::InstId { if (!pattern_id.has_value()) { return SemIR::InstId::None; } @@ -123,8 +125,14 @@ static auto ClonePattern(Context& context, SemIR::SpecificId specific_id, // Finally, either a binding pattern or a return slot pattern. auto new_pattern_id = SemIR::InstId::None; if (auto binding = pattern.TryAs()) { - new_pattern_id = CloneBindingPattern(context, pattern_id, *binding, - get_type(pattern_id)); + auto type_id = get_type(pattern_id); + if (self_type_override_id.has_value() && + context.entity_names().Get(binding->entity_name_id).name_id == + SemIR::NameId::SelfValue) { + type_id = GetPatternType(context, self_type_override_id); + } + new_pattern_id = + CloneBindingPattern(context, pattern_id, *binding, type_id); } else if (auto return_slot = pattern.TryAs()) { auto new_subpattern_id = SemIR::InstId::None; auto subpattern = context.insts().Get(return_slot->subpattern_id); @@ -162,21 +170,24 @@ static auto ClonePattern(Context& context, SemIR::SpecificId specific_id, if (var_param && new_pattern_id != SemIR::ErrorInst::InstId) { new_pattern_id = RebuildPatternInst( context, var_param_id, - {.type_id = get_type(var_param_id), .subpattern_id = new_pattern_id}); + {.type_id = context.insts().Get(new_pattern_id).type_id(), + .subpattern_id = new_pattern_id}); } return new_pattern_id; } static auto ClonePatternBlock(Context& context, SemIR::SpecificId specific_id, - SemIR::InstBlockId inst_block_id) - -> SemIR::InstBlockId { + SemIR::InstBlockId inst_block_id, + SemIR::TypeId self_type_override_id = + SemIR::TypeId::None) -> SemIR::InstBlockId { if (!inst_block_id.has_value()) { return SemIR::InstBlockId::None; } return context.inst_blocks().Transform( inst_block_id, [&](SemIR::InstId inst_id) { - return ClonePattern(context, specific_id, inst_id); + return ClonePattern(context, specific_id, inst_id, + self_type_override_id); }); } @@ -206,6 +217,7 @@ static auto CloneTypeInstId(Context& context, SemIR::SpecificId specific_id, static auto CloneFunctionDecl(Context& context, SemIR::LocId loc_id, SemIR::FunctionId signature_id, SemIR::SpecificId signature_specific_id, + SemIR::TypeId signature_self_type_override_id, SemIR::FunctionId callee_id) -> std::pair { StartGenericDecl(context); @@ -215,7 +227,8 @@ static auto CloneFunctionDecl(Context& context, SemIR::LocId loc_id, // Clone the signature. context.pattern_block_stack().Push(); auto implicit_param_patterns_id = ClonePatternBlock( - context, signature_specific_id, signature.implicit_param_patterns_id); + context, signature_specific_id, signature.implicit_param_patterns_id, + signature_self_type_override_id); auto param_patterns_id = ClonePatternBlock(context, signature_specific_id, signature.param_patterns_id); auto return_pattern_id = @@ -447,12 +460,12 @@ auto BuildThunkDefinition(Context& context, auto BuildThunk(Context& context, SemIR::FunctionId signature_id, SemIR::SpecificId signature_specific_id, + SemIR::TypeId signature_self_type_override_id, SemIR::InstId callee_id, bool defer_definition) -> SemIR::InstId { auto callee = SemIR::GetCalleeAsFunction(context.sem_ir(), callee_id); // Check whether we can use the given function without a thunk. - // TODO: For virtual functions, we want different rules for checking `self`. // TODO: This is too strict; for example, we should not compare parameter // names here. if (context.functions().Get(callee.function_id).special_function_kind != @@ -460,7 +473,8 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, CheckFunctionTypeMatches( context, context.functions().Get(callee.function_id), context.functions().Get(signature_id), signature_specific_id, - /*check_syntax=*/false, /*check_self=*/true, /*diagnose=*/false)) { + /*check_syntax=*/false, signature_self_type_override_id, + /*diagnose=*/false)) { return callee_id; } @@ -490,9 +504,9 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, // We can't use the function directly. Build a thunk. // TODO: Check for and diagnose obvious reasons why this will fail, such as // arity mismatch, before trying to build the thunk. - auto [function_id, thunk_inst_id] = - CloneFunctionDecl(context, SemIR::LocId(callee_id), signature_id, - signature_specific_id, callee.function_id); + auto [function_id, thunk_inst_id] = CloneFunctionDecl( + context, SemIR::LocId(callee_id), signature_id, signature_specific_id, + signature_self_type_override_id, callee.function_id); auto thunk_id = context.sem_ir().thunks().Add({.callee_id = callee_id, diff --git a/toolchain/check/thunk.h b/toolchain/check/thunk.h index 9805e901dc70..5afd915d97d9 100644 --- a/toolchain/check/thunk.h +++ b/toolchain/check/thunk.h @@ -16,6 +16,7 @@ namespace Carbon::Check { // unchanged if it can be used directly. auto BuildThunk(Context& context, SemIR::FunctionId signature_id, SemIR::SpecificId signature_specific_id, + SemIR::TypeId signature_self_type_override_id, SemIR::InstId callee_id, bool defer_definition) -> SemIR::InstId; diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index a6b6810109d2..4fffd4cd0c91 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -707,7 +707,7 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, } llvm::CallInst* call; - if (function.virtual_modifier == SemIR::Function::VirtualModifier::None) { + if (function.virtual_index == -1) { auto* llvm_callee = function_info->llvm_function; auto describe_call = [&] { RawStringOstream out; diff --git a/toolchain/lower/testdata/class/virtual.carbon b/toolchain/lower/testdata/class/virtual.carbon index 01d4cc720951..257772d69ac5 100644 --- a/toolchain/lower/testdata/class/virtual.carbon +++ b/toolchain/lower/testdata/class/virtual.carbon @@ -184,6 +184,34 @@ fn Make() { var _: Derived; } +// --- thunk.carbon + +library "[[@TEST_NAME]]"; + +class From {} +class To {} + +impl From as Core.ImplicitAs(To) { + fn Convert[self: From]() -> To; +} + +base class Base { + virtual fn F[ref self: Self](n: From) -> To; +} + +class Derived { + extend base: Base; + override fn F[unused self: Self](unused n: To) -> From { + return {}; + } +} + +fn Use() { + var d: Derived = {.base = {}}; + let p: Base* = &d; + p->F({}); +} + // CHECK:STDOUT: ; ModuleID = 'classes.carbon' // CHECK:STDOUT: source_filename = "classes.carbon" // CHECK:STDOUT: @@ -730,9 +758,10 @@ fn Make() { // CHECK:STDOUT: %.loc14_32.7.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc14_32.6.base, i32 0, i32 0, !dbg !8 // CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %v.var, ptr align 8 @Derived.val.loc14_32.5, i64 8, i1 false), !dbg !8 // CHECK:STDOUT: store ptr @"_CDerived.Main.$vtable", ptr %.loc14_32.7.vptr, align 8, !dbg !8 -// CHECK:STDOUT: %Derived.F.call.vtable = load ptr, ptr %v.var, align 8, !dbg !10 -// CHECK:STDOUT: %Derived.F.call = call ptr @llvm.load.relative.i32(ptr %Derived.F.call.vtable, i32 0), !dbg !10 -// CHECK:STDOUT: call void %Derived.F.call(ptr %v.var), !dbg !10 +// CHECK:STDOUT: %.loc15_3.1.base = getelementptr inbounds nuw { { ptr } }, ptr %v.var, i32 0, i32 0, !dbg !10 +// CHECK:STDOUT: %Base.F.call.vtable = load ptr, ptr %.loc15_3.1.base, align 8, !dbg !10 +// CHECK:STDOUT: %Base.F.call = call ptr @llvm.load.relative.i32(ptr %Base.F.call.vtable, i32 0), !dbg !10 +// CHECK:STDOUT: call void %Base.F.call(ptr %.loc15_3.1.base), !dbg !10 // CHECK:STDOUT: call void @"_COp.12e0d0434542305a:core.Destroy.Core"(ptr %v.var), !dbg !7 // CHECK:STDOUT: ret void, !dbg !11 // CHECK:STDOUT: } @@ -1048,3 +1077,175 @@ fn Make() { // CHECK:STDOUT: !29 = !{!30} // CHECK:STDOUT: !30 = !DILocalVariable(arg: 1, scope: !28, type: !12) // CHECK:STDOUT: !31 = !DILocation(line: 5, column: 3, scope: !28) +// CHECK:STDOUT: ; ModuleID = 'thunk.carbon' +// CHECK:STDOUT: source_filename = "thunk.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:thunk:Base.Main:Derived.Main" to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)] +// CHECK:STDOUT: @From.val = internal constant {} zeroinitializer +// CHECK:STDOUT: @From.val.loc18_14 = internal constant {} zeroinitializer +// CHECK:STDOUT: @Derived.val.loc23_31.5 = internal constant { { ptr } } poison +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @"_CConvert.From.Main:ImplicitAs.a0663b0b66554b38.Core"(ptr sret({}), ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CF.Base.Main(ptr sret({}), ptr, ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CF.Derived.Main(ptr sret({}) %return, ptr %self, ptr %n) #0 !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %return, ptr align 1 @From.val.loc18_14, i64 0, i1 false), !dbg !11 +// CHECK:STDOUT: ret void, !dbg !11 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline nounwind +// CHECK:STDOUT: define void @"_CF:thunk:Base.Main:Derived.Main"(ptr sret({}) %return, ptr %self, ptr %n) #1 !dbg !12 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc17_58.1.temp = alloca {}, align 8, !dbg !16 +// CHECK:STDOUT: %.loc12_33.1.temp = alloca {}, align 8, !dbg !17 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc17_58.1.temp), !dbg !16 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc12_33.1.temp), !dbg !17 +// CHECK:STDOUT: call void @"_CConvert.From.Main:ImplicitAs.a0663b0b66554b38.Core"(ptr %.loc12_33.1.temp, ptr %n), !dbg !17 +// CHECK:STDOUT: call void @_CF.Derived.Main(ptr %.loc17_58.1.temp, ptr %self, ptr %.loc12_33.1.temp), !dbg !16 +// CHECK:STDOUT: call void @"_CConvert.From.Main:ImplicitAs.a0663b0b66554b38.Core"(ptr %return, ptr %.loc17_58.1.temp), !dbg !16 +// CHECK:STDOUT: call void @"_COp.b59cd0c4bb4fc669:core.Destroy.Core"(ptr %.loc17_58.1.temp), !dbg !16 +// CHECK:STDOUT: call void @"_COp.17affc8c9ed5036b:core.Destroy.Core"(ptr %.loc12_33.1.temp), !dbg !17 +// CHECK:STDOUT: ret void, !dbg !16 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.b59cd0c4bb4fc669:core.Destroy.Core"(ptr %self) #0 !dbg !18 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !23 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.17affc8c9ed5036b:core.Destroy.Core"(ptr %self) #0 !dbg !24 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !27 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CUse.Main() #0 !dbg !28 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %d.var = alloca { { ptr } }, align 8, !dbg !31 +// CHECK:STDOUT: %.loc25_10.1.temp = alloca {}, align 8, !dbg !32 +// CHECK:STDOUT: %.loc25_9.2.temp = alloca {}, align 8, !dbg !33 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %d.var), !dbg !31 +// CHECK:STDOUT: %.loc23_31.2.base = getelementptr inbounds nuw { { ptr } }, ptr %d.var, i32 0, i32 0, !dbg !34 +// CHECK:STDOUT: %.loc23_30.2.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc23_31.2.base, i32 0, i32 0, !dbg !35 +// CHECK:STDOUT: %.loc23_31.6.base = getelementptr inbounds nuw { { ptr } }, ptr %d.var, i32 0, i32 0, !dbg !34 +// CHECK:STDOUT: %.loc23_31.7.vptr = getelementptr inbounds nuw { ptr }, ptr %.loc23_31.6.base, i32 0, i32 0, !dbg !34 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %d.var, ptr align 8 @Derived.val.loc23_31.5, i64 8, i1 false), !dbg !34 +// CHECK:STDOUT: store ptr @"_CDerived.Main.$vtable", ptr %.loc23_31.7.vptr, align 8, !dbg !34 +// CHECK:STDOUT: %.loc24_18.2.base = getelementptr inbounds nuw { { ptr } }, ptr %d.var, i32 0, i32 0, !dbg !36 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc25_10.1.temp), !dbg !32 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc25_9.2.temp), !dbg !33 +// CHECK:STDOUT: %Base.F.call.vtable = load ptr, ptr %.loc25_10.1.temp, align 8, !dbg !32 +// CHECK:STDOUT: %Base.F.call = call ptr @llvm.load.relative.i32(ptr %Base.F.call.vtable, i32 0), !dbg !32 +// CHECK:STDOUT: call void %Base.F.call(ptr %.loc25_10.1.temp, ptr %.loc24_18.2.base, ptr @From.val.loc18_14), !dbg !32 +// CHECK:STDOUT: call void @"_COp.17affc8c9ed5036b:core.Destroy.Core"(ptr %.loc25_10.1.temp), !dbg !32 +// CHECK:STDOUT: call void @"_COp.b59cd0c4bb4fc669:core.Destroy.Core"(ptr @From.val), !dbg !33 +// CHECK:STDOUT: call void @"_COp.12e0d0434542305a:core.Destroy.Core"(ptr %d.var), !dbg !31 +// CHECK:STDOUT: ret void, !dbg !37 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.a8f528cd70a29ff8:core.Destroy.Core"(ptr %self) #0 !dbg !38 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !41 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.96a711151205ee34:core.Destroy.Core"(ptr %self) #0 !dbg !42 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !45 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.4bfa84f6e0b7c617:core.Destroy.Core"(ptr %self) #0 !dbg !46 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !49 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.12e0d0434542305a:core.Destroy.Core"(ptr %self) #0 !dbg !50 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !53 +// CHECK:STDOUT: } +// 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) #2 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #3 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read) +// CHECK:STDOUT: declare ptr @llvm.load.relative.i32(ptr, i32) #4 +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 } +// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 4, 3, 2, 1, 0 } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nounwind } +// CHECK:STDOUT: attributes #1 = { alwaysinline nounwind } +// CHECK:STDOUT: attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #3 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #4 = { 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_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "thunk.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Derived.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !8) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{!7, !7, !7} +// CHECK:STDOUT: !7 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +// CHECK:STDOUT: !8 = !{!9, !10} +// CHECK:STDOUT: !9 = !DILocalVariable(arg: 1, scope: !4, type: !7) +// CHECK:STDOUT: !10 = !DILocalVariable(arg: 2, scope: !4, type: !7) +// CHECK:STDOUT: !11 = !DILocation(line: 18, column: 5, scope: !4) +// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "F", linkageName: "_CF:thunk:Base.Main:Derived.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !13) +// CHECK:STDOUT: !13 = !{!14, !15} +// CHECK:STDOUT: !14 = !DILocalVariable(arg: 1, scope: !12, type: !7) +// CHECK:STDOUT: !15 = !DILocalVariable(arg: 2, scope: !12, type: !7) +// CHECK:STDOUT: !16 = !DILocation(line: 17, column: 3, scope: !12) +// CHECK:STDOUT: !17 = !DILocation(line: 12, column: 32, scope: !12) +// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "Op", linkageName: "_COp.b59cd0c4bb4fc669:core.Destroy.Core", scope: null, file: !3, line: 17, type: !19, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !21) +// CHECK:STDOUT: !19 = !DISubroutineType(types: !20) +// CHECK:STDOUT: !20 = !{null, !7} +// CHECK:STDOUT: !21 = !{!22} +// CHECK:STDOUT: !22 = !DILocalVariable(arg: 1, scope: !18, type: !7) +// CHECK:STDOUT: !23 = !DILocation(line: 17, column: 3, scope: !18) +// CHECK:STDOUT: !24 = distinct !DISubprogram(name: "Op", linkageName: "_COp.17affc8c9ed5036b:core.Destroy.Core", scope: null, file: !3, line: 12, type: !19, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !25) +// CHECK:STDOUT: !25 = !{!26} +// CHECK:STDOUT: !26 = !DILocalVariable(arg: 1, scope: !24, type: !7) +// CHECK:STDOUT: !27 = !DILocation(line: 12, column: 32, scope: !24) +// CHECK:STDOUT: !28 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Main", scope: null, file: !3, line: 22, type: !29, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !29 = !DISubroutineType(types: !30) +// CHECK:STDOUT: !30 = !{null} +// CHECK:STDOUT: !31 = !DILocation(line: 23, column: 3, scope: !28) +// CHECK:STDOUT: !32 = !DILocation(line: 25, column: 3, scope: !28) +// CHECK:STDOUT: !33 = !DILocation(line: 25, column: 8, scope: !28) +// CHECK:STDOUT: !34 = !DILocation(line: 23, column: 20, scope: !28) +// CHECK:STDOUT: !35 = !DILocation(line: 23, column: 29, scope: !28) +// CHECK:STDOUT: !36 = !DILocation(line: 24, column: 18, scope: !28) +// CHECK:STDOUT: !37 = !DILocation(line: 22, column: 1, scope: !28) +// CHECK:STDOUT: !38 = distinct !DISubprogram(name: "Op", linkageName: "_COp.a8f528cd70a29ff8:core.Destroy.Core", scope: null, file: !3, line: 23, type: !19, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !39) +// CHECK:STDOUT: !39 = !{!40} +// CHECK:STDOUT: !40 = !DILocalVariable(arg: 1, scope: !38, type: !7) +// CHECK:STDOUT: !41 = !DILocation(line: 23, column: 3, scope: !38) +// CHECK:STDOUT: !42 = distinct !DISubprogram(name: "Op", linkageName: "_COp.96a711151205ee34:core.Destroy.Core", scope: null, file: !3, line: 23, type: !19, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !43) +// CHECK:STDOUT: !43 = !{!44} +// CHECK:STDOUT: !44 = !DILocalVariable(arg: 1, scope: !42, type: !7) +// CHECK:STDOUT: !45 = !DILocation(line: 23, column: 3, scope: !42) +// CHECK:STDOUT: !46 = distinct !DISubprogram(name: "Op", linkageName: "_COp.4bfa84f6e0b7c617:core.Destroy.Core", scope: null, file: !3, line: 23, type: !19, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !47) +// CHECK:STDOUT: !47 = !{!48} +// CHECK:STDOUT: !48 = !DILocalVariable(arg: 1, scope: !46, type: !7) +// CHECK:STDOUT: !49 = !DILocation(line: 23, column: 3, scope: !46) +// CHECK:STDOUT: !50 = distinct !DISubprogram(name: "Op", linkageName: "_COp.12e0d0434542305a:core.Destroy.Core", scope: null, file: !3, line: 23, type: !19, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !51) +// CHECK:STDOUT: !51 = !{!52} +// CHECK:STDOUT: !52 = !DILocalVariable(arg: 1, scope: !50, type: !7) +// CHECK:STDOUT: !53 = !DILocation(line: 23, column: 3, scope: !50) diff --git a/toolchain/sem_ir/dump.cpp b/toolchain/sem_ir/dump.cpp index 5851a5c4f4ae..ea3d1b10446d 100644 --- a/toolchain/sem_ir/dump.cpp +++ b/toolchain/sem_ir/dump.cpp @@ -460,6 +460,9 @@ LLVM_DUMP_METHOD auto Dump(const File& file, const NameScope& name_scope) case AccessKind::Private: out << "private "; break; + case AccessKind::Hidden: + out << "hidden "; + break; } out << DumpInstSummary(file, entry.result.target_inst_id()); } else { diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index bca624bed22d..2b86c6db23cc 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -852,6 +852,9 @@ auto Formatter::FormatNameScope(NameScopeId id, llvm::StringRef label) -> void { case AccessKind::Private: out() << " [private]"; break; + case AccessKind::Hidden: + out() << " [hidden]"; + break; } out() << " = "; if (result.is_poisoned()) { diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index 535d4b65c608..1c4cdc5c8a45 100644 --- a/toolchain/sem_ir/function.h +++ b/toolchain/sem_ir/function.h @@ -134,7 +134,8 @@ struct FunctionFields { VirtualModifier virtual_modifier = VirtualModifier::None; // The index of the vtable slot for this virtual function. -1 if the function - // is not virtual (ie: (virtual_modifier == None) == (virtual_index == -1)). + // is not in the vtable. A function with `virtual_modifier != None` may still + // have `virtual_index == -1` if the corresponding vtable entry is a thunk. int32_t virtual_index = -1; // Which, if any, evaluation modifier (eval or musteval) is applied to this diff --git a/toolchain/sem_ir/name_scope.h b/toolchain/sem_ir/name_scope.h index b5de2c07b819..0cb68538e7f4 100644 --- a/toolchain/sem_ir/name_scope.h +++ b/toolchain/sem_ir/name_scope.h @@ -15,9 +15,14 @@ namespace Carbon::SemIR { // Access control for an entity. enum class AccessKind : int8_t { + // Accessible to all code. Public, + // Accessible to the enclosing class and derived classes. Protected, + // Only accessible to the enclosing class and friends. Private, + // Not accessible to any code, but can still be redeclared. + Hidden, }; // Represents the result of a name lookup.