Bläddra i källkod

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 veckor sedan
förälder
incheckning
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(),
           BuildNameRef(context, loc_id, callee.name_id, callee.thunk_decl_id(),
                        callee_function.enclosing_specific_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
       // This recurses back into `PerformCall`. However, we never form a thunk
       // to a thunk, so we only recurse once.
       // 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: {
     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(
       auto* namespace_decl = clang::NamespaceDecl::Create(
           context.ast_context(), decl_context, false, clang::SourceLocation(),
           context.ast_context(), decl_context, false, clang::SourceLocation(),
           clang::SourceLocation(), identifier_info, nullptr, false);
           clang::SourceLocation(), identifier_info, nullptr, false);
+      decl_context->addHiddenDecl(namespace_decl);
       decl_context = namespace_decl;
       decl_context = namespace_decl;
     } else if (inst.Is<SemIR::ClassDecl>()) {
     } else if (inst.Is<SemIR::ClassDecl>()) {
       // TODO: Provide a source location.
       // TODO: Provide a source location.
@@ -90,6 +91,7 @@ auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
         record_decl->setAccess(clang::AS_public);
         record_decl->setAccess(clang::AS_public);
       }
       }
 
 
+      decl_context->addHiddenDecl(record_decl);
       decl_context = record_decl;
       decl_context = record_decl;
       decl_context->setHasExternalLexicalStorage();
       decl_context->setHasExternalLexicalStorage();
     } else {
     } else {
@@ -186,7 +188,7 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
         context.sem_ir(), context.insts().Get(param_inst_id).type_id());
         context.sem_ir(), context.insts().Get(param_inst_id).type_id());
     auto cpp_type = MapToCppType(context, scrutinee_type_id);
     auto cpp_type = MapToCppType(context, scrutinee_type_id);
     if (cpp_type.isNull()) {
     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;
       return nullptr;
     }
     }
     auto ref_type = context.ast_context().getLValueReferenceType(cpp_type);
     auto ref_type = context.ast_context().getLValueReferenceType(cpp_type);
@@ -233,16 +235,28 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
 static auto BuildCppToCarbonThunkDecl(
 static auto BuildCppToCarbonThunkDecl(
     Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
     Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
     clang::DeclarationName thunk_name,
     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();
   clang::ASTContext& ast_context = context.ast_context();
 
 
   auto clang_loc = GetCppLocation(context, loc_id);
   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);
   clang::DeclarationNameInfo name_info(thunk_name, clang_loc);
 
 
   auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
   auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
   clang::QualType thunk_function_type = ast_context.getFunctionType(
   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 =
   auto* tinfo =
       ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc);
       ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc);
@@ -296,6 +310,33 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
     -> clang::StmtResult {
     -> clang::StmtResult {
   clang::SourceLocation clang_loc = function_decl->getLocation();
   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(
   clang::ExprResult callee = sema.BuildDeclRefExpr(
       callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue,
       callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue,
       clang_loc);
       clang_loc);
@@ -307,11 +348,28 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
                               clang::VK_LValue, clang_loc);
                               clang::VK_LValue, clang_loc);
     call_args.push_back(call_arg);
     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,
   clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
                                               call_args, clang_loc);
                                               call_args, clang_loc);
   CARBON_CHECK(call.isUsable());
   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
 // 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(
 static auto BuildCppToCarbonThunk(
     Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
     Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
     llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl,
     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.
   // Create the thunk's name.
   llvm::SmallString<64> thunk_name = base_name;
   llvm::SmallString<64> thunk_name = base_name;
   thunk_name += "__cpp_thunk";
   thunk_name += "__cpp_thunk";
@@ -339,7 +397,7 @@ static auto BuildCppToCarbonThunk(
   }
   }
 
 
   auto* thunk_function_decl = BuildCppToCarbonThunkDecl(
   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.
   // Build the thunk function body.
   clang::Sema& sema = context.clang_sema();
   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
 // Create a Carbon thunk that calls `callee`. The thunk's parameters are
 // all references to the callee parameter type.
 // all references to the callee parameter type.
 static auto BuildCarbonToCarbonThunk(
 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 {
     llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids) -> SemIR::FunctionId {
   // Create the thunk's name.
   // Create the thunk's name.
   llvm::SmallString<64> thunk_name =
   llvm::SmallString<64> thunk_name =
@@ -368,16 +427,27 @@ static auto BuildCarbonToCarbonThunk(
   auto thunk_name_id =
   auto thunk_name_id =
       SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName()));
       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 =
   auto carbon_thunk_function_id =
       MakeGeneratedFunctionDecl(context, loc_id,
       MakeGeneratedFunctionDecl(context, loc_id,
                                 {.parent_scope_id = callee.parent_scope_id,
                                 {.parent_scope_id = callee.parent_scope_id,
                                  .name_id = thunk_name_id,
                                  .name_id = thunk_name_id,
-                                 .param_type_ids = callee_param_type_ids,
+                                 .param_type_ids = thunk_param_type_ids,
                                  .params_are_refs = true})
                                  .params_are_refs = true})
           .second;
           .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;
   return carbon_thunk_function_id;
 }
 }
