Просмотр исходного кода

Add support for C++ calling Carbon functions with parameters (#7024)

This works by generating two thunks, one in C++ and one in Carbon. For
example, given this input:
```c++
// Carbon:
fn Callme(f: f32) {}

// C++:
void F() {
  // This will call `Callme__cpp_thunk`
  Carbon::Callme(1.0);
}
```

These functions are generated:
```c++
// Carbon:
fn Callme__carbon_thunk(ref f: f32) {
  // Call the target function.
  Callme(f);
}

// C++:

// C++ declaration for the Carbon thunk.
void Callme__carbon_thunk(float& f);

void Callme__cpp_thunk(float f) {
  // Call the Carbon thunk with args passed by reference.
  Callme__carbon_thunk(f);
}
```

For now, all arguments are passed by reference, even if they are simple
types like pointers or i32.

Functions with non-void return types are not supported yet.
Nicholas Bishop 3 недель назад
Родитель
Сommit
0635f4628f

+ 2 - 0
toolchain/check/BUILD

@@ -26,6 +26,7 @@ cc_library(
         "cpp/constant.cpp",
         "cpp/context.cpp",
         "cpp/custom_type_mapping.cpp",
+        "cpp/export.cpp",
         "cpp/generate_ast.cpp",
         "cpp/impl_lookup.cpp",
         "cpp/import.cpp",
@@ -88,6 +89,7 @@ cc_library(
         "cpp/constant.h",
         "cpp/context.h",
         "cpp/custom_type_mapping.h",
+        "cpp/export.h",
         "cpp/generate_ast.h",
         "cpp/impl_lookup.h",
         "cpp/import.h",

+ 272 - 0
toolchain/check/cpp/export.cpp

@@ -0,0 +1,272 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/check/cpp/export.h"
+
+#include "toolchain/check/cpp/import.h"
+#include "toolchain/check/cpp/location.h"
+#include "toolchain/check/cpp/type_mapping.h"
+#include "toolchain/check/function.h"
+#include "toolchain/check/thunk.h"
+#include "toolchain/check/type.h"
+#include "toolchain/sem_ir/mangler.h"
+
+namespace Carbon::Check {
+
+// Create a `clang::FunctionDecl` for the given Carbon function. This
+// can be used to call the Carbon function from C++. The Carbon
+// function's ABI must be compatible with C++.
+//
+// The resulting decl is used to allow a generated C++ function to call
+// a generated Carbon function.
+static auto BuildCppFunctionDeclForCarbonFn(Context& context,
+                                            clang::DeclContext& decl_context,
+                                            SemIR::LocId loc_id,
+                                            SemIR::FunctionId function_id)
+    -> clang::FunctionDecl* {
+  auto clang_loc = GetCppLocation(context, loc_id);
+
+  const SemIR::Function& function = context.functions().Get(function_id);
+
+  // Get parameters types.
+  auto carbon_function_params =
+      context.inst_blocks().Get(function.call_param_patterns_id);
+  llvm::SmallVector<clang::QualType> cpp_param_types;
+  for (auto param_inst_id : carbon_function_params) {
+    auto scrutinee_type_id = ExtractScrutineeType(
+        context.sem_ir(), context.insts().Get(param_inst_id).type_id());
+    auto cpp_type = MapToCppType(context, scrutinee_type_id);
+    if (cpp_type.isNull()) {
+      context.TODO(loc_id, "failed to map C++ type to Carbon");
+      return nullptr;
+    }
+    auto ref_type = context.ast_context().getLValueReferenceType(cpp_type);
+    cpp_param_types.push_back(ref_type);
+  }
+
+  CARBON_CHECK(function.return_type_inst_id == SemIR::TypeInstId::None);
+  auto cpp_return_type = context.ast_context().VoidTy;
+
+  auto cpp_function_type = context.ast_context().getFunctionType(
+      cpp_return_type, cpp_param_types,
+      clang::FunctionProtoType::ExtProtoInfo());
+
+  auto* identifier_info = GetClangIdentifierInfo(context, function.name_id);
+  CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
+               function.name_id);
+
+  auto* function_decl = clang::FunctionDecl::Create(
+      context.ast_context(), &decl_context,
+      /*StartLoc=*/clang_loc,
+      /*NLoc=*/clang_loc, clang::DeclarationName(identifier_info),
+      cpp_function_type,
+      /*TInfo=*/nullptr, clang::SC_Extern);
+
+  // Build parameter decls.
+  llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
+  for (auto [i, type] : llvm::enumerate(cpp_param_types)) {
+    clang::ParmVarDecl* param = clang::ParmVarDecl::Create(
+        context.ast_context(), function_decl, /*StartLoc=*/clang_loc,
+        /*IdLoc=*/clang_loc, /*Id=*/nullptr, type, /*TInfo=*/nullptr,
+        clang::SC_None, /*DefArg=*/nullptr);
+    param_var_decls.push_back(param);
+  }
+  function_decl->setParams(param_var_decls);
+
+  // Mangle the function name and attach it to the `FunctionDecl`.
+  SemIR::Mangler m(context.sem_ir(), context.total_ir_count());
+  std::string mangled_name = m.Mangle(function_id, SemIR::SpecificId::None);
+  function_decl->addAttr(
+      clang::AsmLabelAttr::Create(context.ast_context(), mangled_name));
+
+  return function_decl;
+}
+
+// Create the declaration of the C++ thunk.
+static auto BuildCppToCarbonThunkDecl(
+    Context& context, SemIR::LocId loc_id, clang::DeclarationName thunk_name,
+    llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
+  clang::ASTContext& ast_context = context.ast_context();
+
+  auto clang_loc = GetCppLocation(context, loc_id);
+
+  auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
+  clang::QualType thunk_function_type = ast_context.getFunctionType(
+      ast_context.VoidTy, thunk_param_types, ext_proto_info);
+
+  clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl();
+  auto* tinfo =
+      ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc);
+  clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create(
+      ast_context, decl_context, clang_loc, clang_loc, thunk_name,
+      thunk_function_type, tinfo, clang::SC_Static);
+  decl_context->addDecl(thunk_function_decl);
+
+  llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
+  for (auto [i, type] : llvm::enumerate(thunk_param_types)) {
+    clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
+        ast_context, thunk_function_decl, /*StartLoc=*/clang_loc,
+        /*IdLoc=*/clang_loc, /*Id=*/nullptr, type,
+        /*TInfo=*/nullptr, clang::SC_None, /*DefArg=*/nullptr);
+    param_var_decls.push_back(thunk_param);
+  }
+  thunk_function_decl->setParams(param_var_decls);
+
+  // Set always_inline.
+  thunk_function_decl->addAttr(
+      clang::AlwaysInlineAttr::CreateImplicit(ast_context));
+
+  return thunk_function_decl;
+}
+
+// Create the body of a C++ thunk that calls a Carbon thunk. The
+// arguments are passed by reference to the callee.
+static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
+                                      clang::FunctionDecl* function_decl,
+                                      clang::FunctionDecl* callee_function_decl)
+    -> clang::StmtResult {
+  clang::SourceLocation clang_loc = function_decl->getLocation();
+
+  clang::ExprResult callee = sema.BuildDeclRefExpr(
+      callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue,
+      clang_loc);
+
+  llvm::SmallVector<clang::Expr*> call_args;
+  for (auto* param : function_decl->parameters()) {
+    clang::Expr* call_arg =
+        sema.BuildDeclRefExpr(param, param->getType().getNonReferenceType(),
+                              clang::VK_LValue, clang_loc);
+    call_args.push_back(call_arg);
+  }
+  clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
+                                              call_args, clang_loc);
+  CARBON_CHECK(call.isUsable());
+
+  return call.get();
+}
+
+// Create a C++ thunk that calls the Carbon thunk. The C++ thunk's
+// parameter types are mapped from the parameters of the target function
+// with `MapToCppType`. (Note that the target function here is the
+// callee of the Carbon thunk.)
+static auto BuildCppToCarbonThunk(
+    Context& context, SemIR::LocId loc_id, llvm::StringRef base_name,
+    clang::FunctionDecl* carbon_function_decl,
+    llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids)
+    -> clang::FunctionDecl* {
+  // Create the thunk's name.
+  llvm::SmallString<64> thunk_name = base_name;
+  thunk_name += "__cpp_thunk";
+  auto& thunk_ident = context.ast_context().Idents.get(thunk_name);
+
+  llvm::SmallVector<clang::QualType> param_types;
+  for (auto type_id : callee_param_type_ids) {
+    auto cpp_type = MapToCppType(context, type_id);
+    if (cpp_type.isNull()) {
+      context.TODO(loc_id, "failed to map C++ type to Carbon");
+      return nullptr;
+    }
+    param_types.push_back(cpp_type);
+  }
+
+  auto* thunk_function_decl =
+      BuildCppToCarbonThunkDecl(context, loc_id, &thunk_ident, param_types);
+
+  // Build the thunk function body.
+  clang::Sema& sema = context.clang_sema();
+  clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
+  sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
+  clang::StmtResult body = BuildCppToCarbonThunkBody(sema, thunk_function_decl,
+                                                     carbon_function_decl);
+  sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
+  CARBON_CHECK(!body.isInvalid());
+
+  context.clang_sema().getASTConsumer().HandleTopLevelDecl(
+      clang::DeclGroupRef(thunk_function_decl));
+  return thunk_function_decl;
+}
+
+// Create a Carbon thunk that calls `callee`. The thunk's parameters are
+// all references to the callee parameter type.
+static auto BuildCarbonToCarbonThunk(
+    Context& context, SemIR::LocId loc_id, const SemIR::Function& callee,
+    llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids) -> SemIR::FunctionId {
+  // Create the thunk's name.
+  llvm::SmallString<64> thunk_name =
+      context.names().GetFormatted(callee.name_id);
+  thunk_name += "__carbon_thunk";
+  auto& ident = context.ast_context().Idents.get(thunk_name);
+  auto thunk_name_id =
+      SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName()));
+
+  auto carbon_thunk_function_id =
+      MakeGeneratedFunctionDecl(context, loc_id,
+                                {.parent_scope_id = callee.parent_scope_id,
+                                 .name_id = thunk_name_id,
+                                 .param_type_ids = callee_param_type_ids,
+                                 .params_are_refs = true})
+          .second;
+  BuildThunkDefinition(context, carbon_thunk_function_id,
+                       carbon_thunk_function_id, callee.first_decl_id(),
+                       callee.first_decl_id());
+
+  return carbon_thunk_function_id;
+}
+
+auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
+                                   clang::DeclContext& decl_context,
+                                   SemIR::FunctionId callee_function_id)
+    -> clang::FunctionDecl* {
+  const SemIR::Function& callee = context.functions().Get(callee_function_id);
+
+  if (callee.return_type_inst_id != SemIR::TypeInstId::None) {
+    context.TODO(loc_id,
+                 "unsupported: C++ calling a Carbon function with "
+                 "return type other than `()`");
+    return nullptr;
+  }
+
+  if (callee.generic_id.has_value()) {
+    context.TODO(loc_id,
+                 "unsupported: C++ calling a Carbon function with "
+                 "generic parameters");
+    return nullptr;
+  }
+
+  if (callee.call_param_ranges.implicit_size() != 0) {
+    context.TODO(loc_id,
+                 "unsupported: C++ calling a Carbon function with "
+                 "an implicit parameter");
+    return nullptr;
+  }
+
+  // Get the parameter types of the Carbon function being called.
+  auto callee_function_params =
+      context.inst_blocks().Get(callee.call_param_patterns_id);
+  llvm::SmallVector<SemIR::TypeId> callee_param_type_ids;
+  for (auto callee_param_inst_id : callee_function_params) {
+    auto scrutinee_type_id = ExtractScrutineeType(
+        context.sem_ir(), context.insts().Get(callee_param_inst_id).type_id());
+    callee_param_type_ids.push_back(scrutinee_type_id);
+  }
+
+  // Create a Carbon thunk that calls the callee. The thunk's parameters
+  // are all references so that the ABI is compatible with C++ callers.
+  auto carbon_thunk_function_id =
+      BuildCarbonToCarbonThunk(context, loc_id, callee, callee_param_type_ids);
+
+  // Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
+  auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
+      context, decl_context, loc_id, carbon_thunk_function_id);
+  if (!carbon_function_decl) {
+    return nullptr;
+  }
+
+  // Create a C++ thunk that calls the Carbon thunk.
+  return BuildCppToCarbonThunk(context, loc_id,
+                               context.names().GetFormatted(callee.name_id),
+                               carbon_function_decl, callee_param_type_ids);
+}
+
+}  // namespace Carbon::Check

