Przeglądaj źródła

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.
Nicholas Bishop 2 tygodni temu
rodzic
commit
114cf401c2

+ 8 - 3
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: {

+ 91 - 23
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<SemIR::ClassDecl>()) {
       // 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<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
+    llvm::ArrayRef<clang::QualType> 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<clang::Stmt*> 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());
+
+  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 call.get();
+  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<SemIR::TypeId> callee_param_type_ids)
-    -> clang::FunctionDecl* {
+    llvm::ArrayRef<SemIR::TypeId> 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<SemIR::TypeId> 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<SemIR::TypeId> 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<SemIR::TypeId> 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

+ 6 - 8
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<SemIR::InstId> param_pattern_ids,
                        llvm::ArrayRef<SemIR::InstId> 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),

+ 1 - 1
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<SemIR::InstId> param_pattern_ids,
                        llvm::ArrayRef<SemIR::InstId> outer_call_args)
     -> ThunkPatternMatchResults;
 

+ 3 - 12
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();
 }
 ''';
 

+ 55 - 0
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]+}} <<invalid sloc>> <invalid sloc>
+// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <<invalid sloc>> <invalid sloc> Carbon
+
+// --- thunk_with_args_and_return.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+fn F(i: i32) -> i32 {
+// CHECK:STDOUT: | `-FunctionDecl {{0x[a-f0-9]+}} <thunk_with_args_and_return.carbon:[[@LINE-1]]:21> col:21 used F__cpp_thunk 'int (int)' inline
+// CHECK:STDOUT: |   |-ParmVarDecl {{0x[a-f0-9]+}} <col:21> col:21 used 'int'
+// CHECK:STDOUT: |   |-CompoundStmt {{0x[a-f0-9]+}} <col:21>
+// CHECK:STDOUT: |   | |-DeclStmt {{0x[a-f0-9]+}} <col:21>
+// CHECK:STDOUT: |   | | `-VarDecl {{0x[a-f0-9]+}} <col:21> col:21 used return_storage 'int' nrvo
+// CHECK:STDOUT: |   | |-CallExpr {{0x[a-f0-9]+}} <col:21> 'void'
+// CHECK:STDOUT: |   | | |-ImplicitCastExpr {{0x[a-f0-9]+}} <col:21> 'void (*)(int &, int &)' <FunctionToPointerDecay>
+// CHECK:STDOUT: |   | | | `-DeclRefExpr {{0x[a-f0-9]+}} <col:21> 'void (int &, int &)' Function {{0x[a-f0-9]+}} 'F__carbon_thunk' 'void (int &, int &)'
+// CHECK:STDOUT: |   | | |-DeclRefExpr {{0x[a-f0-9]+}} <col:21> 'int' lvalue ParmVar {{0x[a-f0-9]+}} depth 0 index 0 'int'
+// CHECK:STDOUT: |   | | `-DeclRefExpr {{0x[a-f0-9]+}} <col:21> 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int'
+// CHECK:STDOUT: |   | `-ReturnStmt {{0x[a-f0-9]+}} <col:21> nrvo_candidate(Var {{0x[a-f0-9]+}} 'return_storage' 'int')
+// CHECK:STDOUT: |   |   `-DeclRefExpr {{0x[a-f0-9]+}} <col:21> 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int'
+// CHECK:STDOUT: |   |-AlwaysInlineAttr {{0x[a-f0-9]+}} <<invalid sloc>> Implicit always_inline
+// CHECK:STDOUT: |   `-InternalLinkageAttr {{0x[a-f0-9]+}} <<invalid sloc>> Implicit
+// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} <line:35:1, line:37:1> line:35:5 G 'int (int)'
+// CHECK:STDOUT:   |-ParmVarDecl {{0x[a-f0-9]+}} <col:7, col:11> col:11 used i 'int'
+// CHECK:STDOUT:   `-CompoundStmt {{0x[a-f0-9]+}} <col:14, line:37:1>
+// CHECK:STDOUT:     `-ReturnStmt {{0x[a-f0-9]+}} <line:36:3, col:21>
+// CHECK:STDOUT:       `-CallExpr {{0x[a-f0-9]+}} <col:10, col:21> 'int'
+// CHECK:STDOUT:         |-ImplicitCastExpr {{0x[a-f0-9]+}} <col:10, col:18> 'int (*)(int)' <FunctionToPointerDecay>
+// CHECK:STDOUT:         | `-DeclRefExpr {{0x[a-f0-9]+}} <col:10, col:18> '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]+}} <col:20> 'int' <LValueToRValue>
+// CHECK:STDOUT:           `-DeclRefExpr {{0x[a-f0-9]+}} <col:20> 'int' lvalue ParmVar {{0x[a-f0-9]+}} 'i' 'int'
+  return i;
+}
+
+inline Cpp '''
+int G(int i) {
+  return Carbon::F(i);
+}
+''';

+ 91 - 22
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<SemIR::InstId> param_pattern_ids,
                       llvm::ArrayRef<SemIR::InstId> 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<SemIR::InstId> args = args_vec;
 
   // If we have a self parameter, form `self.<callee_id>` 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<SemIR::InstId> param_pattern_ids,
+                           llvm::ArrayRef<SemIR::InstId> 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);
+}
+
+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);
 }
 
-auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id,
-                          SemIR::FunctionId function_id, SemIR::InstId thunk_id,
-                          SemIR::InstId callee_id) -> void {
+// 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<SemIR::InstId> 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<SemIR::InstId> 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 {

+ 12 - 6
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<SemIR::InstId> param_pattern_ids,
                       llvm::ArrayRef<SemIR::InstId> 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_

+ 41 - 3
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"