@@ -387,13 +457,6 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
     -> clang::FunctionDecl* {
     -> clang::FunctionDecl* {
   const SemIR::Function& callee = context.functions().Get(callee_function_id);
   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()) {
   if (callee.generic_id.has_value()) {
     context.TODO(loc_id,
     context.TODO(loc_id,
                  "unsupported: C++ calling a Carbon function with "
                  "unsupported: C++ calling a Carbon function with "
@@ -415,9 +478,13 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
     return nullptr;
     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 =
   auto callee_function_params =
       context.inst_blocks().Get(callee.call_param_patterns_id);
       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;
   llvm::SmallVector<SemIR::TypeId> callee_param_type_ids;
   for (auto callee_param_inst_id : callee_function_params) {
   for (auto callee_param_inst_id : callee_function_params) {
     auto scrutinee_type_id = ExtractScrutineeType(
     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
   // Create a Carbon thunk that calls the callee. The thunk's parameters
   // are all references so that the ABI is compatible with C++ callers.
   // 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.
   // Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
   auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
   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.
   // Create a C++ thunk that calls the Carbon thunk.
   return BuildCppToCarbonThunk(context, loc_id, decl_context,
   return BuildCppToCarbonThunk(context, loc_id, decl_context,
                                context.names().GetFormatted(callee.name_id),
                                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
 }  // 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,
 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)
                        llvm::ArrayRef<SemIR::InstId> outer_call_args)
     -> ThunkPatternMatchResults {
     -> ThunkPatternMatchResults {
   ThunkState state = {.outer_call_args = outer_call_args};
   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}}));
          .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),
   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
 // computes the corresponding syntactic argument list, suitable for passing to
 // the inner part of the thunked function call.
 // the inner part of the thunked function call.
 auto ThunkPatternMatch(Context& context, SemIR::InstId self_pattern_id,
 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)
                        llvm::ArrayRef<SemIR::InstId> outer_call_args)
     -> ThunkPatternMatchResults;
     -> 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]]";
 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 Other;
 import Cpp inline '''
 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,
 auto PerformThunkCall(Context& context, SemIR::LocId loc_id,
                       SemIR::FunctionId function_id,
                       SemIR::FunctionId function_id,
+                      llvm::ArrayRef<SemIR::InstId> param_pattern_ids,
                       llvm::ArrayRef<SemIR::InstId> call_arg_ids,
                       llvm::ArrayRef<SemIR::InstId> call_arg_ids,
                       SemIR::InstId callee_id) -> SemIR::InstId {
                       SemIR::InstId callee_id) -> SemIR::InstId {
   auto& function = context.functions().Get(function_id);
   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;
   llvm::ArrayRef<SemIR::InstId> args = args_vec;
 
 
   // If we have a self parameter, form `self.<callee_id>` if needed.
   // 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
 // Build a call to a function that forwards the arguments of the enclosing
 // function, for use when constructing a thunk.
 // function, for use when constructing a thunk.
 static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id,
 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);
   auto& function = context.functions().Get(function_id);
 
 
   // Build a `NameRef` naming the callee, and a `SpecificConstant` if needed.
   // 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_id = BuildNameRef(context, loc_id, function.name_id, callee_id,
                            callee_type.specific_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
   // 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
   // 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
   // thunk because of a type mismatch, but couldn't, with notes explaining
   // why, rather than the primary error message being whatever went wrong
   // why, rather than the primary error message being whatever went wrong
   // building the thunk.
   // 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 checks below produce diagnostics pointing at the callee, so also note
   // the signature.
   // the signature.
@@ -334,7 +346,15 @@ auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id,
                      ThunkSignature);
                      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)) {
   if (HasDeclaredReturnType(context, function_id)) {
     BuildReturnWithExpr(context, SemIR::LocId(callee_id), call_id);
     BuildReturnWithExpr(context, SemIR::LocId(callee_id), call_id);
   } else {
   } else {
@@ -345,6 +365,55 @@ auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id,
   FinishFunctionDefinition(context, function_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,
 auto BuildThunkDefinition(Context& context,
                           DeferredDefinitionWorklist::DefineThunk&& task)
                           DeferredDefinitionWorklist::DefineThunk&& task)
     -> void {
     -> void {

+ 12 - 6
toolchain/check/thunk.h

@@ -11,12 +11,6 @@
 
 
 namespace Carbon::Check {
 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
 // Given a function signature and a callee function, build a thunk that matches
 // the given signature and calls the specified callee. Returns the callee
 // the given signature and calls the specified callee. Returns the callee
 // unchanged if it can be used directly.
 // 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.
 // of call arguments for `function_id`, not a syntactic argument list.
 auto PerformThunkCall(Context& context, SemIR::LocId loc_id,
 auto PerformThunkCall(Context& context, SemIR::LocId loc_id,
                       SemIR::FunctionId function_id,
                       SemIR::FunctionId function_id,
+                      llvm::ArrayRef<SemIR::InstId> param_pattern_ids,
                       llvm::ArrayRef<SemIR::InstId> call_arg_ids,
                       llvm::ArrayRef<SemIR::InstId> call_arg_ids,
                       SemIR::InstId callee_id) -> SemIR::InstId;
                       SemIR::InstId callee_id) -> SemIR::InstId;
 
 
@@ -40,6 +35,17 @@ auto BuildThunkDefinition(Context& context,
                           DeferredDefinitionWorklist::DefineThunk&& task)
                           DeferredDefinitionWorklist::DefineThunk&& task)
     -> void;
     -> 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
 }  // namespace Carbon::Check
 
 
 #endif  // CARBON_TOOLCHAIN_CHECK_THUNK_H_
 #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) {
 fn FloatArg(a: f32) {
   a;
   a;
 }
 }
+fn IntReturn() -> i32 {
+  return 123;
+}
 // --- function.carbon
 // --- function.carbon
 
 
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 import Other;
 import Other;
 import Cpp inline '''
 import Cpp inline '''
-void G() {
+int G() {
   Carbon::Other::NoArgs();
   Carbon::Other::NoArgs();
   Carbon::Other::BoolArg(true);
   Carbon::Other::BoolArg(true);
   Carbon::Other::IntArg(123);
   Carbon::Other::IntArg(123);
   Carbon::Other::FloatArg(1.5);
   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:   ret void, !dbg !25
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // 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: attributes #0 = { nounwind }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
@@ -116,19 +126,24 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: !23 = !{!24}
 // CHECK:STDOUT: !23 = !{!24}
 // CHECK:STDOUT: !24 = !DILocalVariable(arg: 1, scope: !22, type: !11)
 // CHECK:STDOUT: !24 = !DILocalVariable(arg: 1, scope: !22, type: !11)
 // CHECK:STDOUT: !25 = !DILocation(line: 9, column: 1, scope: !22)
 // 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: ; ModuleID = 'function.carbon'
 // CHECK:STDOUT: source_filename = "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 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: target triple = "x86_64-unknown-linux-gnu"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: mustprogress uwtable
 // 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: entry:
 // CHECK:STDOUT:   call void @_ZN6Carbon5OtherL17NoArgs__cpp_thunkEv()
 // CHECK:STDOUT:   call void @_ZN6Carbon5OtherL17NoArgs__cpp_thunkEv()
 // CHECK:STDOUT:   call void @_ZN6Carbon5OtherL18BoolArg__cpp_thunkEb(i1 noundef zeroext true)
 // 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 @_ZN6Carbon5OtherL17IntArg__cpp_thunkEi(i32 noundef 123)
 // CHECK:STDOUT:   call void @_ZN6Carbon5OtherL19FloatArg__cpp_thunkEf(float noundef 1.500000e+00)
 // 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:
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
 // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
@@ -166,6 +181,15 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // 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: declare void @_CNoArgs.Other()
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
 // CHECK:STDOUT: ; Function Attrs: nounwind
@@ -206,6 +230,16 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT:   ret void, !dbg !37
 // CHECK:STDOUT:   ret void, !dbg !37
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // 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 #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 #1 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
 // CHECK:STDOUT: attributes #2 = { nounwind }
 // CHECK:STDOUT: attributes #2 = { nounwind }
@@ -252,6 +286,10 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: !35 = !{!36}
 // CHECK:STDOUT: !35 = !{!36}
 // CHECK:STDOUT: !36 = !DILocalVariable(arg: 1, scope: !34, type: !23)
 // CHECK:STDOUT: !36 = !DILocalVariable(arg: 1, scope: !34, type: !23)
 // CHECK:STDOUT: !37 = !DILocation(line: 9, column: 1, scope: !34)
 // 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: ; ModuleID = 'single_file.carbon'
 // CHECK:STDOUT: source_filename = "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"
 // 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"