+ 22 - 0
toolchain/check/cpp/export.h

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_
+#define CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_
+
+#include "clang/AST/Decl.h"
+#include "toolchain/check/context.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::Check {
+
+// Get a `clang::FunctionDecl` that can be used to call a Carbon function.
+auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
+                                   clang::DeclContext& decl_context,
+                                   SemIR::FunctionId function_id)
+    -> clang::FunctionDecl*;
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_

+ 5 - 44
toolchain/check/cpp/generate_ast.cpp

@@ -28,6 +28,7 @@
 #include "llvm/Support/raw_ostream.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/cpp/export.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/name_lookup.h"
@@ -36,7 +37,6 @@
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/cpp_file.h"
-#include "toolchain/sem_ir/mangler.h"
 
 namespace Carbon::Check {
 
@@ -432,56 +432,17 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
       if (!callee_function) {
         return nullptr;
       }
+
       const SemIR::Function& function =
           context_->functions().Get(callee_function->function_id);
       if (function.clang_decl_id.has_value()) {
         return cast<clang::NamedDecl>(
             context_->clang_decls().Get(function.clang_decl_id).key.decl);
       }
-      auto* identifier_info =
-          GetClangIdentifierInfo(*context_, function.name_id);
-      CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
-                   function.name_id);
-
-      if (function.call_param_ranges.explicit_size() != 0) {
-        context_->TODO(target_inst_id,
-                       "unsupported: C++ calling a Carbon function with "
-                       "parameters");
-        return nullptr;
-      }
-
-      if (function.return_type_inst_id != SemIR::TypeInstId::None) {
-        context_->TODO(target_inst_id,
-                       "unsupported: C++ calling a Carbon function with "
-                       "return type other than `()`");
-        return nullptr;
-      }
-
-      // TODO: support non-empty parameter lists.
-      llvm::SmallVector<clang::QualType> cpp_param_types;
-
-      // TODO: support non-void return types.
-      auto cpp_return_type = ast_context_->VoidTy;
-
-      auto cpp_function_type = ast_context_->getFunctionType(
-          cpp_return_type, cpp_param_types,
-          clang::FunctionProtoType::ExtProtoInfo());
-
-      auto* function_decl = clang::FunctionDecl::Create(
-          *ast_context_, &decl_context,
-          /*StartLoc=*/clang::SourceLocation(),
-          /*NLoc=*/clang::SourceLocation(),
-          clang::DeclarationName(identifier_info), cpp_function_type,
-          /*TInfo=*/nullptr, clang::SC_Extern);
-
-      // Mangle the function name and attach it to the `FunctionDecl`.
-      SemIR::Mangler m(context_->sem_ir(), context_->total_ir_count());
-      std::string mangled_name =
-          m.Mangle(callee_function->function_id, SemIR::SpecificId::None);
-      function_decl->addAttr(
-          clang::AsmLabelAttr::Create(*ast_context_, mangled_name));
 
