diff --git a/toolchain/check/context.h b/toolchain/check/context.h index c3881e443c9d..b03999756985 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -363,6 +363,7 @@ class Context { return sem_ir().cpp_overload_sets(); } auto functions() -> SemIR::FunctionStore& { return sem_ir().functions(); } + auto thunks() -> SemIR::ThunkStore& { return sem_ir().thunks(); } auto classes() -> SemIR::ClassStore& { return sem_ir().classes(); } auto fields() -> SemIR::FieldStore& { return sem_ir().fields(); } auto vtables() -> SemIR::VtableStore& { return sem_ir().vtables(); } diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index 2926abdb9cc2..31f4bb6ca598 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -99,6 +99,8 @@ auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id, decl_context = namespace_decl; } else if (inst.Is()) { // TODO: Provide a source location. + // TODO: This duplicates work done by ExportClassToCpp. Factor out the + // shared code! auto* record_decl = clang::CXXRecordDecl::Create( context.ast_context(), clang::TagTypeKind::Class, decl_context, clang::SourceLocation(), clang::SourceLocation(), identifier_info); @@ -164,6 +166,7 @@ auto ExportClassToCpp(Context& context, SemIR::LocId loc_id, record_decl->setAccess(clang::AS_public); } + decl_context->addHiddenDecl(record_decl); record_decl->setHasExternalLexicalStorage(); record_decl->setHasExternalVisibleStorage(); @@ -384,6 +387,23 @@ static auto MapToCppThunkParamType(Context& context, SemIR::TypeId type_id) context.ast_context().getConstType(cpp_type)); } +// Build FunctionInfo for an export of the given Carbon function. Exports the +// name scope if necessary. +static auto BuildFunctionInfo(Context& context, SemIR::LocId loc_id, + SemIR::FunctionId callee_function_id) + -> std::optional { + const SemIR::Function& callee = context.functions().Get(callee_function_id); + + // Map the parent scope into the C++ AST. + auto* decl_context = + ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id); + if (!decl_context) { + return std::nullopt; + } + + return FunctionInfo(context, callee_function_id, callee, decl_context); +} + // Create a `clang::FunctionDecl` for the given Carbon function. This // can be used to call the Carbon function from C++. The Carbon // function's ABI must be compatible with C++. @@ -459,14 +479,55 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context, return function_decl; } -// Create the declaration of the C++ thunk. -static auto BuildCppToCarbonThunkDecl( - Context& context, SemIR::LocId loc_id, const FunctionInfo& target, - clang::DeclarationName thunk_name, - llvm::ArrayRef thunk_param_types) -> clang::FunctionDecl* { - clang::ASTContext& ast_context = context.ast_context(); +// Returns whether the given Carbon parameter should be passed as a C++ const +// reference. +static auto PassAsConstRef(Context& /*context*/, + const FunctionInfo::Param& param, + clang::QualType cpp_type) -> bool { + // Use pass-by-const-ref for value parameters of array type. + // TODO: Should we do this for value parameters of any type that uses a + // pointer value representation? + return param.kind == ParamPatternKind::Value && cpp_type->isArrayType(); +} - auto clang_loc = GetCppLocation(context, loc_id); +// Converts a Carbon parameter type to the parameter type that should be exposed +// to C++ callers. +static auto MapToCppParamType(Context& context, SemIR::LocId loc_id, + const FunctionInfo::Param& param) + -> clang::QualType { + auto cpp_type = MapToCppType(context, param.type_id); + if (cpp_type.isNull()) { + return clang::QualType(); + } + if (param.kind == Check::ParamPatternKind::Ref) { + cpp_type = context.ast_context().getLValueReferenceType(cpp_type); + } else if (PassAsConstRef(context, param, cpp_type)) { + cpp_type = context.ast_context().getLValueReferenceType( + context.ast_context().getConstType(cpp_type)); + } else if (cpp_type->isArrayType()) { + // C++ doesn't support passing arrays by value. + context.TODO(loc_id, "by-var array parameter"); + return clang::QualType(); + } + return cpp_type; +} + +// Returns the C++ function type (`clang::FunctionProtoType`) to use for a C++ +// thunk calling a Carbon function. +static auto BuildCppToCarbonThunkFunctionType(Context& context, + SemIR::LocId loc_id, + const FunctionInfo& target) + -> const clang::FunctionProtoType* { + llvm::SmallVector thunk_param_types; + thunk_param_types.reserve(target.explicit_params.size()); + for (auto param : target.explicit_params) { + auto cpp_type = MapToCppParamType(context, loc_id, param); + if (cpp_type.isNull()) { + context.TODO(loc_id, "failed to map C++ type to Carbon"); + return nullptr; + } + thunk_param_types.push_back(cpp_type); + } // Get the C++ return type (this corresponds to the return type of the // target Carbon function). @@ -485,17 +546,50 @@ static auto BuildCppToCarbonThunkDecl( } } - clang::DeclarationNameInfo name_info(thunk_name, clang_loc); - auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); if (target.self_param && target.self_param->kind == ParamPatternKind::Ref) { ext_proto_info.RefQualifier = clang::RQ_LValue; } - clang::QualType thunk_function_type = ast_context.getFunctionType( - cpp_return_type, thunk_param_types, ext_proto_info); + return context.ast_context() + .getFunctionType(cpp_return_type, thunk_param_types, ext_proto_info) + ->getAs(); +} - auto* tinfo = - ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc); +// Create the declaration of the C++ thunk. +static auto BuildCppToCarbonThunkDecl(Context& context, SemIR::LocId loc_id, + const FunctionInfo& target, + clang::DeclarationName thunk_name) + -> clang::FunctionDecl* { + clang::ASTContext& ast_context = context.ast_context(); + + auto clang_loc = GetCppLocation(context, loc_id); + + // If the signature was imported from C++, use that declaration to form the + // parameter types rather than (lossily) re-exporting the Carbon signature + // back to C++. + const clang::FunctionProtoType* thunk_function_type = nullptr; + if (auto thunk_id = target.function.thunk_id(); thunk_id.has_value()) { + const auto& thunk = context.thunks().Get(thunk_id); + const auto& thunk_signature = context.functions().Get(thunk.signature_id); + if (const auto* clang_decl = + context.clang_decls().Lookup(thunk_signature.first_decl_id())) { + thunk_function_type = cast(clang_decl->decl()) + ->getType() + ->getAs(); + } + } + if (!thunk_function_type) { + thunk_function_type = + BuildCppToCarbonThunkFunctionType(context, loc_id, target); + if (!thunk_function_type) { + return nullptr; + } + } + + clang::DeclarationNameInfo name_info(thunk_name, clang_loc); + + auto* tinfo = ast_context.getTrivialTypeSourceInfo( + clang::QualType(thunk_function_type, 0), clang_loc); bool uses_fp_intrin = false; bool inline_specified = true; @@ -506,25 +600,31 @@ static auto BuildCppToCarbonThunkDecl( if (auto* parent_class = dyn_cast(target.decl_context)) { thunk_function_decl = clang::CXXMethodDecl::Create( - ast_context, parent_class, clang_loc, name_info, thunk_function_type, - tinfo, target.GetStorageClass(), uses_fp_intrin, inline_specified, + ast_context, parent_class, clang_loc, name_info, + clang::QualType(thunk_function_type, 0), tinfo, + target.GetStorageClass(), uses_fp_intrin, inline_specified, constexpr_kind, clang_loc, trailing_requires_clause); // TODO: Map Carbon access to C++ access. thunk_function_decl->setAccess(clang::AS_public); + // Carbon overriders are non-virtual in C++; only the corresponding thunk is + // virtual. thunk_function_decl->setVirtualAsWritten( target.function.virtual_modifier != - SemIR::Function::VirtualModifier::None); + SemIR::Function::VirtualModifier::None && + target.function.virtual_modifier != + SemIR::Function::VirtualModifier::Override); + // TODO: Call setIsPureVirtual if VirtualModifier::Abstract is present. } else { thunk_function_decl = clang::FunctionDecl::Create( ast_context, target.decl_context, clang_loc, name_info, - thunk_function_type, tinfo, clang::SC_None, uses_fp_intrin, - inline_specified, + clang::QualType(thunk_function_type, 0), tinfo, clang::SC_None, + uses_fp_intrin, inline_specified, /*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause); } target.decl_context->addHiddenDecl(thunk_function_decl); llvm::SmallVector param_var_decls; - for (auto [i, type] : llvm::enumerate(thunk_param_types)) { + for (auto [i, type] : llvm::enumerate(thunk_function_type->param_types())) { clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create( ast_context, thunk_function_decl, /*StartLoc=*/clang_loc, /*IdLoc=*/clang_loc, /*Id=*/nullptr, type, @@ -632,84 +732,6 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema, clang_loc); } -// Returns whether the given Carbon parameter should be passed as a C++ const -// reference. -static auto PassAsConstRef(Context& /*context*/, - const FunctionInfo::Param& param, - clang::QualType cpp_type) -> bool { - // Use pass-by-const-ref for value parameters of array type. - // TODO: Should we do this for value parameters of any type that uses a - // pointer value representation? - return param.kind == ParamPatternKind::Value && cpp_type->isArrayType(); -} - -// Converts a Carbon parameter type to the parameter type that should be exposed -// to C++ callers. -static auto MapToCppParamType(Context& context, SemIR::LocId loc_id, - const FunctionInfo::Param& param) - -> clang::QualType { - auto cpp_type = MapToCppType(context, param.type_id); - if (cpp_type.isNull()) { - return clang::QualType(); - } - if (param.kind == Check::ParamPatternKind::Ref) { - cpp_type = context.ast_context().getLValueReferenceType(cpp_type); - } else if (PassAsConstRef(context, param, cpp_type)) { - cpp_type = context.ast_context().getLValueReferenceType( - context.ast_context().getConstType(cpp_type)); - } else if (cpp_type->isArrayType()) { - // C++ doesn't support passing arrays by value. - context.TODO(loc_id, "by-var array parameter"); - return clang::QualType(); - } - return cpp_type; -} - -// Create a C++ thunk that calls the Carbon thunk. The C++ thunk's -// parameter types are mapped from the parameters of the target function -// with `MapToCppType`. (Note that the target function here is the -// callee of the Carbon thunk.) -static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id, - const FunctionInfo& target, - llvm::StringRef thunk_name, - clang::FunctionDecl* carbon_function_decl) - -> clang::FunctionDecl* { - auto& thunk_ident = context.ast_context().Idents.get(thunk_name); - - llvm::SmallVector param_types; - for (auto param : target.explicit_params) { - auto cpp_type = MapToCppParamType(context, loc_id, param); - if (cpp_type.isNull()) { - context.TODO(loc_id, "failed to map C++ type to Carbon"); - return nullptr; - } - param_types.push_back(cpp_type); - } - - auto* thunk_function_decl = BuildCppToCarbonThunkDecl( - context, loc_id, target, &thunk_ident, param_types); - if (!thunk_function_decl) { - return nullptr; - } - - // Build the thunk function body. - clang::Sema& sema = context.clang_sema(); - clang::Sema::ContextRAII context_raii(sema, thunk_function_decl); - // Ensure that the evaluation context is not `Unevaluated`, as that - // would cause code generation to fail. - clang::EnterExpressionEvaluationContext evaluated( - sema, clang::Sema::ExpressionEvaluationContext::PotentiallyEvaluated); - sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl); - clang::StmtResult body = BuildCppToCarbonThunkBody( - sema, target, thunk_function_decl, carbon_function_decl); - sema.ActOnFinishFunctionBody(thunk_function_decl, body.get()); - CARBON_CHECK(!body.isInvalid()); - - context.clang_sema().getASTConsumer().HandleTopLevelDecl( - clang::DeclGroupRef(thunk_function_decl)); - return thunk_function_decl; -} - // Create a Carbon thunk that calls `callee`. The thunk's parameters are // all references to the callee parameter type. // @@ -759,15 +781,30 @@ static auto BuildCarbonToCarbonThunk(Context& context, SemIR::LocId loc_id, return carbon_thunk_function_id; } -// Creates a `clang::FunctionDecl` that calls the Carbon function in -// `target`. The `extra_name` string is appended to the Carbon thunk's -// name. -// -// Returns nullptr if an error occurs. -auto ExportNonGenericFunctionToCpp(Context& context, SemIR::LocId loc_id, - const FunctionInfo& target, - std::string_view extra_name = "") +static auto ExportNonGenericFunctionDeclToCpp(Context& context, + SemIR::LocId loc_id, + const FunctionInfo& target) -> clang::FunctionDecl* { + auto& thunk_ident = context.ast_context().Idents.get( + context.names().GetFormatted(target.function.name_id)); + + return BuildCppToCarbonThunkDecl(context, loc_id, target, &thunk_ident); +} + +auto ExportVirtualFunctionDeclToCpp(Context& context, SemIR::LocId loc_id, + clang::CXXRecordDecl* parent, + SemIR::FunctionId function_id) + -> clang::CXXMethodDecl* { + FunctionInfo target(context, function_id, + context.functions().Get(function_id), parent); + return cast_or_null( + ExportNonGenericFunctionDeclToCpp(context, loc_id, target)); +} + +static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id, + const FunctionInfo& target, + clang::FunctionDecl* thunk_function_decl, + std::string_view extra_name) -> void { // Create a Carbon thunk that calls the callee. The thunk's parameters // are all references so that the ABI is compatible with C++ callers. auto carbon_thunk_function_id = @@ -777,14 +814,53 @@ auto ExportNonGenericFunctionToCpp(Context& context, SemIR::LocId loc_id, auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn( context, loc_id, carbon_thunk_function_id); if (!carbon_function_decl) { + return; + } + + // Build the thunk function body. + clang::Sema& sema = context.clang_sema(); + clang::Sema::ContextRAII context_raii(sema, thunk_function_decl); + // Ensure that the evaluation context is not `Unevaluated`, as that + // would cause code generation to fail. + clang::EnterExpressionEvaluationContext evaluated( + sema, clang::Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl); + clang::StmtResult body = BuildCppToCarbonThunkBody( + sema, target, thunk_function_decl, carbon_function_decl); + sema.ActOnFinishFunctionBody(thunk_function_decl, body.get()); + CARBON_CHECK(!body.isInvalid()); + + context.clang_sema().getASTConsumer().HandleTopLevelDecl( + clang::DeclGroupRef(thunk_function_decl)); +} + +auto DefineExportedVirtualFunction(Context& context, SemIR::LocId loc_id, + SemIR::FunctionId callee_function_id, + clang::CXXMethodDecl* method_decl) -> void { + const SemIR::Function& callee = context.functions().Get(callee_function_id); + FunctionInfo target_function_info(context, callee_function_id, callee, + method_decl->getDeclContext()); + BuildCppToCarbonThunk(context, loc_id, target_function_info, method_decl, ""); +} + +// Creates a `clang::FunctionDecl` that calls the Carbon function in +// `target`. The `extra_name` string is appended to the Carbon thunk's +// name. +// +// Returns nullptr if an error occurs. +auto ExportNonGenericFunctionToCpp(Context& context, SemIR::LocId loc_id, + const FunctionInfo& target, + std::string_view extra_name = "") + -> clang::FunctionDecl* { + auto* thunk_function_decl = + ExportNonGenericFunctionDeclToCpp(context, loc_id, target); + if (!thunk_function_decl) { return nullptr; } - // Create a C++ thunk that calls the Carbon thunk. - return BuildCppToCarbonThunk( - context, loc_id, target, - context.names().GetFormatted(target.function.name_id), - carbon_function_decl); + BuildCppToCarbonThunk(context, loc_id, target, thunk_function_decl, + extra_name); + return thunk_function_decl; } auto ExportFunctionSpecializationToCpp( @@ -946,23 +1022,16 @@ static auto ExportGenericFunctionToCpp(Context& context, SemIR::LocId loc_id, auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id, SemIR::FunctionId callee_function_id) -> clang::NamedDecl* { - const SemIR::Function& callee = context.functions().Get(callee_function_id); - - // Map the parent scope into the C++ AST. - auto* decl_context = - ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id); - if (!decl_context) { + auto target = BuildFunctionInfo(context, loc_id, callee_function_id); + if (!target) { return nullptr; } - FunctionInfo target_function_info(context, callee_function_id, callee, - decl_context); - - if (callee.generic_id.has_value()) { - return ExportGenericFunctionToCpp(context, loc_id, target_function_info); + if (target->function.generic_id.has_value()) { + return ExportGenericFunctionToCpp(context, loc_id, *target); } - return ExportNonGenericFunctionToCpp(context, loc_id, target_function_info); + return ExportNonGenericFunctionToCpp(context, loc_id, *target); } // Returns whether the given class has any abstract methods. diff --git a/toolchain/check/cpp/export.h b/toolchain/check/cpp/export.h index 9cb940617636..9b505938f74e 100644 --- a/toolchain/check/cpp/export.h +++ b/toolchain/check/cpp/export.h @@ -11,6 +11,7 @@ namespace clang { class CXXDestructorDecl; +class CXXMethodDecl; class CXXRecordDecl; } // namespace clang @@ -57,6 +58,19 @@ auto ExportFieldToCpp(Context& context, SemIR::InstId field_inst_id, auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id, SemIR::FunctionId function_id) -> clang::NamedDecl*; +// Exports a Carbon virtual function as a C++ `clang::FunctionDecl` declaration. +// Does not emit a definition. +auto ExportVirtualFunctionDeclToCpp(Context& context, SemIR::LocId loc_id, + clang::CXXRecordDecl* parent, + SemIR::FunctionId callee_function_id) + -> clang::CXXMethodDecl*; + +// Defines an virtual function that was previously exported to C++ with +// ExportVirtualFunctionDeclToCpp. +auto DefineExportedVirtualFunction(Context& context, SemIR::LocId loc_id, + SemIR::FunctionId callee_function_id, + clang::CXXMethodDecl* method_decl) -> void; + // Creates a C++ function template specialization for a generic Carbon // function. // diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 6188983fc43c..16ef4621b14b 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -510,7 +510,6 @@ auto CarbonExternalASTSource::GetOrExportFunctionToCpp( clang_function_decl, context_->clang_decl_signatures().Add(std::move(thunk_signature))), .inst_id = function.first_decl_id()}); - return clang_function_decl; } @@ -687,6 +686,15 @@ auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void { // TODO: Import any special member functions that affect class properties. + // Virtual functions whose definitions we have deferred generating until the + // class is complete. + struct PendingVirtualFunction { + SemIR::LocId loc_id; + SemIR::FunctionId function_id; + clang::CXXMethodDecl* method_decl; + }; + llvm::SmallVector pending_virtual_functions; + if (class_info.vtable_decl_id.has_value()) { auto vtable_inst_block = context_->inst_blocks().Get( context_->vtables() @@ -699,37 +707,42 @@ auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void { continue; } - // The Carbon vtable entry for a function override is a thunk, which wraps - // the function declaration to expose the signature of the overridden - // virtual function. Here we want to generate a C++ method declaration for - // the override, so we need to look through the thunk wrapping. - const auto [callee_function, function] = - [&]() -> std::pair { - auto vtable_callee_function = - GetCalleeAsFunction(context_->sem_ir(), vtable_entry_id); - const SemIR::Function& vtable_function = - context_->functions().Get(vtable_callee_function.function_id); - if (!vtable_function.thunk_id().has_value()) { - return {vtable_callee_function, vtable_function}; - } - auto vtable_thunk = - context_->sem_ir().thunks().Get(vtable_function.thunk_id()); - auto callee_function = - GetCalleeAsFunction(context_->sem_ir(), vtable_thunk.callee_id); - return {callee_function, - context_->functions().Get(callee_function.function_id)}; - }(); + const auto callee_function = + GetCalleeAsFunction(context_->sem_ir(), vtable_entry_id); + const SemIR::Function& function = + context_->functions().Get(callee_function.function_id); // If this is a member of a base class, nothing to do here. if (function.parent_scope_id != class_info.scope_id) { continue; } - auto* method_decl = cast(GetOrExportFunctionToCpp( - vtable_entry_id, callee_function.function_id)); + auto* method_decl = + cast_or_null(ExportVirtualFunctionDeclToCpp( + *context_, SemIR::LocId(vtable_entry_id), class_decl, + callee_function.function_id)); + if (!method_decl) { + continue; + } context_->clang_sema().AddOverriddenMethods(class_decl, method_decl); + context_->clang_decls().Add( + {.key = SemIR::ClangDeclKey::ForFunctionDecl( + method_decl, + MakeVirtualFunctionSignature(*context_, method_decl)), + .inst_id = function.first_decl_id()}); + pending_virtual_functions.push_back( + {.loc_id = SemIR::LocId(vtable_entry_id), + .function_id = callee_function.function_id, + .method_decl = method_decl}); } } class_decl->completeDefinition(); + + // Now the class is complete, we can define the virtual function thunks. + for (auto virtual_fn : pending_virtual_functions) { + DefineExportedVirtualFunction(*context_, virtual_fn.loc_id, + virtual_fn.function_id, + virtual_fn.method_decl); + } } auto CarbonExternalASTSource::layoutRecordType( diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index 3814497cd87f..7b1261e125b5 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -903,11 +903,8 @@ static auto GetVirtualFunctionParamPassingMode(clang::QualType type) return SemIR::ClangDeclSignature::PassingMode::ByVar; } -// Computes the signature to use for the given imported virtual function. Unlike -// with regular imported functions, we can only use a single signature here, so -// we pick one conservatively. -static auto MakeVirtualFunctionSignature( - Context& context, const clang::CXXMethodDecl* method_decl) +auto MakeVirtualFunctionSignature(Context& context, + const clang::CXXMethodDecl* method_decl) -> SemIR::ClangDeclSignatureId { SemIR::ClangDeclSignature signature = { .kind = SemIR::ClangDeclSignature::Normal, diff --git a/toolchain/check/cpp/import.h b/toolchain/check/cpp/import.h index 224ddd1c9c88..c9d7d2e6b857 100644 --- a/toolchain/check/cpp/import.h +++ b/toolchain/check/cpp/import.h @@ -114,6 +114,13 @@ auto ImportClassDefinitionForClangDecl(Context& context, SemIR::ClangDeclId clang_decl_id) -> bool; +// Computes the signature to use for the given imported virtual function. Unlike +// with regular imported functions, we can only use a single signature here, so +// we pick one conservatively. +auto MakeVirtualFunctionSignature(Context& context, + const clang::CXXMethodDecl* method_decl) + -> SemIR::ClangDeclSignatureId; + // Gets the identifier info for a name. Returns `nullptr` if the name is not an // identifier name. auto GetClangIdentifierInfo(Context& context, SemIR::NameId name_id) diff --git a/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon b/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon index ce463e09d287..e23b64afab80 100644 --- a/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon +++ b/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon @@ -76,10 +76,10 @@ fn G(x: Cpp.X) { // CHECK:STDOUT: entity_name50000003: {name: name4, parent_scope: name_scope50000001, index: -1, is_template: 0, is_unused: 0, form: inst} // CHECK:STDOUT: functions: // CHECK:STDOUT: function50000000: {name: name0, parent_scope: name_scope0, call_param_patterns_id: inst_block50000007, call_params_id: inst_block50000008, body: [inst_block5000000B]} -// CHECK:STDOUT: function50000001: {name: name3, parent_scope: name_scope50000001, call_param_patterns_id: inst_block_empty, call_params_id: inst_block_empty} -// CHECK:STDOUT: function50000002: {name: name6, parent_scope: name_scope50000001, call_param_patterns_id: inst_block_empty, call_params_id: inst_block_empty} -// CHECK:STDOUT: function50000003: {name: name3, parent_scope: name_scope50000001, call_param_patterns_id: inst_block5000000F, call_params_id: inst_block50000010} -// CHECK:STDOUT: function50000004: {name: name6, parent_scope: name_scope50000001, call_param_patterns_id: inst_block50000015, call_params_id: inst_block50000016} +// CHECK:STDOUT: function50000001: {name: name3, parent_scope: name_scope50000001, call_param_patterns_id: inst_block_empty, call_params_id: inst_block_empty, cpp_thunk_decl: inst50000032} +// CHECK:STDOUT: function50000002: {name: name6, parent_scope: name_scope50000001, call_param_patterns_id: inst_block_empty, call_params_id: inst_block_empty, cpp_thunk_callee: inst5000002F} +// CHECK:STDOUT: function50000003: {name: name3, parent_scope: name_scope50000001, call_param_patterns_id: inst_block5000000F, call_params_id: inst_block50000010, cpp_thunk_decl: inst50000047} +// CHECK:STDOUT: function50000004: {name: name6, parent_scope: name_scope50000001, call_param_patterns_id: inst_block50000015, call_params_id: inst_block50000016, cpp_thunk_callee: inst5000003D} // CHECK:STDOUT: classes: // CHECK:STDOUT: class50000000: {name: name2, parent_scope: name_scope50000001, self_type_id: type(inst50000014), inheritance_kind: Base, is_dynamic: 0, scope_id: name_scope50000002, body_block_id: inst_block5000000C, adapt_id: inst, base_id: inst, complete_type_witness_id: inst50000027, vtable_decl_id: inst}} // CHECK:STDOUT: interfaces: {} diff --git a/toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon b/toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon index 55352f1ac2ab..e706b125b1a3 100644 --- a/toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon +++ b/toolchain/check/testdata/basics/raw_sem_ir/one_file.carbon @@ -415,7 +415,7 @@ fn Foo[T: type](p: T*) -> (T*, ()) { // CHECK:STDOUT: function78000000: {name: name0, parent_scope: name_scope0, call_param_patterns_id: inst_block78000010, call_params_id: inst_block78000011, return_type_inst_id: inst78000036, return_form_inst_id: inst78000038, return_pattern_id: inst78000040, body: [inst_block78000018]} // CHECK:STDOUT: function78000001: {name: name4, parent_scope: name_scope78000003, call_param_patterns_id: inst_block7800001E, return_type_inst_id: inst78000072, return_form_inst_id: inst78000073, return_pattern_id: inst78000074} // CHECK:STDOUT: function78000002: {name: name4, parent_scope: name_scope78000004, call_param_patterns_id: inst_block78000029, return_type_inst_id: inst780000A3, return_form_inst_id: inst780000A4, return_pattern_id: inst780000A5} -// CHECK:STDOUT: function78000003: {name: name4, parent_scope: name_scope78000009, call_param_patterns_id: inst_block7800003F, return_type_inst_id: inst78000101, return_form_inst_id: inst78000102, return_pattern_id: inst78000103} +// CHECK:STDOUT: function78000003: {name: name4, parent_scope: name_scope78000009, call_param_patterns_id: inst_block7800003F, return_type_inst_id: inst78000101, return_form_inst_id: inst78000102, return_pattern_id: inst78000103, builtin: primitive_copy} // CHECK:STDOUT: function78000004: {name: name4, parent_scope: name_scope7800000C, call_param_patterns_id: inst_block7800004C, return_type_inst_id: inst78000136, return_form_inst_id: inst78000137, return_pattern_id: inst78000138} // CHECK:STDOUT: function78000005: {name: name4, parent_scope: name_scope7800000D, call_param_patterns_id: inst_block78000068, return_type_inst_id: inst78000192, return_form_inst_id: inst78000193, return_pattern_id: inst78000194} // CHECK:STDOUT: classes: {} diff --git a/toolchain/check/testdata/interop/cpp/class/export/override.carbon b/toolchain/check/testdata/interop/cpp/class/export/override.carbon new file mode 100644 index 000000000000..6627eec6ec7c --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/class/export/override.carbon @@ -0,0 +1,32 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/destroy.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/class/export/override.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/export/override.carbon + +// --- const_ref_param.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +inline Cpp ''' +struct X {}; + +struct A { + virtual void f(const X& x) = 0; +}; +'''; + +class B { + extend base: Cpp.A; + override fn f(unused self, unused x: Cpp.X) {} +} + +inline Cpp ''' +Carbon::B b; +'''; diff --git a/toolchain/check/testdata/interop/cpp/function/export/array.carbon b/toolchain/check/testdata/interop/cpp/function/export/array.carbon index a68c5c4948cb..7815066e9fde 100644 --- a/toolchain/check/testdata/interop/cpp/function/export/array.carbon +++ b/toolchain/check/testdata/interop/cpp/function/export/array.carbon @@ -43,16 +43,6 @@ import Cpp; // HLSL extension, or by mapping to a `std::array` parameter. // TODO: These diagnostics are very bad. -// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+18]]:1: error: cannot copy value of type `array(i32, 5)` [CopyOfUncopyableType] -// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+15]]:1: note: type `array(i32, 5)` does not implement interface `Core.Copy` [MissingImplInMemberAccessInContext] -// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+12]]:6: note: initializing function parameter [InCallToFunctionParam] -// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: // CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+8]]:1: error: semantics TODO: `by-var array parameter` [SemanticsTodo] // CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -67,7 +57,7 @@ inline Cpp ''' int G() { //@dump-sem-ir-begin // CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+4]]:18: error: no member named 'F' in namespace 'Carbon' [CppInteropParseError] - // CHECK:STDERR: 38 | return Carbon::F({1, 2, 3, 4, 5}); + // CHECK:STDERR: 28 | return Carbon::F({1, 2, 3, 4, 5}); // CHECK:STDERR: | ^ // CHECK:STDERR: return Carbon::F({1, 2, 3, 4, 5}); diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 6662741d524c..3532c36ee240 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -452,6 +452,7 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, // names here. if (context.functions().Get(callee.function_id).special_function_kind != SemIR::Function::SpecialFunctionKind::HasCppThunk && + !override_self_type_id.has_value() && CheckFunctionTypeMatches( context, context.functions().Get(callee.function_id), context.functions().Get(signature_id), signature_specific_id, diff --git a/toolchain/docs/check/cpp/thunks.md b/toolchain/docs/check/cpp/thunks.md new file mode 100644 index 000000000000..f12827306e34 --- /dev/null +++ b/toolchain/docs/check/cpp/thunks.md @@ -0,0 +1,298 @@ + + +# C++ Interoperability: Thunks + + + +## Table of contents + +- [Overview](#overview) + - [Simplified ABI](#simplified-abi) + - [Thunk inlining](#thunk-inlining) + - [Signature adaptation thunks](#signature-adaptation-thunks) +- [Carbon calling C++](#carbon-calling-c) +- [C++ calling Carbon](#c-calling-carbon) +- [Carbon overriding C++ virtual functions](#carbon-overriding-c-virtual-functions) + - [What goes in the vtables](#what-goes-in-the-vtables) + - [Example of synthesized functions](#example-of-synthesized-functions) + - [Deferred thunk generation](#deferred-thunk-generation) + + + +## Overview + +When C++ code calls Carbon functions, or when Carbon calls C++ code, we need to +be able to interoperate between the Carbon ABI and the C++ ABI. We don't want to +hardcode all the minutiae of the various C++ ABIs into the Carbon toolchain, so +instead we generate thunks with intentionally simple ABIs. + +Example: + +```carbon +inline Cpp ''' +class X { ... }; +X f(X x); +'''; + +fn G(x: Cpp.X) -> Cpp.X { + return Cpp.f(x); +} +``` + +The calling conventions used to pass and return an `X` object in C++ are very +varied, and depend on various aspects of both the target and of the definition +of `X`. In order to call `f` from Carbon, we generate a thunk on the C++ side: + +```cpp +__attribute__((always_inline)) +inline void f__carbon_thunk__(void *result, void *x) { + new (result) X(f(*static_cast(x))); +} +``` + +... and notionally create a second thunk on the Carbon side to call it: + +```carbon +fn F:thunk(var x: Cpp.X) -> Cpp.X { + returned var result: Cpp.X; + Cpp.f__carbon_thunk__(&result, &x); + return var; +} +``` + +### Simplified ABI + +The simple ABI that we use for cross-language calls supports only the following: + +- Pointer and reference parameter and return types. +- 32- and 64-bit integer parameter and return types. +- `void` return types. + +We generate calls with these types by using the corresponding LLVM function +type. We assume that this matches the calling convention for these types on the +C++ side; in practice, it does for the ABIs that we care about. + +### Thunk inlining + +We aggressively inline thunks. This happens in two ways: + +1. Thunks are marked as "always inline" on both the C++ side and the Carbon + side. In C++ this happens by adding an `always_inline` attribute in the AST; + in Carbon it happens by emitting the LLVM `alwaysinline` attribute directly. +2. When building SemIR, if we try to build a `call` instruction whose target is + a Carbon-defined thunk, we instead inline the body of the thunk directly + into the SemIR. This means that we can usually avoid the Carbon-side thunk + entirely. + +### Signature adaptation thunks + +Carbon has another kind of thunk beyond those used for C++ interoperability. +When a function in an interface has a different signature than the corresponding +function in an impl, or a virtual function has a different signature than an +overrider, a thunk is generated to adapt the signature of the function. This +thunk simply implicitly converts each argument to the parameter type, and +implicitly converts the return value to the return type, with one exception: for +a virtual function, the `self` parameter is converted from the base class type +to the derived class type. + +```carbon +base class A { + virtual fn F(self, n: i32) -> i32; +} +class B { + extend base: A; + override fn F(self, n: i64) -> i16; +} + +// Behaves as if this function is in the vtable: +fn B.OverrideF(self: A, n: i32) -> i32 { + // Implicitly converts n from i32 to i64 + // Implicitly converts result from i16 to i32 + return (self unsafe as B).F(n); +} +``` + +These thunks are not the topic of this document, but understanding them is +important for understanding the behavior of +[Carbon overriders of C++ virtual functions](#carbon-overriding-c-virtual-functions). + +## Carbon calling C++ + +If a C++ function already has a simple ABI, we don't generate a thunk, and +instead we call it directly. Otherwise, we generate a thunk as follows. + +On the C++ side, we have two `clang::FunctionDecl`s: + +- The original callee. +- The thunk with a simplified ABI, which is defined to call the original callee. + +On the Carbon side, we have two `SemIR::Function`s: + +- The function representing the original C++ function signature. This is marked + as `SpecialFunctionKind::HasCppThunk`. Attempts to call this function generate + a call through the thunk instead. This is returned when Carbon invokes C++ + overload resolution. +- The function representing the C++ thunk. This is the target of SemIR `call` + instructions, and is marked as `SpecialFunctionKind::CppThunk`. This has the + same symbol name as the C++ thunk. + +`Context::clang_decls` can be used to map between the corresponding C++ and +Carbon functions above, and `Function::cpp_thunk_decl_id` and +`Function::cpp_thunk_callee` can be used to map between the two +`SemIR::Function`s. + +The `HasCppThunk` function on the Carbon side is only ever directly invoked. It +can't be placed into a witness table or a vtable. Therefore the thunk is +[always inlined](#thunk-inlining), and we never generate a Carbon-side +definition for it. + +The full story is a little more involved than this: in order to support C++ +default arguments (and some other call quirks), each C++ function can map to +multiple different `SemIR::Function`s with different Carbon-side signatures, +such as having different numbers of parameters. This leads to there being up to +2N `SemIR::Function`s per C++ function rather than only 2, where N is the number +of function variants in use. + +## C++ calling Carbon + +When C++ code calls into Carbon, we always generate a thunk on each side. + +On the Carbon side, we have two `SemIR::Function`s: + +- The original callee. +- The thunk with a simplified ABI, which is defined to call the original callee. + This is marked as `SpecialFunctionKind::CppThunk`. + +On the C++ side, we have two `clang::FunctionDecl`s: + +- A C++ function representing the original Carbon function signature. This + function is defined in the C++ AST with a body that calls the thunk; from + Clang's perspective this is a normal C++ function. +- A C++ function representing the Carbon thunk. This has the same symbol name as + the Carbon thunk. + +`Context::clang_decls` can be used to map between the corresponding C++ and +Carbon functions above. `Function::cpp_thunk_callee` can be used to map from the +`CppThunk` to the original Carbon callee. + +## Carbon overriding C++ virtual functions + +When a Carbon class extends a C++ base class and overrides a C++ virtual +function, we generate a set of thunks in order to produce a virtual override +with the correct signature. Broadly we use a similar pattern to C++ calling +Carbon, but with extra complexity because we need to fine-tune the signature of +the C++ function, and we need to import it back into Carbon so it can be +referenced from the Carbon vtable representation. + +Consider the following example of a Carbon class overriding a C++ virtual +function: + +```carbon +import Cpp; + +inline Cpp ''' +struct Base { + virtual void f(int x) = 0; +}; +void CallBase(Base& b) { b.f(42); } +'''; + +class Derived { + extend base: Cpp.Base; + override fn f(self, x: i64) { + ... + } +} +``` + +This is implemented by combining three other kinds of thunk, as follows: + +- A declaration of the C++ function in the base class is imported into Carbon as + a member of the derived class. This function is marked as being a + [signature adaptation thunk](#signature-adaptation-thunks) for the Carbon + overrider. +- The signature adaptation thunk is [exported to C++](#c-calling-carbon). This + generates a Carbon-side `CppThunk` definition that calls the signature + adaptation thunk, and a C++-side definition. +- The Carbon-side thunk's call to the signature adaptation thunk is inlined in + SemIR. +- The C++-side function definition uses the exact signature of the original C++ + function. We know to do this because it is a thunk generated for a signature + adaptation thunk whose signature is itself imported from C++. + +In `clang_decls`, the signature adaptation thunk corresponds to the C++ virtual +override function. + +We generate a signature adaptation thunk in Carbon so that any conversions +required in order to call the Carbon derived function from the base function +signature are performed using Carbon rules, not C++ rules. + +### What goes in the vtables + +We are concerned with two vtables: + +- The Carbon-side vtable representation. This is notionally the authoritative + vtable, as the class `Derived` is a Carbon class, but in practice it is only + used for constant evaluation on the Carbon side. +- The C++-side vtable representation. In this case, because the vptr was + originally introduced by a C++ class, this will be used for code generation. + This is generated by Clang based on our exporting a suitable set of overriding + functions when we export the Carbon class to C++. + +The Carbon-side vtable contains the signature adaptation thunk. The C++-side +vtable contains the corresponding C++ virtual override function. + +### Example of synthesized functions + +In the C++ AST: + +```cpp +namespace Carbon { +struct Derived : Base { + // The C++ vtable entry for Derived::f. + // Placed into `Derived`'s C++ vtable. When called from C++, + // it forwards directly to the Carbon thunk. + virtual void f(int x) override { + // The C++ declaration of the Carbon-side thunk. + extern void _Cf__carbon_thunk_Derived_Main(Base* self, int x); + _Cf__carbon_thunk_Derived_Main(this, x); + } +}; +} +``` + +In Carbon SemIR: + +```carbon +// The user-written Carbon override function. +fn Derived.f(self: Derived, x: i64) { + ... +} + +// Signature adaptation thunk. +fn Derived.OverrideF(self: Cpp.Base, x: i32) { + // Explicitly converts `self` from `Base` to `Derived`. + // Implicitly converts x from i32 to i64. + (self unsafe as Derived).F(x); +} + +// The synthesized Carbon thunk. +fn _Cf__carbon_thunk.Derived.Main(self_ptr: Base*, x: i32) { + // Notionally: `Derived.OverrideF(*self_ptr, x)`, but call is inlined: + (*self_ptr unsafe as Derived).F(x); +} +``` + +### Deferred thunk generation + +We can't synthesize the definition of the C++-side virtual overrider until the +enclosing class is complete in the C++ AST. Therefore we split the +responsibility for generating the thunks in two: + +- When we complete the Carbon class, we generate the signature adaptation thunk. +- When we form a corresponding complete C++ class type, we generate the C++-side + virtual overrider thunk. diff --git a/toolchain/lower/testdata/interop/cpp/class/virtual_fn.carbon b/toolchain/lower/testdata/interop/cpp/class/virtual_fn.carbon index f70d6fdcd5a9..9ee31e555787 100644 --- a/toolchain/lower/testdata/interop/cpp/class/virtual_fn.carbon +++ b/toolchain/lower/testdata/interop/cpp/class/virtual_fn.carbon @@ -82,8 +82,8 @@ void delete_new_Base() { // CHECK:STDOUT: $_ZTSN6Carbon4BaseE = comdat any // CHECK:STDOUT: // CHECK:STDOUT: @_ZTV1A = available_externally constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr @_ZTI1A, ptr @__cxa_pure_virtual, ptr @__cxa_pure_virtual, ptr @_ZNK1A1FEv] }, align 8 -// CHECK:STDOUT: @_ZTVN6Carbon5FinalE = linkonce_odr dso_local constant { [6 x ptr] } { [6 x ptr] [ptr null, ptr @_ZTIN6Carbon5FinalE, ptr @_ZN6Carbon5FinalD2Ev, ptr @_ZN6Carbon5FinalD0Ev, ptr @_ZNK1A1FEv, ptr @_ZN6Carbon5Final1FEv] }, comdat, align 8 -// CHECK:STDOUT: @_ZTVN6Carbon4BaseE = linkonce_odr dso_local constant { [6 x ptr] } { [6 x ptr] [ptr null, ptr @_ZTIN6Carbon4BaseE, ptr @_ZN6Carbon4BaseD2Ev, ptr @_ZN6Carbon4BaseD0Ev, ptr @_ZNK1A1FEv, ptr @_ZN6Carbon4Base1FEv] }, comdat, align 8 +// CHECK:STDOUT: @_ZTVN6Carbon5FinalE = linkonce_odr dso_local constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr @_ZTIN6Carbon5FinalE, ptr @_ZN6Carbon5FinalD2Ev, ptr @_ZN6Carbon5FinalD0Ev, ptr @_ZNK6Carbon5Final1FEv] }, comdat, align 8 +// CHECK:STDOUT: @_ZTVN6Carbon4BaseE = linkonce_odr dso_local constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr @_ZTIN6Carbon4BaseE, ptr @_ZN6Carbon4BaseD2Ev, ptr @_ZN6Carbon4BaseD0Ev, ptr @_ZNK6Carbon4Base1FEv] }, comdat, align 8 // CHECK:STDOUT: @_ZTI1A = external constant ptr // CHECK:STDOUT: @_ZTIN6Carbon5FinalE = linkonce_odr dso_local constant { ptr, ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2), ptr @_ZTSN6Carbon5FinalE, ptr @_ZTI1A }, comdat, align 8 // CHECK:STDOUT: @_ZTVN10__cxxabiv120__si_class_type_infoE = external global [0 x ptr] @@ -126,7 +126,7 @@ void delete_new_Base() { // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !12 // CHECK:STDOUT: %this1 = load ptr, ptr %this.addr, align 8 // CHECK:STDOUT: call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this1) #8 -// CHECK:STDOUT: store ptr getelementptr inbounds inrange(-16, 32) ({ [6 x ptr] }, ptr @_ZTVN6Carbon5FinalE, i32 0, i32 0, i32 2), ptr %this1, align 8, !tbaa !15 +// CHECK:STDOUT: store ptr getelementptr inbounds inrange(-16, 24) ({ [5 x ptr] }, ptr @_ZTVN6Carbon5FinalE, i32 0, i32 0, i32 2), ptr %this1, align 8, !tbaa !15 // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: @@ -178,7 +178,7 @@ void delete_new_Base() { // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !17 // CHECK:STDOUT: %this1 = load ptr, ptr %this.addr, align 8 // CHECK:STDOUT: call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this1) #8 -// CHECK:STDOUT: store ptr getelementptr inbounds inrange(-16, 32) ({ [6 x ptr] }, ptr @_ZTVN6Carbon4BaseE, i32 0, i32 0, i32 2), ptr %this1, align 8, !tbaa !15 +// CHECK:STDOUT: store ptr getelementptr inbounds inrange(-16, 24) ({ [5 x ptr] }, ptr @_ZTVN6Carbon4BaseE, i32 0, i32 0, i32 2), ptr %this1, align 8, !tbaa !15 // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: @@ -278,7 +278,7 @@ void delete_new_Base() { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable -// CHECK:STDOUT: define internal void @_ZN6Carbon5Final1FEv(ptr noundef nonnull align 8 dereferenceable(8) %this) unnamed_addr #5 align 2 { +// CHECK:STDOUT: define internal void @_ZNK6Carbon5Final1FEv(ptr noundef nonnull align 8 dereferenceable(8) %this) unnamed_addr #5 align 2 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %this.addr = alloca ptr, align 8 // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !12 @@ -293,7 +293,7 @@ void delete_new_Base() { // CHECK:STDOUT: %this.addr = alloca ptr, align 8 // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !17 // CHECK:STDOUT: %this1 = load ptr, ptr %this.addr, align 8 -// CHECK:STDOUT: store ptr getelementptr inbounds inrange(-16, 32) ({ [6 x ptr] }, ptr @_ZTVN6Carbon4BaseE, i32 0, i32 0, i32 2), ptr %this1, align 8, !tbaa !15 +// CHECK:STDOUT: store ptr getelementptr inbounds inrange(-16, 24) ({ [5 x ptr] }, ptr @_ZTVN6Carbon4BaseE, i32 0, i32 0, i32 2), ptr %this1, align 8, !tbaa !15 // CHECK:STDOUT: call void @"_C__destroy_thunk:thunk.Base.Main"(ptr noundef nonnull align 8 dereferenceable(8) %this1) // CHECK:STDOUT: call void @_ZN1AD2Ev(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %this1) #8 // CHECK:STDOUT: ret void @@ -311,7 +311,7 @@ void delete_new_Base() { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable -// CHECK:STDOUT: define internal void @_ZN6Carbon4Base1FEv(ptr noundef nonnull align 8 dereferenceable(8) %this) unnamed_addr #5 align 2 { +// CHECK:STDOUT: define internal void @_ZNK6Carbon4Base1FEv(ptr noundef nonnull align 8 dereferenceable(8) %this) unnamed_addr #5 align 2 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %this.addr = alloca ptr, align 8 // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !17 @@ -336,7 +336,6 @@ void delete_new_Base() { // CHECK:STDOUT: ; uselistorder directives // CHECK:STDOUT: uselistorder ptr @_ZN6Carbon5FinalD2Ev, { 2, 1, 0 } // CHECK:STDOUT: uselistorder ptr @__cxa_pure_virtual, { 1, 0 } -// CHECK:STDOUT: uselistorder ptr @_ZNK1A1FEv, { 2, 1, 0 } // CHECK:STDOUT: uselistorder ptr @_ZN6Carbon4BaseD2Ev, { 1, 0 } // CHECK:STDOUT: uselistorder ptr @_ZN1AC2Ev, { 1, 0 } // CHECK:STDOUT: uselistorder ptr @_ZN1AD2Ev, { 1, 0 } diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index 8e7a25d40bf3..09c521201991 100644 --- a/toolchain/sem_ir/function.h +++ b/toolchain/sem_ir/function.h @@ -20,11 +20,36 @@ struct FunctionFields { // Kinds of special functions. See `Function::Set*` for details on each; these // shouldn't be assigned directly (but are used for reads/switches). enum class SpecialFunctionKind : uint8_t { + // A regular function. None, + // A builtin function. `special_function_kind_data` is the corresponding + // `BuiltinFunctionKind`. Builtin, + // A synthesized function generated for a `Core` witness. + // `special_function_kind_data` is the corresponding `BuiltinFunctionKind`, + // or may be `BuiltinFunctionKind::None` if a function body is synthesized + // too. During name mangling, extra information is included for this + // function to avoid collisions. CoreWitness, + // A thunk that adapts a function with one signature to have another + // signature, by forwarding the arguments and return value, with implicit + // conversions applied as necessary. `special_function_kind_data` is the + // corresponding `ThunkId`. A call to this function can generally be + // rewritten as a call to its target function, after performing the + // intermediate parameter conversions. Thunk, + // A thunk for calling to or from C++, with an intentionally-simple ABI so + // that it can be called across the language barrier. `Context::clang_decls` + // can be used to find the corresponding C++ function, which will have the + // same mangled name. + // + // `special_function_kind_data` is the `InstId` of the wrapped function. If + // the wrapped function is in Carbon, the Carbon version of the thunk will + // be a definition. If the wrapped function is in C++, the C++ version of + // the thunk will be a definition. CppThunk, + // A function that was imported from C++, for which we generated a + // `CppThunk`. `special_function_kind_data` is the `InstId` of that thunk. HasCppThunk, }; @@ -193,6 +218,21 @@ struct Function : public EntityWithParamsBase, if (return_pattern_id.has_value()) { out << ", return_pattern_id: " << return_pattern_id; } + if (auto builtin_kind = builtin_function_kind(); + builtin_kind != BuiltinFunctionKind::None) { + out << ", builtin: " << builtin_kind; + } + if (auto thunk_id_val = thunk_id(); thunk_id_val.has_value()) { + out << ", thunk: " << thunk_id_val; + } + if (auto cpp_thunk_decl_id_val = cpp_thunk_decl_id(); + cpp_thunk_decl_id_val.has_value()) { + out << ", cpp_thunk_decl: " << cpp_thunk_decl_id_val; + } + if (auto cpp_thunk_callee_val = cpp_thunk_callee(); + cpp_thunk_callee_val.has_value()) { + out << ", cpp_thunk_callee: " << cpp_thunk_callee_val; + } if (!body_block_ids.empty()) { out << llvm::formatv( ", body: [{0}]",