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
This commit is contained in:
Richard Smith
2026-05-13 17:40:45 +00:00
committed by GitHub
parent 0b47efa57a
commit c33fb9fc48
18 changed files with 427 additions and 110 deletions
+20 -7
View File
@@ -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<SemIR::FunctionDecl>(*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<SemIR::FunctionDecl>(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<SemIR::SpecificFunction>(
derived_vtable_entry_id)) {
+1
View File
@@ -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;
}
}
+4 -3
View File
@@ -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,
+11 -9
View File
@@ -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
+4 -4
View File
@@ -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;
}
+6
View File
@@ -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;
}
+26 -16
View File
@@ -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 &&
+9 -8
View File
@@ -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
+2
View File
@@ -268,6 +268,8 @@ static auto IsAccessProhibited(std::optional<AccessInfo> access_info,
return access_info->highest_allowed_access !=
SemIR::AccessKind::Private ||
is_parent_access;
case SemIR::AccessKind::Hidden:
return true;
}
}
+97 -44
View File
@@ -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 `<pattern for T2*>` of parameter 1 in redeclaration differs from previous parameter type `<pattern for T1*>` [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 `<pattern for T>` of parameter 1 in redeclaration differs from previous parameter type `<pattern for T*>` [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 = <poisoned>
// 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 = <poisoned>
// 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 = <poisoned>
// 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: <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: <elided>
// 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: <elided>
// 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: <elided>
// 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: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: complete_type_witness = %complete_type
@@ -1107,7 +1137,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Base = <poisoned>
// CHECK:STDOUT: .base = %.loc21
// CHECK:STDOUT: .T2 = <poisoned>
// 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> = 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> = bound_method %Derived.F.call, %impl.elem0
// CHECK:STDOUT: <elided>
// 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> = 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 = <poisoned>
// CHECK:STDOUT: .T1 = <poisoned>
// 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 = <poisoned>
// CHECK:STDOUT: .T = <poisoned>
// 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: }
+28 -14
View File
@@ -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<SemIR::AnyBindingPattern>()) {
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<SemIR::ReturnSlotPattern>()) {
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<SemIR::VarParamPattern>(
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<SemIR::FunctionId, SemIR::InstId> {
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,
+1
View File
@@ -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;
+1 -1
View File
@@ -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;
+204 -3
View File
@@ -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)
+3
View File
@@ -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 {
+3
View File
@@ -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()) {
+2 -1
View File
@@ -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
+5
View File
@@ -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.