-      return function_decl;
+      return GetReverseInteropFunctionDecl(
+          *context_, SemIR::LocId(target_inst_id), decl_context,
+          callee_function->function_id);
     }
     default:
       return nullptr;

+ 3 - 3
toolchain/check/function.cpp

@@ -139,8 +139,8 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id,
     context.full_pattern_stack().EndImplicitParamList();
   }
 
-  // Build and add any explicit parameters. We always use value parameters for
-  // now.
+  // Build and add any explicit parameters. Whether these are references
+  // or not is controlled by `args.params_are_refs`.
   context.full_pattern_stack().StartExplicitParamList();
   if (args.param_type_ids.empty()) {
     insts.param_patterns_id = SemIR::InstBlockId::Empty;
@@ -154,7 +154,7 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id,
 
       context.inst_block_stack().AddInstId(AddParamPattern(
           context, loc_id, SemIR::NameId::Underscore, param_type_region_id,
-          param_type_id, /*is_ref=*/false));
+          param_type_id, /*is_ref=*/args.params_are_refs));
     }
     insts.param_patterns_id = context.inst_block_stack().Pop();
   }

+ 2 - 0
toolchain/check/function.h

@@ -41,6 +41,8 @@ struct FunctionDeclArgs {
   bool self_is_ref = true;
   // The types of the explicit parameters.
   llvm::ArrayRef<SemIR::TypeId> param_type_ids = {};
+  // Whether the parameters described by `param_type_ids` are references.
+  bool params_are_refs = false;
   // The return type, or `None` if the function doesn't declare a return type.
   SemIR::TypeId return_type_id = SemIR::TypeId::None;
 };

