From 114cf401c200366f2af0063459892b0489cad4ba Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Wed, 15 Apr 2026 20:29:38 -0400 Subject: [PATCH] Support C++ calling Carbon functions with non-`()` return type (#7051) For calling non-`()` functions, the Carbon->Carbon thunk now takes an extra reference parameter and writes the target function's return value out to that parameter. (At the SemIR level this is how returns already work, but adding this extra reference parameter is needed so that the function is lowered correctly.) The C++ thunk now creates a local variable to be initialized by the Carbon thunk, and then returns that value to the original C++ caller. --- toolchain/check/call.cpp | 11 +- toolchain/check/cpp/export.cpp | 114 ++++++++++++++---- toolchain/check/pattern_match.cpp | 14 +-- toolchain/check/pattern_match.h | 2 +- .../interop/cpp/reverse/function.carbon | 15 +-- .../interop/cpp/reverse/thunk_ast.carbon | 55 +++++++++ toolchain/check/thunk.cpp | 113 +++++++++++++---- toolchain/check/thunk.h | 18 ++- .../interop/cpp/reverse/function.carbon | 44 ++++++- 9 files changed, 308 insertions(+), 78 deletions(-) create mode 100644 toolchain/check/testdata/interop/cpp/reverse/thunk_ast.carbon diff --git a/toolchain/check/call.cpp b/toolchain/check/call.cpp index 5ce0a7b36f1b..9fed5fa2b757 100644 --- a/toolchain/check/call.cpp +++ b/toolchain/check/call.cpp @@ -280,11 +280,16 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id, BuildNameRef(context, loc_id, callee.name_id, callee.thunk_decl_id(), callee_function.enclosing_specific_id); + auto param_pattern_ids = + context.inst_blocks().Get(context.functions() + .Get(callee_function.function_id) + .param_patterns_id); + // This recurses back into `PerformCall`. However, we never form a thunk // to a thunk, so we only recurse once. - return PerformThunkCall(context, loc_id, callee_function.function_id, - context.inst_blocks().Get(converted_args_id), - thunk_ref_id); + return PerformThunkCall( + context, loc_id, callee_function.function_id, param_pattern_ids, + context.inst_blocks().Get(converted_args_id), thunk_ref_id); } case SemIR::Function::SpecialFunctionKind::HasCppThunk: { diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index eaa8a11a0d2a..cdaa6fdc1e48 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -78,6 +78,7 @@ auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id, auto* namespace_decl = clang::NamespaceDecl::Create( context.ast_context(), decl_context, false, clang::SourceLocation(), clang::SourceLocation(), identifier_info, nullptr, false); + decl_context->addHiddenDecl(namespace_decl); decl_context = namespace_decl; } else if (inst.Is()) { // TODO: Provide a source location. @@ -90,6 +91,7 @@ auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id, record_decl->setAccess(clang::AS_public); } + decl_context->addHiddenDecl(record_decl); decl_context = record_decl; decl_context->setHasExternalLexicalStorage(); } else { @@ -186,7 +188,7 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context, 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"); + context.TODO(loc_id, "failed to map Carbon type to C++"); return nullptr; } auto ref_type = context.ast_context().getLValueReferenceType(cpp_type); @@ -233,16 +235,28 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context, static auto BuildCppToCarbonThunkDecl( Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context, clang::DeclarationName thunk_name, - llvm::ArrayRef thunk_param_types) -> clang::FunctionDecl* { + llvm::ArrayRef thunk_param_types, + SemIR::TypeId return_type_id) -> clang::FunctionDecl* { clang::ASTContext& ast_context = context.ast_context(); auto clang_loc = GetCppLocation(context, loc_id); + // Get the C++ return type (this corresponds to the return type of the + // target Carbon function). + clang::QualType cpp_return_type = context.ast_context().VoidTy; + if (return_type_id != SemIR::TypeId::None) { + cpp_return_type = MapToCppType(context, return_type_id); + if (cpp_return_type.isNull()) { + context.TODO(loc_id, "failed to map Carbon return type to C++ type"); + return nullptr; + } + } + clang::DeclarationNameInfo name_info(thunk_name, clang_loc); auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); clang::QualType thunk_function_type = ast_context.getFunctionType( - ast_context.VoidTy, thunk_param_types, ext_proto_info); + cpp_return_type, thunk_param_types, ext_proto_info); auto* tinfo = ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc); @@ -296,6 +310,33 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema, -> clang::StmtResult { clang::SourceLocation clang_loc = function_decl->getLocation(); + llvm::SmallVector stmts; + + // Create return storage if the target function returns non-void. + const bool has_return_value = !function_decl->getReturnType()->isVoidType(); + clang::VarDecl* return_storage_var_decl = nullptr; + clang::ExprResult return_storage_expr; + if (has_return_value) { + auto& return_storage_ident = + sema.getASTContext().Idents.get("return_storage"); + return_storage_var_decl = + clang::VarDecl::Create(sema.getASTContext(), function_decl, + /*StartLoc=*/clang_loc, + /*IdLoc=*/clang_loc, &return_storage_ident, + function_decl->getReturnType(), + /*TInfo=*/nullptr, clang::SC_None); + return_storage_var_decl->setNRVOVariable(true); + return_storage_expr = sema.BuildDeclRefExpr( + return_storage_var_decl, return_storage_var_decl->getType(), + clang::VK_LValue, clang_loc); + + auto decl_group_ref = clang::DeclGroupRef(return_storage_var_decl); + auto decl_stmt = + sema.ActOnDeclStmt(clang::Sema::DeclGroupPtrTy::make(decl_group_ref), + clang_loc, clang_loc); + stmts.push_back(decl_stmt.get()); + } + clang::ExprResult callee = sema.BuildDeclRefExpr( callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue, clang_loc); @@ -307,11 +348,28 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema, clang::VK_LValue, clang_loc); call_args.push_back(call_arg); } + + // If the target function returns non-void, the Carbon thunk takes an + // extra output parameter referencing the return storage. + if (has_return_value) { + call_args.push_back(return_storage_expr.get()); + } + clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc, call_args, clang_loc); CARBON_CHECK(call.isUsable()); + stmts.push_back(call.get()); - return call.get(); + if (has_return_value) { + auto* return_stmt = clang::ReturnStmt::Create( + sema.getASTContext(), clang_loc, return_storage_expr.get(), + return_storage_var_decl); + stmts.push_back(return_stmt); + } + + return clang::CompoundStmt::Create(sema.getASTContext(), stmts, + clang::FPOptionsOverride(), clang_loc, + clang_loc); } // Create a C++ thunk that calls the Carbon thunk. The C++ thunk's @@ -321,8 +379,8 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema, static auto BuildCppToCarbonThunk( Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context, llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl, - llvm::ArrayRef callee_param_type_ids) - -> clang::FunctionDecl* { + llvm::ArrayRef callee_param_type_ids, + SemIR::TypeId return_type_id) -> clang::FunctionDecl* { // Create the thunk's name. llvm::SmallString<64> thunk_name = base_name; thunk_name += "__cpp_thunk"; @@ -339,7 +397,7 @@ static auto BuildCppToCarbonThunk( } auto* thunk_function_decl = BuildCppToCarbonThunkDecl( - context, loc_id, decl_context, &thunk_ident, param_types); + context, loc_id, decl_context, &thunk_ident, param_types, return_type_id); // Build the thunk function body. clang::Sema& sema = context.clang_sema(); @@ -358,7 +416,8 @@ static auto BuildCppToCarbonThunk( // 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, + Context& context, SemIR::LocId loc_id, SemIR::FunctionId callee_function_id, + const SemIR::Function& callee, llvm::ArrayRef callee_param_type_ids) -> SemIR::FunctionId { // Create the thunk's name. llvm::SmallString<64> thunk_name = @@ -368,16 +427,27 @@ static auto BuildCarbonToCarbonThunk( auto thunk_name_id = SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName())); + // Get the thunk's parameters. These match the callee parameters, with + // the addition of an output parameter for the callee's return value + // (if it has one). + llvm::SmallVector thunk_param_type_ids(callee_param_type_ids); + auto callee_return_type_id = callee.GetDeclaredReturnType(context.sem_ir()); + if (callee_return_type_id != SemIR::TypeId::None) { + thunk_param_type_ids.push_back(callee_return_type_id); + } + 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, + .param_type_ids = thunk_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()); + + BuildThunkDefinitionForExport( + context, carbon_thunk_function_id, callee_function_id, + context.functions().Get(carbon_thunk_function_id).first_decl_id(), + callee.first_decl_id()); return carbon_thunk_function_id; } @@ -387,13 +457,6 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_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 " @@ -415,9 +478,13 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id, return nullptr; } - // Get the parameter types of the Carbon function being called. + // Get the parameter types of the Carbon function being + // called. Exclude return params, if present. auto callee_function_params = context.inst_blocks().Get(callee.call_param_patterns_id); + callee_function_params = + callee_function_params.drop_back(callee.call_param_ranges.return_size()); + llvm::SmallVector callee_param_type_ids; for (auto callee_param_inst_id : callee_function_params) { auto scrutinee_type_id = ExtractScrutineeType( @@ -427,8 +494,8 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_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); + auto carbon_thunk_function_id = BuildCarbonToCarbonThunk( + context, loc_id, callee_function_id, callee, callee_param_type_ids); // Create a `clang::FunctionDecl` that can be used to call the Carbon thunk. auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn( @@ -440,7 +507,8 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id, // Create a C++ thunk that calls the Carbon thunk. return BuildCppToCarbonThunk(context, loc_id, decl_context, context.names().GetFormatted(callee.name_id), - carbon_function_decl, callee_param_type_ids); + carbon_function_decl, callee_param_type_ids, + callee.GetDeclaredReturnType(context.sem_ir())); } } // namespace Carbon::Check diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index 42a26bcdb357..cb450e3d9a57 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -934,7 +934,7 @@ auto CalleePatternMatch(Context& context, } auto ThunkPatternMatch(Context& context, SemIR::InstId self_pattern_id, - SemIR::InstBlockId param_patterns_id, + llvm::ArrayRef param_pattern_ids, llvm::ArrayRef outer_call_args) -> ThunkPatternMatchResults { ThunkState state = {.outer_call_args = outer_call_args}; @@ -950,13 +950,11 @@ auto ThunkPatternMatch(Context& context, SemIR::InstId self_pattern_id, .work = MatchContext::PreWork{.scrutinee_id = SemIR::InstId::None}})); } - if (param_patterns_id.has_value()) { - for (SemIR::InstId inst_id : context.inst_blocks().Get(param_patterns_id)) { - inner_args.push_back(match.MatchWithResult( - &state, {.pattern_id = inst_id, - .work = MatchContext::PreWork{.scrutinee_id = - SemIR::InstId::None}})); - } + for (SemIR::InstId inst_id : param_pattern_ids) { + inner_args.push_back(match.MatchWithResult( + &state, + {.pattern_id = inst_id, + .work = MatchContext::PreWork{.scrutinee_id = SemIR::InstId::None}})); } return {.syntactic_args = std::move(inner_args), diff --git a/toolchain/check/pattern_match.h b/toolchain/check/pattern_match.h index 3cf48d097c83..35d18e1bb859 100644 --- a/toolchain/check/pattern_match.h +++ b/toolchain/check/pattern_match.h @@ -58,7 +58,7 @@ struct ThunkPatternMatchResults { // computes the corresponding syntactic argument list, suitable for passing to // the inner part of the thunked function call. auto ThunkPatternMatch(Context& context, SemIR::InstId self_pattern_id, - SemIR::InstBlockId param_patterns_id, + llvm::ArrayRef param_pattern_ids, llvm::ArrayRef outer_call_args) -> ThunkPatternMatchResults; diff --git a/toolchain/check/testdata/interop/cpp/reverse/function.carbon b/toolchain/check/testdata/interop/cpp/reverse/function.carbon index 7608a45525c7..a11b40e72588 100644 --- a/toolchain/check/testdata/interop/cpp/reverse/function.carbon +++ b/toolchain/check/testdata/interop/cpp/reverse/function.carbon @@ -35,23 +35,14 @@ void G() { } '''; -// --- fail_todo_non_void.carbon +// --- return_int.carbon library "[[@TEST_NAME]]"; -// CHECK:STDERR: fail_todo_non_void.carbon:[[@LINE+5]]:1: in import [InImport] -// CHECK:STDERR: other.carbon:4:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with return type other than `()`` [SemanticsTodo] -// CHECK:STDERR: fn F2() -> i32 { return 0; } -// CHECK:STDERR: ^~~~~~~~~~~~~~~~ -// CHECK:STDERR: import Other; import Cpp inline ''' -void G() { - // CHECK:STDERR: fail_todo_non_void.carbon:[[@LINE+4]]:18: error: no member named 'F2' in namespace 'Carbon::Other' [CppInteropParseError] - // CHECK:STDERR: 16 | Carbon::Other::F2(); - // CHECK:STDERR: | ^~ - // CHECK:STDERR: - Carbon::Other::F2(); +int G() { + return Carbon::Other::F2(); } '''; diff --git a/toolchain/check/testdata/interop/cpp/reverse/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/reverse/thunk_ast.carbon new file mode 100644 index 000000000000..4eee7c28525d --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/reverse/thunk_ast.carbon @@ -0,0 +1,55 @@ +// 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/int.carbon +// EXTRA-ARGS: --dump-cpp-ast +// SET-CHECK-SUBSET +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/reverse/thunk_ast.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/reverse/thunk_ast.carbon +// CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <> +// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> Carbon + +// --- thunk_with_args_and_return.carbon + +library "[[@TEST_NAME]]"; + +import Cpp; + +fn F(i: i32) -> i32 { +// CHECK:STDOUT: | `-FunctionDecl {{0x[a-f0-9]+}} col:21 used F__cpp_thunk 'int (int)' inline +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} col:21 used 'int' +// CHECK:STDOUT: | |-CompoundStmt {{0x[a-f0-9]+}} +// CHECK:STDOUT: | | |-DeclStmt {{0x[a-f0-9]+}} +// CHECK:STDOUT: | | | `-VarDecl {{0x[a-f0-9]+}} col:21 used return_storage 'int' nrvo +// CHECK:STDOUT: | | |-CallExpr {{0x[a-f0-9]+}} 'void' +// CHECK:STDOUT: | | | |-ImplicitCastExpr {{0x[a-f0-9]+}} 'void (*)(int &, int &)' +// CHECK:STDOUT: | | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'void (int &, int &)' Function {{0x[a-f0-9]+}} 'F__carbon_thunk' 'void (int &, int &)' +// CHECK:STDOUT: | | | |-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue ParmVar {{0x[a-f0-9]+}} depth 0 index 0 'int' +// CHECK:STDOUT: | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int' +// CHECK:STDOUT: | | `-ReturnStmt {{0x[a-f0-9]+}} nrvo_candidate(Var {{0x[a-f0-9]+}} 'return_storage' 'int') +// CHECK:STDOUT: | | `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int' +// CHECK:STDOUT: | |-AlwaysInlineAttr {{0x[a-f0-9]+}} <> Implicit always_inline +// CHECK:STDOUT: | `-InternalLinkageAttr {{0x[a-f0-9]+}} <> Implicit +// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} line:35:5 G 'int (int)' +// CHECK:STDOUT: |-ParmVarDecl {{0x[a-f0-9]+}} col:11 used i 'int' +// CHECK:STDOUT: `-CompoundStmt {{0x[a-f0-9]+}} +// CHECK:STDOUT: `-ReturnStmt {{0x[a-f0-9]+}} +// CHECK:STDOUT: `-CallExpr {{0x[a-f0-9]+}} 'int' +// CHECK:STDOUT: |-ImplicitCastExpr {{0x[a-f0-9]+}} 'int (*)(int)' +// CHECK:STDOUT: | `-DeclRefExpr {{0x[a-f0-9]+}} 'int (int)' lvalue Function {{0x[a-f0-9]+}} 'F__cpp_thunk' 'int (int)' +// CHECK:STDOUT: | `-NestedNameSpecifier Namespace {{0x[a-f0-9]+}} 'Carbon' +// CHECK:STDOUT: `-ImplicitCastExpr {{0x[a-f0-9]+}} 'int' +// CHECK:STDOUT: `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue ParmVar {{0x[a-f0-9]+}} 'i' 'int' + return i; +} + +inline Cpp ''' +int G(int i) { + return Carbon::F(i); +} +'''; diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 5ea60f53fc00..7c106dcf6111 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -261,13 +261,13 @@ static auto HasDeclaredReturnType(Context& context, auto PerformThunkCall(Context& context, SemIR::LocId loc_id, SemIR::FunctionId function_id, + llvm::ArrayRef param_pattern_ids, llvm::ArrayRef call_arg_ids, SemIR::InstId callee_id) -> SemIR::InstId { auto& function = context.functions().Get(function_id); - auto [args_vec, ignored_call_args] = - ThunkPatternMatch(context, function.self_param_id, - function.param_patterns_id, call_arg_ids); + auto [args_vec, ignored_call_args] = ThunkPatternMatch( + context, function.self_param_id, param_pattern_ids, call_arg_ids); llvm::ArrayRef args = args_vec; // If we have a self parameter, form `self.` if needed. @@ -287,7 +287,10 @@ auto PerformThunkCall(Context& context, SemIR::LocId loc_id, // Build a call to a function that forwards the arguments of the enclosing // function, for use when constructing a thunk. static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id, - SemIR::InstId callee_id) -> SemIR::InstId { + SemIR::InstId callee_id, + llvm::ArrayRef param_pattern_ids, + llvm::ArrayRef call_arg_ids) + -> SemIR::InstId { auto& function = context.functions().Get(function_id); // Build a `NameRef` naming the callee, and a `SpecificConstant` if needed. @@ -297,31 +300,40 @@ static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id, callee_id = BuildNameRef(context, loc_id, function.name_id, callee_id, callee_type.specific_id); - auto call_params = context.inst_blocks().Get(function.call_params_id); - return PerformThunkCall(context, loc_id, function_id, call_params, callee_id); + return PerformThunkCall(context, loc_id, function_id, param_pattern_ids, + call_arg_ids, callee_id); } -auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id, - SemIR::FunctionId function_id, SemIR::InstId thunk_id, - SemIR::InstId callee_id) -> void { +static auto StartThunkFunctionDefinition(Context& context, + SemIR::FunctionId function_id, + SemIR::InstId thunk_id, + SemIR::InstId callee_id) { + // The check below produces diagnostics referring to the signature, so also + // note the callee. + Diagnostics::AnnotationScope annot_scope( + &context.emitter(), [&](DiagnosticBuilder& builder) { + CARBON_DIAGNOSTIC(ThunkCallee, Note, + "while building thunk calling this function"); + builder.Note(callee_id, ThunkCallee); + }); + + StartFunctionDefinition(context, thunk_id, function_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) -> 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 // why, rather than the primary error message being whatever went wrong // building the thunk. - { - // The check below produces diagnostics referring to the signature, so also - // note the callee. - Diagnostics::AnnotationScope annot_scope( - &context.emitter(), [&](DiagnosticBuilder& builder) { - CARBON_DIAGNOSTIC(ThunkCallee, Note, - "while building thunk calling this function"); - builder.Note(callee_id, ThunkCallee); - }); - - StartFunctionDefinition(context, thunk_id, function_id); - } + StartThunkFunctionDefinition(context, function_id, thunk_id, callee_id); // The checks below produce diagnostics pointing at the callee, so also note // the signature. @@ -334,7 +346,15 @@ auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id, ThunkSignature); }); - auto call_id = BuildThunkCall(context, function_id, callee_id); + const auto& function = context.functions().Get(function_id); + llvm::ArrayRef param_pattern_ids; + if (function.param_patterns_id.has_value()) { + param_pattern_ids = context.inst_blocks().Get(function.param_patterns_id); + } + auto call_param_ids = context.inst_blocks().Get(function.call_params_id); + + auto call_id = BuildThunkCall(context, function_id, callee_id, + param_pattern_ids, call_param_ids); if (HasDeclaredReturnType(context, function_id)) { BuildReturnWithExpr(context, SemIR::LocId(callee_id), call_id); } else { @@ -345,6 +365,55 @@ auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id, FinishFunctionDefinition(context, function_id); } +auto BuildThunkDefinitionForExport(Context& context, + SemIR::FunctionId thunk_function_id, + SemIR::FunctionId callee_function_id, + SemIR::InstId thunk_id, + SemIR::InstId callee_id) -> void { + auto& thunk_function = context.functions().Get(thunk_function_id); + auto& callee_function = context.functions().Get(callee_function_id); + + StartThunkFunctionDefinition(context, thunk_function_id, thunk_id, callee_id); + + const bool thunk_has_return_param = + callee_function.return_type_inst_id != SemIR::TypeInstId::None; + + llvm::ArrayRef param_pattern_ids; + if (thunk_function.param_patterns_id.has_value()) { + param_pattern_ids = + context.inst_blocks().Get(thunk_function.param_patterns_id); + } + auto call_param_ids = + context.inst_blocks().Get(thunk_function.call_params_id); + + if (thunk_has_return_param) { + param_pattern_ids = param_pattern_ids.drop_back(); + call_param_ids = call_param_ids.drop_back(); + } + + auto call_id = BuildThunkCall(context, thunk_function_id, callee_id, + param_pattern_ids, call_param_ids); + if (thunk_has_return_param) { + auto out_param_id = + context.inst_blocks().Get(thunk_function.call_params_id).back(); + + SemIR::LocId loc_id(out_param_id); + auto init_id = + Initialize(context, loc_id, out_param_id, call_id, /*for_return=*/true); + AddInst(context, loc_id, + SemIR::Assign{ + .lhs_id = out_param_id, + .rhs_id = init_id, + }); + } else { + DiscardExpr(context, call_id); + } + + BuildReturnWithNoExpr(context, SemIR::LocId(callee_id)); + + FinishFunctionDefinition(context, thunk_function_id); +} + auto BuildThunkDefinition(Context& context, DeferredDefinitionWorklist::DefineThunk&& task) -> void { diff --git a/toolchain/check/thunk.h b/toolchain/check/thunk.h index d0fab73033f6..e1589987b545 100644 --- a/toolchain/check/thunk.h +++ b/toolchain/check/thunk.h @@ -11,12 +11,6 @@ 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. @@ -31,6 +25,7 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id, // of call arguments for `function_id`, not a syntactic argument list. auto PerformThunkCall(Context& context, SemIR::LocId loc_id, SemIR::FunctionId function_id, + llvm::ArrayRef param_pattern_ids, llvm::ArrayRef call_arg_ids, SemIR::InstId callee_id) -> SemIR::InstId; @@ -40,6 +35,17 @@ auto BuildThunkDefinition(Context& context, DeferredDefinitionWorklist::DefineThunk&& task) -> void; +// Given a declaration of a thunk and the function that it should call, +// build a thunk body for calling a Carbon function from a C++ +// function. If the callee has a return value, the thunk returns it +// through an explicit output parameter at the end of the parameter +// list. +auto BuildThunkDefinitionForExport(Context& context, + SemIR::FunctionId thunk_function_id, + SemIR::FunctionId callee_function_id, + SemIR::InstId thunk_id, + SemIR::InstId callee_id) -> void; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_THUNK_H_ diff --git a/toolchain/lower/testdata/interop/cpp/reverse/function.carbon b/toolchain/lower/testdata/interop/cpp/reverse/function.carbon index b1d320c983b0..7b3764c6341f 100644 --- a/toolchain/lower/testdata/interop/cpp/reverse/function.carbon +++ b/toolchain/lower/testdata/interop/cpp/reverse/function.carbon @@ -22,17 +22,21 @@ fn IntArg(a: i32) { fn FloatArg(a: f32) { a; } +fn IntReturn() -> i32 { + return 123; +} // --- function.carbon library "[[@TEST_NAME]]"; import Other; import Cpp inline ''' -void G() { +int G() { Carbon::Other::NoArgs(); Carbon::Other::BoolArg(true); Carbon::Other::IntArg(123); Carbon::Other::FloatArg(1.5); + return Carbon::Other::IntReturn(); } '''; @@ -85,6 +89,12 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: ret void, !dbg !25 // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i32 @_CIntReturn.Other() #0 !dbg !26 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret i32 123, !dbg !29 +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { nounwind } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1} @@ -116,19 +126,24 @@ fn H() { Cpp.G2(); } // 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: !26 = distinct !DISubprogram(name: "IntReturn", linkageName: "_CIntReturn.Other", scope: null, file: !3, line: 12, type: !27, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !27 = !DISubroutineType(types: !28) +// CHECK:STDOUT: !28 = !{!18} +// CHECK:STDOUT: !29 = !DILocation(line: 13, column: 3, scope: !26) // 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" // CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: mustprogress uwtable -// CHECK:STDOUT: define dso_local void @_Z1Gv() #0 { +// CHECK:STDOUT: define dso_local noundef i32 @_Z1Gv() #0 { // CHECK:STDOUT: entry: // CHECK:STDOUT: call void @_ZN6Carbon5OtherL17NoArgs__cpp_thunkEv() // CHECK:STDOUT: call void @_ZN6Carbon5OtherL18BoolArg__cpp_thunkEb(i1 noundef zeroext true) // CHECK:STDOUT: call void @_ZN6Carbon5OtherL17IntArg__cpp_thunkEi(i32 noundef 123) // CHECK:STDOUT: call void @_ZN6Carbon5OtherL19FloatArg__cpp_thunkEf(float noundef 1.500000e+00) -// CHECK:STDOUT: ret void +// CHECK:STDOUT: %call = call noundef i32 @_ZN6Carbon5OtherL20IntReturn__cpp_thunkEv() +// CHECK:STDOUT: ret i32 %call // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable @@ -166,6 +181,15 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: ret void // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal noundef i32 @_ZN6Carbon5OtherL20IntReturn__cpp_thunkEv() #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %retval = alloca i32, align 4 +// CHECK:STDOUT: call void @_CIntReturn__carbon_thunk.Other(ptr noundef nonnull align 4 dereferenceable(4) %retval) +// CHECK:STDOUT: %0 = load i32, ptr %retval, align 4 +// CHECK:STDOUT: ret i32 %0 +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: declare void @_CNoArgs.Other() // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: nounwind @@ -206,6 +230,16 @@ fn H() { Cpp.G2(); } // CHECK:STDOUT: ret void, !dbg !37 // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: declare i32 @_CIntReturn.Other() +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CIntReturn__carbon_thunk.Other(ptr %_) #2 !dbg !38 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %IntReturn.call = call i32 @_CIntReturn.Other(), !dbg !41 +// CHECK:STDOUT: store i32 %IntReturn.call, ptr %_, align 4, !dbg !41 +// CHECK:STDOUT: ret void, !dbg !41 +// 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 = { 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 } @@ -252,6 +286,10 @@ fn H() { Cpp.G2(); } // 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: !38 = distinct !DISubprogram(name: "IntReturn__carbon_thunk", linkageName: "_CIntReturn__carbon_thunk.Other", scope: null, file: !16, line: 12, type: !28, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !39) +// CHECK:STDOUT: !39 = !{!40} +// CHECK:STDOUT: !40 = !DILocalVariable(arg: 1, scope: !38, type: !30) +// CHECK:STDOUT: !41 = !DILocation(line: 12, column: 1, scope: !38) // 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"