From 0635f4628f08e652261c43306bbb01f27e29e58a Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Tue, 7 Apr 2026 19:25:57 -0400 Subject: [PATCH] Add support for C++ calling Carbon functions with parameters (#7024) This works by generating two thunks, one in C++ and one in Carbon. For example, given this input: ```c++ // Carbon: fn Callme(f: f32) {} // C++: void F() { // This will call `Callme__cpp_thunk` Carbon::Callme(1.0); } ``` These functions are generated: ```c++ // Carbon: fn Callme__carbon_thunk(ref f: f32) { // Call the target function. Callme(f); } // C++: // C++ declaration for the Carbon thunk. void Callme__carbon_thunk(float& f); void Callme__cpp_thunk(float f) { // Call the Carbon thunk with args passed by reference. Callme__carbon_thunk(f); } ``` For now, all arguments are passed by reference, even if they are simple types like pointers or i32. Functions with non-void return types are not supported yet. --- toolchain/check/BUILD | 2 + toolchain/check/cpp/export.cpp | 272 ++++++++++++++++++ toolchain/check/cpp/export.h | 22 ++ toolchain/check/cpp/generate_ast.cpp | 49 +--- toolchain/check/function.cpp | 6 +- toolchain/check/function.h | 2 + .../interop/cpp/reverse/function.carbon | 79 ++++- toolchain/check/thunk.cpp | 10 +- toolchain/check/thunk.h | 6 + .../interop/cpp/reverse/function.carbon | 204 +++++++++++-- 10 files changed, 571 insertions(+), 81 deletions(-) create mode 100644 toolchain/check/cpp/export.cpp create mode 100644 toolchain/check/cpp/export.h diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 49da37eabec9..0249914fcc55 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -26,6 +26,7 @@ cc_library( "cpp/constant.cpp", "cpp/context.cpp", "cpp/custom_type_mapping.cpp", + "cpp/export.cpp", "cpp/generate_ast.cpp", "cpp/impl_lookup.cpp", "cpp/import.cpp", @@ -88,6 +89,7 @@ cc_library( "cpp/constant.h", "cpp/context.h", "cpp/custom_type_mapping.h", + "cpp/export.h", "cpp/generate_ast.h", "cpp/impl_lookup.h", "cpp/import.h", diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp new file mode 100644 index 000000000000..9041f523c6b2 --- /dev/null +++ b/toolchain/check/cpp/export.cpp @@ -0,0 +1,272 @@ +// 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 "toolchain/check/cpp/export.h" + +#include "toolchain/check/cpp/import.h" +#include "toolchain/check/cpp/location.h" +#include "toolchain/check/cpp/type_mapping.h" +#include "toolchain/check/function.h" +#include "toolchain/check/thunk.h" +#include "toolchain/check/type.h" +#include "toolchain/sem_ir/mangler.h" + +namespace Carbon::Check { + +// 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++. +// +// The resulting decl is used to allow a generated C++ function to call +// a generated Carbon function. +static auto BuildCppFunctionDeclForCarbonFn(Context& context, + clang::DeclContext& decl_context, + SemIR::LocId loc_id, + SemIR::FunctionId function_id) + -> clang::FunctionDecl* { + auto clang_loc = GetCppLocation(context, loc_id); + + const SemIR::Function& function = context.functions().Get(function_id); + + // Get parameters types. + auto carbon_function_params = + context.inst_blocks().Get(function.call_param_patterns_id); + llvm::SmallVector cpp_param_types; + for (auto param_inst_id : carbon_function_params) { + auto scrutinee_type_id = ExtractScrutineeType( + context.sem_ir(), context.insts().Get(param_inst_id).type_id()); + auto cpp_type = MapToCppType(context, scrutinee_type_id); + if (cpp_type.isNull()) { + context.TODO(loc_id, "failed to map C++ type to Carbon"); + return nullptr; + } + auto ref_type = context.ast_context().getLValueReferenceType(cpp_type); + cpp_param_types.push_back(ref_type); + } + + CARBON_CHECK(function.return_type_inst_id == SemIR::TypeInstId::None); + auto cpp_return_type = context.ast_context().VoidTy; + + auto cpp_function_type = context.ast_context().getFunctionType( + cpp_return_type, cpp_param_types, + clang::FunctionProtoType::ExtProtoInfo()); + + auto* identifier_info = GetClangIdentifierInfo(context, function.name_id); + CARBON_CHECK(identifier_info, "function with non-identifier name {0}", + function.name_id); + + auto* function_decl = clang::FunctionDecl::Create( + context.ast_context(), &decl_context, + /*StartLoc=*/clang_loc, + /*NLoc=*/clang_loc, clang::DeclarationName(identifier_info), + cpp_function_type, + /*TInfo=*/nullptr, clang::SC_Extern); + + // Build parameter decls. + llvm::SmallVector param_var_decls; + for (auto [i, type] : llvm::enumerate(cpp_param_types)) { + clang::ParmVarDecl* param = clang::ParmVarDecl::Create( + context.ast_context(), function_decl, /*StartLoc=*/clang_loc, + /*IdLoc=*/clang_loc, /*Id=*/nullptr, type, /*TInfo=*/nullptr, + clang::SC_None, /*DefArg=*/nullptr); + param_var_decls.push_back(param); + } + function_decl->setParams(param_var_decls); + + // Mangle the function name and attach it to the `FunctionDecl`. + SemIR::Mangler m(context.sem_ir(), context.total_ir_count()); + std::string mangled_name = m.Mangle(function_id, SemIR::SpecificId::None); + function_decl->addAttr( + clang::AsmLabelAttr::Create(context.ast_context(), mangled_name)); + + return function_decl; +} + +// Create the declaration of the C++ thunk. +static auto BuildCppToCarbonThunkDecl( + Context& context, SemIR::LocId loc_id, clang::DeclarationName thunk_name, + llvm::ArrayRef thunk_param_types) -> clang::FunctionDecl* { + clang::ASTContext& ast_context = context.ast_context(); + + auto clang_loc = GetCppLocation(context, loc_id); + + auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); + clang::QualType thunk_function_type = ast_context.getFunctionType( + ast_context.VoidTy, thunk_param_types, ext_proto_info); + + clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl(); + auto* tinfo = + ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc); + clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create( + ast_context, decl_context, clang_loc, clang_loc, thunk_name, + thunk_function_type, tinfo, clang::SC_Static); + decl_context->addDecl(thunk_function_decl); + + llvm::SmallVector param_var_decls; + for (auto [i, type] : llvm::enumerate(thunk_param_types)) { + clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create( + ast_context, thunk_function_decl, /*StartLoc=*/clang_loc, + /*IdLoc=*/clang_loc, /*Id=*/nullptr, type, + /*TInfo=*/nullptr, clang::SC_None, /*DefArg=*/nullptr); + param_var_decls.push_back(thunk_param); + } + thunk_function_decl->setParams(param_var_decls); + + // Set always_inline. + thunk_function_decl->addAttr( + clang::AlwaysInlineAttr::CreateImplicit(ast_context)); + + return thunk_function_decl; +} + +// Create the body of a C++ thunk that calls a Carbon thunk. The +// arguments are passed by reference to the callee. +static auto BuildCppToCarbonThunkBody(clang::Sema& sema, + clang::FunctionDecl* function_decl, + clang::FunctionDecl* callee_function_decl) + -> clang::StmtResult { + clang::SourceLocation clang_loc = function_decl->getLocation(); + + clang::ExprResult callee = sema.BuildDeclRefExpr( + callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue, + clang_loc); + + llvm::SmallVector call_args; + for (auto* param : function_decl->parameters()) { + clang::Expr* call_arg = + sema.BuildDeclRefExpr(param, param->getType().getNonReferenceType(), + clang::VK_LValue, clang_loc); + call_args.push_back(call_arg); + } + clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc, + call_args, clang_loc); + CARBON_CHECK(call.isUsable()); + + return call.get(); +} + +// 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, llvm::StringRef base_name, + clang::FunctionDecl* carbon_function_decl, + llvm::ArrayRef callee_param_type_ids) + -> clang::FunctionDecl* { + // Create the thunk's name. + llvm::SmallString<64> thunk_name = base_name; + thunk_name += "__cpp_thunk"; + auto& thunk_ident = context.ast_context().Idents.get(thunk_name); + + llvm::SmallVector param_types; + for (auto type_id : callee_param_type_ids) { + auto cpp_type = MapToCppType(context, type_id); + 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, &thunk_ident, param_types); + + // Build the thunk function body. + clang::Sema& sema = context.clang_sema(); + clang::Sema::ContextRAII context_raii(sema, thunk_function_decl); + sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl); + clang::StmtResult body = BuildCppToCarbonThunkBody(sema, 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. +static auto BuildCarbonToCarbonThunk( + Context& context, SemIR::LocId loc_id, const SemIR::Function& callee, + llvm::ArrayRef callee_param_type_ids) -> SemIR::FunctionId { + // Create the thunk's name. + llvm::SmallString<64> thunk_name = + context.names().GetFormatted(callee.name_id); + thunk_name += "__carbon_thunk"; + auto& ident = context.ast_context().Idents.get(thunk_name); + auto thunk_name_id = + SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName())); + + auto carbon_thunk_function_id = + MakeGeneratedFunctionDecl(context, loc_id, + {.parent_scope_id = callee.parent_scope_id, + .name_id = thunk_name_id, + .param_type_ids = callee_param_type_ids, + .params_are_refs = true}) + .second; + BuildThunkDefinition(context, carbon_thunk_function_id, + carbon_thunk_function_id, callee.first_decl_id(), + callee.first_decl_id()); + + return carbon_thunk_function_id; +} + +auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id, + clang::DeclContext& decl_context, + SemIR::FunctionId callee_function_id) + -> clang::FunctionDecl* { + const SemIR::Function& callee = context.functions().Get(callee_function_id); + + if (callee.return_type_inst_id != SemIR::TypeInstId::None) { + context.TODO(loc_id, + "unsupported: C++ calling a Carbon function with " + "return type other than `()`"); + return nullptr; + } + + if (callee.generic_id.has_value()) { + context.TODO(loc_id, + "unsupported: C++ calling a Carbon function with " + "generic parameters"); + return nullptr; + } + + if (callee.call_param_ranges.implicit_size() != 0) { + context.TODO(loc_id, + "unsupported: C++ calling a Carbon function with " + "an implicit parameter"); + return nullptr; + } + + // Get the parameter types of the Carbon function being called. + auto callee_function_params = + context.inst_blocks().Get(callee.call_param_patterns_id); + llvm::SmallVector callee_param_type_ids; + for (auto callee_param_inst_id : callee_function_params) { + auto scrutinee_type_id = ExtractScrutineeType( + context.sem_ir(), context.insts().Get(callee_param_inst_id).type_id()); + callee_param_type_ids.push_back(scrutinee_type_id); + } + + // 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 = + BuildCarbonToCarbonThunk(context, loc_id, callee, callee_param_type_ids); + + // Create a `clang::FunctionDecl` that can be used to call the Carbon thunk. + auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn( + context, decl_context, loc_id, carbon_thunk_function_id); + if (!carbon_function_decl) { + return nullptr; + } + + // Create a C++ thunk that calls the Carbon thunk. + return BuildCppToCarbonThunk(context, loc_id, + context.names().GetFormatted(callee.name_id), + carbon_function_decl, callee_param_type_ids); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/cpp/export.h b/toolchain/check/cpp/export.h new file mode 100644 index 000000000000..47b35d2c7ec9 --- /dev/null +++ b/toolchain/check/cpp/export.h @@ -0,0 +1,22 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_ +#define CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_ + +#include "clang/AST/Decl.h" +#include "toolchain/check/context.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::Check { + +// Get a `clang::FunctionDecl` that can be used to call a Carbon function. +auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id, + clang::DeclContext& decl_context, + SemIR::FunctionId function_id) + -> clang::FunctionDecl*; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_ diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 3fc7d87acbbb..444e7fa3ae8e 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -28,6 +28,7 @@ #include "llvm/Support/raw_ostream.h" #include "toolchain/base/kind_switch.h" #include "toolchain/check/context.h" +#include "toolchain/check/cpp/export.h" #include "toolchain/check/cpp/import.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/name_lookup.h" @@ -36,7 +37,6 @@ #include "toolchain/diagnostics/format_providers.h" #include "toolchain/parse/node_ids.h" #include "toolchain/sem_ir/cpp_file.h" -#include "toolchain/sem_ir/mangler.h" namespace Carbon::Check { @@ -432,56 +432,17 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl( if (!callee_function) { return nullptr; } + const SemIR::Function& function = context_->functions().Get(callee_function->function_id); if (function.clang_decl_id.has_value()) { return cast( context_->clang_decls().Get(function.clang_decl_id).key.decl); } - auto* identifier_info = - GetClangIdentifierInfo(*context_, function.name_id); - CARBON_CHECK(identifier_info, "function with non-identifier name {0}", - function.name_id); - if (function.call_param_ranges.explicit_size() != 0) { - context_->TODO(target_inst_id, - "unsupported: C++ calling a Carbon function with " - "parameters"); - return nullptr; - } - - if (function.return_type_inst_id != SemIR::TypeInstId::None) { - context_->TODO(target_inst_id, - "unsupported: C++ calling a Carbon function with " - "return type other than `()`"); - return nullptr; - } - - // TODO: support non-empty parameter lists. - llvm::SmallVector cpp_param_types; - - // TODO: support non-void return types. - auto cpp_return_type = ast_context_->VoidTy; - - auto cpp_function_type = ast_context_->getFunctionType( - cpp_return_type, cpp_param_types, - clang::FunctionProtoType::ExtProtoInfo()); - - auto* function_decl = clang::FunctionDecl::Create( - *ast_context_, &decl_context, - /*StartLoc=*/clang::SourceLocation(), - /*NLoc=*/clang::SourceLocation(), - clang::DeclarationName(identifier_info), cpp_function_type, - /*TInfo=*/nullptr, clang::SC_Extern); - - // Mangle the function name and attach it to the `FunctionDecl`. - SemIR::Mangler m(context_->sem_ir(), context_->total_ir_count()); - std::string mangled_name = - m.Mangle(callee_function->function_id, SemIR::SpecificId::None); - function_decl->addAttr( - clang::AsmLabelAttr::Create(*ast_context_, mangled_name)); - - return function_decl; + return GetReverseInteropFunctionDecl( + *context_, SemIR::LocId(target_inst_id), decl_context, + callee_function->function_id); } default: return nullptr; diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 4263d0bfe121..2aa132abce20 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -139,8 +139,8 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id, context.full_pattern_stack().EndImplicitParamList(); } - // Build and add any explicit parameters. We always use value parameters for - // now. + // Build and add any explicit parameters. Whether these are references + // or not is controlled by `args.params_are_refs`. context.full_pattern_stack().StartExplicitParamList(); if (args.param_type_ids.empty()) { insts.param_patterns_id = SemIR::InstBlockId::Empty; @@ -154,7 +154,7 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id, context.inst_block_stack().AddInstId(AddParamPattern( context, loc_id, SemIR::NameId::Underscore, param_type_region_id, - param_type_id, /*is_ref=*/false)); + param_type_id, /*is_ref=*/args.params_are_refs)); } insts.param_patterns_id = context.inst_block_stack().Pop(); } diff --git a/toolchain/check/function.h b/toolchain/check/function.h index 871374469742..4453dab8a99c 100644 --- a/toolchain/check/function.h +++ b/toolchain/check/function.h @@ -41,6 +41,8 @@ struct FunctionDeclArgs { bool self_is_ref = true; // The types of the explicit parameters. llvm::ArrayRef param_type_ids = {}; + // Whether the parameters described by `param_type_ids` are references. + bool params_are_refs = false; // The return type, or `None` if the function doesn't declare a return type. SemIR::TypeId return_type_id = SemIR::TypeId::None; }; diff --git a/toolchain/check/testdata/interop/cpp/reverse/function.carbon b/toolchain/check/testdata/interop/cpp/reverse/function.carbon index cb091138d35a..2ee447153924 100644 --- a/toolchain/check/testdata/interop/cpp/reverse/function.carbon +++ b/toolchain/check/testdata/interop/cpp/reverse/function.carbon @@ -17,6 +17,13 @@ fn F1() {} fn F2() -> i32 { return 0; } fn F3(_: i32) {} +fn HasGenericArg(T:! type, a: T) { a; } +fn HasDeducedArg[T:! type](a: T) { a; } + +class C { + fn Method[self: Self]() { self; } +} + // --- function.carbon library "[[@TEST_NAME]]"; @@ -48,22 +55,76 @@ void G() { } '''; -// --- fail_todo_args.carbon +// --- args.carbon library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_todo_args.carbon:[[@LINE+5]]:1: in import [InImport] -// CHECK:STDERR: other.carbon:5:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with parameters` [SemanticsTodo] -// CHECK:STDERR: fn F3(_: i32) {} -// CHECK:STDERR: ^~~~~~~~~~~~~~~ +import Other; +import Cpp inline ''' +void G() { + Carbon::Other::F3(123); +} +'''; + +// --- fail_todo_generic.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+5]]:1: in import [InImport] +// CHECK:STDERR: other.carbon:7:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with generic parameters` [SemanticsTodo] +// CHECK:STDERR: fn HasGenericArg(T:! type, a: T) { a; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: import Other; import Cpp inline ''' void G() { - // CHECK:STDERR: fail_todo_args.carbon:[[@LINE+4]]:18: error: no member named 'F3' in namespace 'Carbon::Other' [CppInteropParseError] - // CHECK:STDERR: 16 | Carbon::Other::F3(); - // CHECK:STDERR: | ^~ + // CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+8]]:18: error: no member named 'HasGenericArg' in namespace 'Carbon::Other' [CppInteropParseError] + // CHECK:STDERR: 20 | Carbon::Other::HasGenericArg(123); + // CHECK:STDERR: | ^~~~~~~~~~~~~ // CHECK:STDERR: - Carbon::Other::F3(); + // CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+4]]:35: error: expected '(' for function-style cast or type construction [CppInteropParseError] + // CHECK:STDERR: 20 | Carbon::Other::HasGenericArg(123); + // CHECK:STDERR: | ~~~^ + // CHECK:STDERR: + Carbon::Other::HasGenericArg(123); +} +'''; + +// --- fail_todo_deduced.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_todo_deduced.carbon:[[@LINE+5]]:1: in import [InImport] +// CHECK:STDERR: other.carbon:8:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with generic parameters` [SemanticsTodo] +// CHECK:STDERR: fn HasDeducedArg[T:! type](a: T) { a; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +import Other; +import Cpp inline ''' +void G() { + // CHECK:STDERR: fail_todo_deduced.carbon:[[@LINE+4]]:18: error: no member named 'HasDeducedArg' in namespace 'Carbon::Other' [CppInteropParseError] + // CHECK:STDERR: 16 | Carbon::Other::HasDeducedArg(123); + // CHECK:STDERR: | ^~~~~~~~~~~~~ + // CHECK:STDERR: + Carbon::Other::HasDeducedArg(123); +} +'''; + +// --- fail_todo_method.carbon + +library "[[@TEST_NAME]]"; + +import Other; +import Cpp inline ''' +void G() { + // CHECK:STDERR: fail_todo_method.carbon:[[@LINE+8]]:3: error: invalid use of incomplete type 'Carbon::Other::C' [CppInteropParseError] + // CHECK:STDERR: 15 | Carbon::Other::C().Method(); + // CHECK:STDERR: | ^~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_todo_method.carbon:[[@LINE+4]]:21: error: member access into incomplete type 'Carbon::Other::C' [CppInteropParseError] + // CHECK:STDERR: 15 | Carbon::Other::C().Method(); + // CHECK:STDERR: | ^ + // CHECK:STDERR: + Carbon::Other::C().Method(); } '''; diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 77e5a8c40646..5ea60f53fc00 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -301,13 +301,9 @@ static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id, return PerformThunkCall(context, loc_id, function_id, call_params, callee_id); } -// Given a declaration of a thunk and the function that it should call, build -// the thunk body. -static auto BuildThunkDefinition(Context& context, - SemIR::FunctionId signature_id, - SemIR::FunctionId function_id, - SemIR::InstId thunk_id, - SemIR::InstId callee_id) { +auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id, + SemIR::FunctionId function_id, SemIR::InstId thunk_id, + SemIR::InstId callee_id) -> void { // TODO: Improve the diagnostics produced here. Specifically, it would likely // be better for the primary error message to be that we tried to produce a // thunk because of a type mismatch, but couldn't, with notes explaining diff --git a/toolchain/check/thunk.h b/toolchain/check/thunk.h index 40d621dcc8ee..d0fab73033f6 100644 --- a/toolchain/check/thunk.h +++ b/toolchain/check/thunk.h @@ -11,6 +11,12 @@ namespace Carbon::Check { +// Given a declaration of a thunk and the function that it should call, build +// the thunk body. +auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id, + SemIR::FunctionId function_id, SemIR::InstId thunk_id, + SemIR::InstId callee_id) -> void; + // Given a function signature and a callee function, build a thunk that matches // the given signature and calls the specified callee. Returns the callee // unchanged if it can be used directly. diff --git a/toolchain/lower/testdata/interop/cpp/reverse/function.carbon b/toolchain/lower/testdata/interop/cpp/reverse/function.carbon index 9c049d77f8fc..1f5331238d90 100644 --- a/toolchain/lower/testdata/interop/cpp/reverse/function.carbon +++ b/toolchain/lower/testdata/interop/cpp/reverse/function.carbon @@ -2,7 +2,7 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon // // AUTOUPDATE // TIP: To test this file alone, run: @@ -12,7 +12,16 @@ // --- other.carbon package Other; -fn F() {} +fn NoArgs() {} +fn BoolArg(a: bool) { + a; +} +fn IntArg(a: i32) { + a; +} +fn FloatArg(a: f32) { + a; +} // --- function.carbon library "[[@TEST_NAME]]"; @@ -20,7 +29,10 @@ library "[[@TEST_NAME]]"; import Other; import Cpp inline ''' void G() { - Carbon::Other::F(); + Carbon::Other::NoArgs(); + Carbon::Other::BoolArg(true); + Carbon::Other::IntArg(123); + Carbon::Other::FloatArg(1.5); } '''; @@ -50,11 +62,29 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: source_filename = "other.carbon" // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nounwind -// CHECK:STDOUT: define void @_CF.Other() #0 !dbg !4 { +// CHECK:STDOUT: define void @_CNoArgs.Other() #0 !dbg !4 { // CHECK:STDOUT: entry: // CHECK:STDOUT: ret void, !dbg !7 // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CBoolArg.Other(i1 %a) #0 !dbg !8 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CIntArg.Other(i32 %a) #0 !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !21 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CFloatArg.Other(float %a) #0 !dbg !22 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !25 +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { nounwind } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} @@ -64,10 +94,28 @@ fn H() { Cpp.G2(); } // 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: "other.carbon", directory: "") -// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Other", scope: null, file: !3, line: 2, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "NoArgs", linkageName: "_CNoArgs.Other", scope: null, file: !3, line: 2, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !5 = !DISubroutineType(types: !6) // CHECK:STDOUT: !6 = !{null} // CHECK:STDOUT: !7 = !DILocation(line: 2, column: 1, scope: !4) +// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "BoolArg", linkageName: "_CBoolArg.Other", scope: null, file: !3, line: 3, type: !9, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !12) +// CHECK:STDOUT: !9 = !DISubroutineType(types: !10) +// CHECK:STDOUT: !10 = !{null, !11} +// CHECK:STDOUT: !11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +// CHECK:STDOUT: !12 = !{!13} +// CHECK:STDOUT: !13 = !DILocalVariable(arg: 1, scope: !8, type: !11) +// CHECK:STDOUT: !14 = !DILocation(line: 3, column: 1, scope: !8) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "IntArg", linkageName: "_CIntArg.Other", scope: null, file: !3, line: 6, type: !16, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !19) +// CHECK:STDOUT: !16 = !DISubroutineType(types: !17) +// CHECK:STDOUT: !17 = !{null, !18} +// CHECK:STDOUT: !18 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +// CHECK:STDOUT: !19 = !{!20} +// CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !15, type: !18) +// CHECK:STDOUT: !21 = !DILocation(line: 6, column: 1, scope: !15) +// CHECK:STDOUT: !22 = distinct !DISubprogram(name: "FloatArg", linkageName: "_CFloatArg.Other", scope: null, file: !3, line: 9, type: !9, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !23) +// CHECK:STDOUT: !23 = !{!24} +// CHECK:STDOUT: !24 = !DILocalVariable(arg: 1, scope: !22, type: !11) +// CHECK:STDOUT: !25 = !DILocation(line: 9, column: 1, scope: !22) // CHECK:STDOUT: ; ModuleID = 'function.carbon' // CHECK:STDOUT: source_filename = "function.carbon" // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" @@ -76,14 +124,91 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: ; Function Attrs: mustprogress uwtable // CHECK:STDOUT: define dso_local void @_Z1Gv() #0 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @_CF.Other() +// CHECK:STDOUT: call void @_ZL17NoArgs__cpp_thunkv() +// CHECK:STDOUT: call void @_ZL18BoolArg__cpp_thunkb(i1 noundef zeroext true) +// CHECK:STDOUT: call void @_ZL17IntArg__cpp_thunki(i32 noundef 123) +// CHECK:STDOUT: call void @_ZL19FloatArg__cpp_thunkf(float noundef 1.500000e+00) // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: declare void @_CF.Other() #1 +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal void @_ZL17NoArgs__cpp_thunkv() #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_CNoArgs__carbon_thunk.Other() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal void @_ZL18BoolArg__cpp_thunkb(i1 noundef zeroext %0) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.addr = alloca i8, align 1 +// CHECK:STDOUT: %storedv = zext i1 %0 to i8 +// CHECK:STDOUT: store i8 %storedv, ptr %.addr, align 1, !tbaa !11 +// CHECK:STDOUT: call void @_CBoolArg__carbon_thunk.Other(ptr noundef nonnull align 1 dereferenceable(1) %.addr) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal void @_ZL17IntArg__cpp_thunki(i32 noundef %0) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.addr = alloca i32, align 4 +// CHECK:STDOUT: store i32 %0, ptr %.addr, align 4, !tbaa !7 +// CHECK:STDOUT: call void @_CIntArg__carbon_thunk.Other(ptr noundef nonnull align 4 dereferenceable(4) %.addr) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal void @_ZL19FloatArg__cpp_thunkf(float noundef %0) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.addr = alloca float, align 4 +// CHECK:STDOUT: store float %0, ptr %.addr, align 4, !tbaa !13 +// CHECK:STDOUT: call void @_CFloatArg__carbon_thunk.Other(ptr noundef nonnull align 4 dereferenceable(4) %.addr) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CNoArgs.Other() +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CNoArgs__carbon_thunk.Other() #2 !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_CNoArgs.Other(), !dbg !19 +// CHECK:STDOUT: ret void, !dbg !19 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CBoolArg.Other(i1) +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CBoolArg__carbon_thunk.Other(ptr %_) #2 !dbg !20 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.1 = load i8, ptr %_, align 1, !dbg !26 +// CHECK:STDOUT: %.11 = trunc i8 %.1 to i1, !dbg !26 +// CHECK:STDOUT: call void @_CBoolArg.Other(i1 %.11), !dbg !26 +// CHECK:STDOUT: ret void, !dbg !26 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CIntArg.Other(i32) +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CIntArg__carbon_thunk.Other(ptr %_) #2 !dbg !27 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.1 = load i32, ptr %_, align 4, !dbg !33 +// CHECK:STDOUT: call void @_CIntArg.Other(i32 %.1), !dbg !33 +// CHECK:STDOUT: ret void, !dbg !33 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CFloatArg.Other(float) +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CFloatArg__carbon_thunk.Other(ptr %_) #2 !dbg !34 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.1 = load float, ptr %_, align 4, !dbg !37 +// CHECK:STDOUT: call void @_CFloatArg.Other(float %.1), !dbg !37 +// CHECK:STDOUT: ret void, !dbg !37 +// CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } -// CHECK:STDOUT: attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #2 = { nounwind } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} // CHECK:STDOUT: !llvm.dbg.cu = !{!5} @@ -100,6 +225,33 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: !8 = !{!"int", !9, i64 0} // CHECK:STDOUT: !9 = !{!"omnipotent char", !10, i64 0} // CHECK:STDOUT: !10 = !{!"Simple C++ TBAA"} +// CHECK:STDOUT: !11 = !{!12, !12, i64 0} +// CHECK:STDOUT: !12 = !{!"bool", !9, i64 0} +// CHECK:STDOUT: !13 = !{!14, !14, i64 0} +// CHECK:STDOUT: !14 = !{!"float", !9, i64 0} +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "NoArgs__carbon_thunk", linkageName: "_CNoArgs__carbon_thunk.Other", scope: null, file: !16, line: 2, type: !17, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !16 = !DIFile(filename: "other.carbon", directory: "") +// CHECK:STDOUT: !17 = !DISubroutineType(types: !18) +// CHECK:STDOUT: !18 = !{null} +// CHECK:STDOUT: !19 = !DILocation(line: 2, column: 1, scope: !15) +// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "BoolArg__carbon_thunk", linkageName: "_CBoolArg__carbon_thunk.Other", scope: null, file: !16, line: 3, type: !21, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !24) +// CHECK:STDOUT: !21 = !DISubroutineType(types: !22) +// CHECK:STDOUT: !22 = !{null, !23} +// CHECK:STDOUT: !23 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +// CHECK:STDOUT: !24 = !{!25} +// CHECK:STDOUT: !25 = !DILocalVariable(arg: 1, scope: !20, type: !23) +// CHECK:STDOUT: !26 = !DILocation(line: 3, column: 1, scope: !20) +// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "IntArg__carbon_thunk", linkageName: "_CIntArg__carbon_thunk.Other", scope: null, file: !16, line: 6, type: !28, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !31) +// CHECK:STDOUT: !28 = !DISubroutineType(types: !29) +// CHECK:STDOUT: !29 = !{null, !30} +// CHECK:STDOUT: !30 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +// CHECK:STDOUT: !31 = !{!32} +// CHECK:STDOUT: !32 = !DILocalVariable(arg: 1, scope: !27, type: !30) +// CHECK:STDOUT: !33 = !DILocation(line: 6, column: 1, scope: !27) +// CHECK:STDOUT: !34 = distinct !DISubprogram(name: "FloatArg__carbon_thunk", linkageName: "_CFloatArg__carbon_thunk.Other", scope: null, file: !16, line: 9, type: !21, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !35) +// CHECK:STDOUT: !35 = !{!36} +// CHECK:STDOUT: !36 = !DILocalVariable(arg: 1, scope: !34, type: !23) +// CHECK:STDOUT: !37 = !DILocation(line: 9, column: 1, scope: !34) // CHECK:STDOUT: ; ModuleID = 'single_file.carbon' // CHECK:STDOUT: source_filename = "single_file.carbon" // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" @@ -116,10 +268,17 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nounwind -// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !15 { +// CHECK:STDOUT: define void @_CF__carbon_thunk.Main() #0 !dbg !15 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @_Z2G2v(), !dbg !16 -// CHECK:STDOUT: ret void, !dbg !17 +// CHECK:STDOUT: call void @_CF.Main(), !dbg !16 +// CHECK:STDOUT: ret void, !dbg !16 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !17 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_Z2G2v(), !dbg !18 +// CHECK:STDOUT: ret void, !dbg !19 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress uwtable @@ -129,16 +288,23 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress nounwind uwtable -// CHECK:STDOUT: define linkonce_odr dso_local void @_Z2G1v() #2 comdat { +// CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress uwtable +// CHECK:STDOUT: define linkonce_odr dso_local void @_Z2G1v() #1 comdat { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @_CF.Main() +// CHECK:STDOUT: call void @_ZL12F__cpp_thunkv() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal void @_ZL12F__cpp_thunkv() #2 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_CF__carbon_thunk.Main() // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { nounwind } // CHECK:STDOUT: attributes #1 = { inlinehint mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } -// CHECK:STDOUT: attributes #2 = { inlinehint mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #2 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} // CHECK:STDOUT: !llvm.dbg.cu = !{!5} @@ -159,6 +325,8 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: !12 = !DISubroutineType(types: !13) // CHECK:STDOUT: !13 = !{null} // CHECK:STDOUT: !14 = !DILocation(line: 6, column: 1, scope: !11) -// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !6, line: 20, type: !12, spFlags: DISPFlagDefinition, unit: !5) -// CHECK:STDOUT: !16 = !DILocation(line: 20, column: 10, scope: !15) -// CHECK:STDOUT: !17 = !DILocation(line: 20, column: 1, scope: !15) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "F__carbon_thunk", linkageName: "_CF__carbon_thunk.Main", scope: null, file: !6, line: 6, type: !12, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !16 = !DILocation(line: 6, column: 1, scope: !15) +// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !6, line: 20, type: !12, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !18 = !DILocation(line: 20, column: 10, scope: !17) +// CHECK:STDOUT: !19 = !DILocation(line: 20, column: 1, scope: !17)