+ 70 - 9
toolchain/check/testdata/interop/cpp/reverse/function.carbon

@@ -17,6 +17,13 @@ fn F1() {}
 fn F2() -> i32 { return 0; }
 fn F3(_: i32) {}
 
+fn HasGenericArg(T:! type, a: T) { a; }
+fn HasDeducedArg[T:! type](a: T) { a; }
+
+class C {
+  fn Method[self: Self]() { self; }
+}
+
 // --- function.carbon
 
 library "[[@TEST_NAME]]";
@@ -48,22 +55,76 @@ void G() {
 }
 ''';
 
-// --- fail_todo_args.carbon
+// --- args.carbon
+
+library "[[@TEST_NAME]]";
+
+import Other;
+import Cpp inline '''
+void G() {
+  Carbon::Other::F3(123);
+}
+''';
+
+// --- fail_todo_generic.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_args.carbon:[[@LINE+5]]:1: in import [InImport]
-// CHECK:STDERR: other.carbon:5:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with parameters` [SemanticsTodo]
-// CHECK:STDERR: fn F3(_: i32) {}
-// CHECK:STDERR: ^~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+5]]:1: in import [InImport]
+// CHECK:STDERR: other.carbon:7:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with generic parameters` [SemanticsTodo]
+// CHECK:STDERR: fn HasGenericArg(T:! type, a: T) { a; }
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
 import Other;
 import Cpp inline '''
 void G() {
-  // CHECK:STDERR: fail_todo_args.carbon:[[@LINE+4]]:18: error: no member named 'F3' in namespace 'Carbon::Other' [CppInteropParseError]
-  // CHECK:STDERR:    16 |   Carbon::Other::F3();
-  // CHECK:STDERR:       |                  ^~
+  // CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+8]]:18: error: no member named 'HasGenericArg' in namespace 'Carbon::Other' [CppInteropParseError]
+  // CHECK:STDERR:    20 |   Carbon::Other::HasGenericArg<int>(123);
+  // CHECK:STDERR:       |                  ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_generic.carbon:[[@LINE+4]]:35: error: expected '(' for function-style cast or type construction [CppInteropParseError]
+  // CHECK:STDERR:    20 |   Carbon::Other::HasGenericArg<int>(123);
+  // CHECK:STDERR:       |                                ~~~^
+  // CHECK:STDERR:
+  Carbon::Other::HasGenericArg<int>(123);
+}
+''';
+
+// --- fail_todo_deduced.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_todo_deduced.carbon:[[@LINE+5]]:1: in import [InImport]
+// CHECK:STDERR: other.carbon:8:1: error: semantics TODO: `unsupported: C++ calling a Carbon function with generic parameters` [SemanticsTodo]
+// CHECK:STDERR: fn HasDeducedArg[T:! type](a: T) { a; }
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+import Other;
+import Cpp inline '''
+void G() {
+  // CHECK:STDERR: fail_todo_deduced.carbon:[[@LINE+4]]:18: error: no member named 'HasDeducedArg' in namespace 'Carbon::Other' [CppInteropParseError]
+  // CHECK:STDERR:    16 |   Carbon::Other::HasDeducedArg(123);
+  // CHECK:STDERR:       |                  ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Carbon::Other::HasDeducedArg(123);
+}
+''';
+
+// --- fail_todo_method.carbon
+
+library "[[@TEST_NAME]]";
+
+import Other;
+import Cpp inline '''
+void G() {
+  // CHECK:STDERR: fail_todo_method.carbon:[[@LINE+8]]:3: error: invalid use of incomplete type 'Carbon::Other::C' [CppInteropParseError]
+  // CHECK:STDERR:    15 |   Carbon::Other::C().Method();
+  // CHECK:STDERR:       |   ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_method.carbon:[[@LINE+4]]:21: error: member access into incomplete type 'Carbon::Other::C' [CppInteropParseError]
+  // CHECK:STDERR:    15 |   Carbon::Other::C().Method();
+  // CHECK:STDERR:       |                     ^
   // CHECK:STDERR:
-  Carbon::Other::F3();
+  Carbon::Other::C().Method();
 }
 ''';

+ 3 - 7
toolchain/check/thunk.cpp

@@ -301,13 +301,9 @@ static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id,
   return PerformThunkCall(context, loc_id, function_id, call_params, callee_id);
 }
 
-// Given a declaration of a thunk and the function that it should call, build
-// the thunk body.
-static auto BuildThunkDefinition(Context& context,
-                                 SemIR::FunctionId signature_id,
-                                 SemIR::FunctionId function_id,
-                                 SemIR::InstId thunk_id,
-                                 SemIR::InstId callee_id) {
+auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id,
+                          SemIR::FunctionId function_id, SemIR::InstId thunk_id,
+                          SemIR::InstId callee_id) -> void {
   // TODO: Improve the diagnostics produced here. Specifically, it would likely
   // be better for the primary error message to be that we tried to produce a
   // thunk because of a type mismatch, but couldn't, with notes explaining

+ 6 - 0
toolchain/check/thunk.h

@@ -11,6 +11,12 @@
 
 namespace Carbon::Check {
 
+// Given a declaration of a thunk and the function that it should call, build
+// the thunk body.
+auto BuildThunkDefinition(Context& context, SemIR::FunctionId signature_id,
+                          SemIR::FunctionId function_id, SemIR::InstId thunk_id,
+                          SemIR::InstId callee_id) -> void;
+
 // Given a function signature and a callee function, build a thunk that matches
 // the given signature and calls the specified callee. Returns the callee
 // unchanged if it can be used directly.

+ 186 - 18
toolchain/lower/testdata/interop/cpp/reverse/function.carbon

@@ -2,7 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
-// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
@@ -12,7 +12,16 @@
 
 // --- other.carbon
 package Other;
-fn F() {}
+fn NoArgs() {}
+fn BoolArg(a: bool) {
+  a;
+}
+fn IntArg(a: i32) {
+  a;
+}
+fn FloatArg(a: f32) {
+  a;
+}
 // --- function.carbon
 
 library "[[@TEST_NAME]]";
@@ -20,7 +29,10 @@ library "[[@TEST_NAME]]";
 import Other;
 import Cpp inline '''
 void G() {
-  Carbon::Other::F();
+  Carbon::Other::NoArgs();
+  Carbon::Other::BoolArg(true);
+  Carbon::Other::IntArg(123);
+  Carbon::Other::FloatArg(1.5);
 }
 ''';
 
@@ -50,11 +62,29 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: source_filename = "other.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CF.Other() #0 !dbg !4 {
+// CHECK:STDOUT: define void @_CNoArgs.Other() #0 !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void, !dbg !7
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CBoolArg.Other(i1 %a) #0 !dbg !8 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CIntArg.Other(i32 %a) #0 !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !21
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CFloatArg.Other(float %a) #0 !dbg !22 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !25
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: attributes #0 = { nounwind }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
@@ -64,10 +94,28 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !3 = !DIFile(filename: "other.carbon", directory: "")
-// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Other", scope: null, file: !3, line: 2, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "NoArgs", linkageName: "_CNoArgs.Other", scope: null, file: !3, line: 2, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{null}
 // CHECK:STDOUT: !7 = !DILocation(line: 2, column: 1, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "BoolArg", linkageName: "_CBoolArg.Other", scope: null, file: !3, line: 3, type: !9, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !12)
+// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
+// CHECK:STDOUT: !10 = !{null, !11}
+// CHECK:STDOUT: !11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !12 = !{!13}
+// CHECK:STDOUT: !13 = !DILocalVariable(arg: 1, scope: !8, type: !11)
+// CHECK:STDOUT: !14 = !DILocation(line: 3, column: 1, scope: !8)
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "IntArg", linkageName: "_CIntArg.Other", scope: null, file: !3, line: 6, type: !16, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !19)
+// CHECK:STDOUT: !16 = !DISubroutineType(types: !17)
+// CHECK:STDOUT: !17 = !{null, !18}
+// CHECK:STDOUT: !18 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !19 = !{!20}
+// CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !15, type: !18)
+// CHECK:STDOUT: !21 = !DILocation(line: 6, column: 1, scope: !15)
+// CHECK:STDOUT: !22 = distinct !DISubprogram(name: "FloatArg", linkageName: "_CFloatArg.Other", scope: null, file: !3, line: 9, type: !9, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !23)
+// CHECK:STDOUT: !23 = !{!24}
+// CHECK:STDOUT: !24 = !DILocalVariable(arg: 1, scope: !22, type: !11)
+// CHECK:STDOUT: !25 = !DILocation(line: 9, column: 1, scope: !22)
 // CHECK:STDOUT: ; ModuleID = 'function.carbon'
 // CHECK:STDOUT: source_filename = "function.carbon"
 // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
@@ -76,14 +124,91 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: ; Function Attrs: mustprogress uwtable
 // CHECK:STDOUT: define dso_local void @_Z1Gv() #0 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_CF.Other()
+// CHECK:STDOUT:   call void @_ZL17NoArgs__cpp_thunkv()
+// CHECK:STDOUT:   call void @_ZL18BoolArg__cpp_thunkb(i1 noundef zeroext true)
+// CHECK:STDOUT:   call void @_ZL17IntArg__cpp_thunki(i32 noundef 123)
+// CHECK:STDOUT:   call void @_ZL19FloatArg__cpp_thunkf(float noundef 1.500000e+00)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZL17NoArgs__cpp_thunkv() #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CNoArgs__carbon_thunk.Other()
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CF.Other() #1
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZL18BoolArg__cpp_thunkb(i1 noundef zeroext %0) #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.addr = alloca i8, align 1
+// CHECK:STDOUT:   %storedv = zext i1 %0 to i8
+// CHECK:STDOUT:   store i8 %storedv, ptr %.addr, align 1, !tbaa !11
+// CHECK:STDOUT:   call void @_CBoolArg__carbon_thunk.Other(ptr noundef nonnull align 1 dereferenceable(1) %.addr)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZL17IntArg__cpp_thunki(i32 noundef %0) #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.addr = alloca i32, align 4
+// CHECK:STDOUT:   store i32 %0, ptr %.addr, align 4, !tbaa !7
+// CHECK:STDOUT:   call void @_CIntArg__carbon_thunk.Other(ptr noundef nonnull align 4 dereferenceable(4) %.addr)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZL19FloatArg__cpp_thunkf(float noundef %0) #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.addr = alloca float, align 4
+// CHECK:STDOUT:   store float %0, ptr %.addr, align 4, !tbaa !13
+// CHECK:STDOUT:   call void @_CFloatArg__carbon_thunk.Other(ptr noundef nonnull align 4 dereferenceable(4) %.addr)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CNoArgs.Other()
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CNoArgs__carbon_thunk.Other() #2 !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CNoArgs.Other(), !dbg !19
+// CHECK:STDOUT:   ret void, !dbg !19
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CBoolArg.Other(i1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CBoolArg__carbon_thunk.Other(ptr %_) #2 !dbg !20 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.1 = load i8, ptr %_, align 1, !dbg !26
+// CHECK:STDOUT:   %.11 = trunc i8 %.1 to i1, !dbg !26
+// CHECK:STDOUT:   call void @_CBoolArg.Other(i1 %.11), !dbg !26
+// CHECK:STDOUT:   ret void, !dbg !26
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CIntArg.Other(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CIntArg__carbon_thunk.Other(ptr %_) #2 !dbg !27 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.1 = load i32, ptr %_, align 4, !dbg !33
+// CHECK:STDOUT:   call void @_CIntArg.Other(i32 %.1), !dbg !33
+// CHECK:STDOUT:   ret void, !dbg !33
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CFloatArg.Other(float)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CFloatArg__carbon_thunk.Other(ptr %_) #2 !dbg !34 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.1 = load float, ptr %_, align 4, !dbg !37
+// CHECK:STDOUT:   call void @_CFloatArg.Other(float %.1), !dbg !37
+// CHECK:STDOUT:   ret void, !dbg !37
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: attributes #0 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
-// CHECK:STDOUT: attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT: attributes #2 = { nounwind }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!5}
@@ -100,6 +225,33 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: !8 = !{!"int", !9, i64 0}
 // CHECK:STDOUT: !9 = !{!"omnipotent char", !10, i64 0}
 // CHECK:STDOUT: !10 = !{!"Simple C++ TBAA"}
+// CHECK:STDOUT: !11 = !{!12, !12, i64 0}
+// CHECK:STDOUT: !12 = !{!"bool", !9, i64 0}
+// CHECK:STDOUT: !13 = !{!14, !14, i64 0}
+// CHECK:STDOUT: !14 = !{!"float", !9, i64 0}
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "NoArgs__carbon_thunk", linkageName: "_CNoArgs__carbon_thunk.Other", scope: null, file: !16, line: 2, type: !17, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !16 = !DIFile(filename: "other.carbon", directory: "")
+// CHECK:STDOUT: !17 = !DISubroutineType(types: !18)
+// CHECK:STDOUT: !18 = !{null}
+// CHECK:STDOUT: !19 = !DILocation(line: 2, column: 1, scope: !15)
+// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "BoolArg__carbon_thunk", linkageName: "_CBoolArg__carbon_thunk.Other", scope: null, file: !16, line: 3, type: !21, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !24)
+// CHECK:STDOUT: !21 = !DISubroutineType(types: !22)
+// CHECK:STDOUT: !22 = !{null, !23}
+// CHECK:STDOUT: !23 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !24 = !{!25}
+// CHECK:STDOUT: !25 = !DILocalVariable(arg: 1, scope: !20, type: !23)
+// CHECK:STDOUT: !26 = !DILocation(line: 3, column: 1, scope: !20)
+// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "IntArg__carbon_thunk", linkageName: "_CIntArg__carbon_thunk.Other", scope: null, file: !16, line: 6, type: !28, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !31)
+// CHECK:STDOUT: !28 = !DISubroutineType(types: !29)
+// CHECK:STDOUT: !29 = !{null, !30}
+// CHECK:STDOUT: !30 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !31 = !{!32}
+// CHECK:STDOUT: !32 = !DILocalVariable(arg: 1, scope: !27, type: !30)
+// CHECK:STDOUT: !33 = !DILocation(line: 6, column: 1, scope: !27)
+// CHECK:STDOUT: !34 = distinct !DISubprogram(name: "FloatArg__carbon_thunk", linkageName: "_CFloatArg__carbon_thunk.Other", scope: null, file: !16, line: 9, type: !21, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !35)
+// CHECK:STDOUT: !35 = !{!36}
+// CHECK:STDOUT: !36 = !DILocalVariable(arg: 1, scope: !34, type: !23)
+// CHECK:STDOUT: !37 = !DILocation(line: 9, column: 1, scope: !34)
 // CHECK:STDOUT: ; ModuleID = 'single_file.carbon'
 // CHECK:STDOUT: source_filename = "single_file.carbon"
 // CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
@@ -116,10 +268,17 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nounwind
-// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !15 {
+// CHECK:STDOUT: define void @_CF__carbon_thunk.Main() #0 !dbg !15 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_Z2G2v(), !dbg !16
-// CHECK:STDOUT:   ret void, !dbg !17
+// CHECK:STDOUT:   call void @_CF.Main(), !dbg !16
+// CHECK:STDOUT:   ret void, !dbg !16
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !17 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z2G2v(), !dbg !18
+// CHECK:STDOUT:   ret void, !dbg !19
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress uwtable
@@ -129,16 +288,23 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress nounwind uwtable
-// CHECK:STDOUT: define linkonce_odr dso_local void @_Z2G1v() #2 comdat {
+// CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress uwtable
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z2G1v() #1 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_ZL12F__cpp_thunkv()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZL12F__cpp_thunkv() #2 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_CF.Main()
+// CHECK:STDOUT:   call void @_CF__carbon_thunk.Main()
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: attributes #0 = { nounwind }
 // CHECK:STDOUT: attributes #1 = { inlinehint mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
-// CHECK:STDOUT: attributes #2 = { inlinehint mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT: attributes #2 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!5}
@@ -159,6 +325,8 @@ fn H() { Cpp.G2(); }
 // CHECK:STDOUT: !12 = !DISubroutineType(types: !13)
 // CHECK:STDOUT: !13 = !{null}
 // CHECK:STDOUT: !14 = !DILocation(line: 6, column: 1, scope: !11)
-// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !6, line: 20, type: !12, spFlags: DISPFlagDefinition, unit: !5)
-// CHECK:STDOUT: !16 = !DILocation(line: 20, column: 10, scope: !15)
-// CHECK:STDOUT: !17 = !DILocation(line: 20, column: 1, scope: !15)
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "F__carbon_thunk", linkageName: "_CF__carbon_thunk.Main", scope: null, file: !6, line: 6, type: !12, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !16 = !DILocation(line: 6, column: 1, scope: !15)
+// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !6, line: 20, type: !12, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !18 = !DILocation(line: 20, column: 10, scope: !17)
+// CHECK:STDOUT: !19 = !DILocation(line: 20, column: 1, scope: !17)