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"