Przeglądaj źródła

Support calling Carbon methods from C++ (#7078)

The FunctionDecl created for calling the Carbon thunk now takes a `self`
parameter for non-static methods, and the C++ thunk now passes an extra
argument for that `self` parameter when needed.

The CXXMethodDecl thunk created for calling methods now sets the storage
class appropriate depending on whether the method is static or not.

To reduce the number of parameters being passed around to thunk-building
functions, added a `FunctionInfo` struct and pass that around instead.
Nicholas Bishop 1 tydzień temu
rodzic
commit
33d534ab31

+ 139 - 65
toolchain/check/cpp/export.cpp

@@ -4,6 +4,7 @@
 
 #include "toolchain/check/cpp/export.h"
 
+#include "llvm/Support/Casting.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/cpp/location.h"
 #include "toolchain/check/cpp/type_mapping.h"
@@ -165,6 +166,70 @@ auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
   return record_decl;
 }
 
+namespace {
+struct FunctionInfo {
+  explicit FunctionInfo(Context& context, SemIR::FunctionId function_id,
+                        const SemIR::Function& function,
+                        clang::DeclContext* decl_context)
+      : function_id(function_id),
+        function(function),
+        decl_context(decl_context) {
+    auto function_params =
+        context.inst_blocks().Get(function.call_param_patterns_id);
+
+    // Get the function's `self` parameter type, if present.
+    if (function.call_param_ranges.implicit_size() > 0) {
+      CARBON_CHECK(function.call_param_ranges.implicit_size() == 1);
+
+      auto param_inst_id =
+          function_params[function.call_param_ranges.implicit_begin().index];
+      auto scrutinee_type_id = ExtractScrutineeType(
+          context.sem_ir(), context.insts().Get(param_inst_id).type_id());
+      self_type_id = scrutinee_type_id;
+    }
+
+    // Get the function's explicit parameter types.
+    function_params =
+        function_params.drop_front(function.call_param_ranges.implicit_size());
+    function_params =
+        function_params.drop_back(function.call_param_ranges.return_size());
+    for (auto param_inst_id : function_params) {
+      auto scrutinee_type_id = ExtractScrutineeType(
+          context.sem_ir(), context.insts().Get(param_inst_id).type_id());
+      param_type_ids.push_back(scrutinee_type_id);
+    }
+  }
+
+  // Get the `StorageClass` to use for `CXXMethodDecl`s.
+  auto GetStorageClass() const -> clang::StorageClass {
+    if (has_self()) {
+      return clang::SC_None;
+    } else {
+      return clang::SC_Static;
+    }
+  }
+
+  // Whether the function has a `self` parameter.
+  auto has_self() const -> bool { return self_type_id != SemIR::TypeId::None; }
+
+  SemIR::FunctionId function_id;
+  const SemIR::Function& function;
+
+  // Parent scope in the C++ AST where a C++ thunk for this function can
+  // be created. If the function is a method, this will be a
+  // `CXXRecordDecl`.
+  clang::DeclContext* decl_context;
+
+  // Types of the function's explicit parameters (excludes implicit
+  // parameters and return parameters).
+  llvm::SmallVector<SemIR::TypeId> param_type_ids;
+
+  // Type of the function's `self` parameter, or `None` if the function
+  // is not a method.
+  SemIR::TypeId self_type_id = SemIR::TypeId::None;
+};
+}  // namespace
+
 // 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++.
@@ -178,15 +243,21 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
   auto clang_loc = GetCppLocation(context, loc_id);
 
   const SemIR::Function& function = context.functions().Get(function_id);
+  FunctionInfo callee(context, function_id, function, nullptr);
 
   // 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 (callee.has_self()) {
+    auto cpp_type = MapToCppType(context, callee.self_type_id);
+    if (cpp_type.isNull()) {
+      context.TODO(loc_id, "failed to map Carbon self type to C++");
+      return nullptr;
+    }
+    cpp_type = context.ast_context().getLValueReferenceType(cpp_type);
+    cpp_param_types.push_back(cpp_type);
+  }
+  for (auto param_type_id : callee.param_type_ids) {
+    auto cpp_type = MapToCppType(context, param_type_id);
     if (cpp_type.isNull()) {
       context.TODO(loc_id, "failed to map Carbon type to C++");
       return nullptr;
@@ -206,7 +277,7 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
   CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
                function.name_id);
 
-  auto* function_decl = clang::FunctionDecl::Create(
+  clang::FunctionDecl* function_decl = clang::FunctionDecl::Create(
       context.ast_context(), context.ast_context().getTranslationUnitDecl(),
       /*StartLoc=*/clang_loc, /*NLoc=*/clang_loc, identifier_info,
       cpp_function_type, /*TInfo=*/nullptr, clang::SC_Extern);
@@ -233,10 +304,9 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
 
 // Create the declaration of the C++ thunk.
 static auto BuildCppToCarbonThunkDecl(
-    Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
+    Context& context, SemIR::LocId loc_id, const FunctionInfo& target,
     clang::DeclarationName thunk_name,
-    llvm::ArrayRef<clang::QualType> thunk_param_types,
-    SemIR::TypeId return_type_id) -> clang::FunctionDecl* {
+    llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
   clang::ASTContext& ast_context = context.ast_context();
 
   auto clang_loc = GetCppLocation(context, loc_id);
@@ -244,6 +314,7 @@ static auto BuildCppToCarbonThunkDecl(
   // 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;
+  auto return_type_id = target.function.GetDeclaredReturnType(context.sem_ir());
   if (return_type_id != SemIR::TypeId::None) {
     cpp_return_type = MapToCppType(context, return_type_id);
     if (cpp_return_type.isNull()) {
@@ -267,21 +338,22 @@ static auto BuildCppToCarbonThunkDecl(
   auto trailing_requires_clause = clang::AssociatedConstraint();
 
   clang::FunctionDecl* thunk_function_decl = nullptr;
-  if (auto* parent_class = dyn_cast<clang::CXXRecordDecl>(decl_context)) {
-    // TODO: Support non-static methods.
+  if (auto* parent_class =
+          dyn_cast<clang::CXXRecordDecl>(target.decl_context)) {
     thunk_function_decl = clang::CXXMethodDecl::Create(
         ast_context, parent_class, clang_loc, name_info, thunk_function_type,
-        tinfo, clang::SC_Static, uses_fp_intrin, inline_specified,
+        tinfo, target.GetStorageClass(), uses_fp_intrin, inline_specified,
         constexpr_kind, clang_loc, trailing_requires_clause);
     // TODO: Map Carbon access to C++ access.
     thunk_function_decl->setAccess(clang::AS_public);
   } else {
     thunk_function_decl = clang::FunctionDecl::Create(
-        ast_context, decl_context, clang_loc, name_info, thunk_function_type,
-        tinfo, clang::SC_None, uses_fp_intrin, inline_specified,
+        ast_context, target.decl_context, clang_loc, name_info,
+        thunk_function_type, tinfo, clang::SC_None, uses_fp_intrin,
+        inline_specified,
         /*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause);
   }
-  decl_context->addDecl(thunk_function_decl);
+  target.decl_context->addHiddenDecl(thunk_function_decl);
 
   llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
   for (auto [i, type] : llvm::enumerate(thunk_param_types)) {
@@ -305,6 +377,7 @@ static auto BuildCppToCarbonThunkDecl(
 // 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,
+                                      const FunctionInfo& target,
                                       clang::FunctionDecl* function_decl,
                                       clang::FunctionDecl* callee_function_decl)
     -> clang::StmtResult {
@@ -342,6 +415,20 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
       clang_loc);
 
   llvm::SmallVector<clang::Expr*> call_args;
+  // For methods, pass the `this` pointer as the first argument to the callee.
+  if (target.has_self()) {
+    auto* parent_class = cast<clang::CXXRecordDecl>(target.decl_context);
+    clang::QualType class_type =
+        sema.getASTContext().getCanonicalTagType(parent_class);
+    auto class_ptr_type = sema.getASTContext().getPointerType(class_type);
+    auto* this_expr = sema.BuildCXXThisExpr(clang_loc, class_ptr_type,
+                                            /*IsImplicit=*/true);
+    this_expr = clang::UnaryOperator::Create(
+        sema.getASTContext(), this_expr, clang::UO_Deref, class_type,
+        clang::ExprValueKind::VK_LValue, clang::ExprObjectKind::OK_Ordinary,
+        clang_loc, /*CanOverflow=*/false, clang::FPOptionsOverride());
+    call_args.push_back(this_expr);
+  }
   for (auto* param : function_decl->parameters()) {
     clang::Expr* call_arg =
         sema.BuildDeclRefExpr(param, param->getType().getNonReferenceType(),
@@ -376,18 +463,22 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
 // 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, clang::DeclContext* decl_context,
-    llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl,
-    llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids,
-    SemIR::TypeId return_type_id) -> clang::FunctionDecl* {
+static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id,
+                                  const FunctionInfo& target,
+                                  llvm::StringRef base_name,
+                                  clang::FunctionDecl* carbon_function_decl)
+    -> clang::FunctionDecl* {
   // Create the thunk's name.
   llvm::SmallString<64> thunk_name = base_name;
-  thunk_name += "__cpp_thunk";
+  // TODO: changing the thunk name for methods hits this clang assertion:
+  // https://github.com/llvm/llvm-project/blob/058398c4ceaf/clang/lib/AST/Expr.cpp#L1720
+  if (!target.has_self()) {
+    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) {
+  for (auto type_id : target.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");
@@ -397,14 +488,14 @@ static auto BuildCppToCarbonThunk(
   }
 
   auto* thunk_function_decl = BuildCppToCarbonThunkDecl(
-      context, loc_id, decl_context, &thunk_ident, param_types, return_type_id);
+      context, loc_id, target, &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);
+  clang::StmtResult body = BuildCppToCarbonThunkBody(
+      sema, target, thunk_function_decl, carbon_function_decl);
   sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
   CARBON_CHECK(!body.isInvalid());
 
@@ -415,13 +506,12 @@ 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, SemIR::FunctionId callee_function_id,
-    const SemIR::Function& callee,
-    llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids) -> SemIR::FunctionId {
+static auto BuildCarbonToCarbonThunk(Context& context, SemIR::LocId loc_id,
+                                     const FunctionInfo& target)
+    -> SemIR::FunctionId {
   // Create the thunk's name.
   llvm::SmallString<64> thunk_name =
-      context.names().GetFormatted(callee.name_id);
+      context.names().GetFormatted(target.function.name_id);
   thunk_name += "__carbon_thunk";
   auto& ident = context.ast_context().Idents.get(thunk_name);
   auto thunk_name_id =
@@ -430,24 +520,27 @@ static auto BuildCarbonToCarbonThunk(
   // 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());
+  llvm::SmallVector<SemIR::TypeId> thunk_param_type_ids(target.param_type_ids);
+  auto callee_return_type_id =
+      target.function.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 = thunk_param_type_ids,
-                                 .params_are_refs = true})
+      MakeGeneratedFunctionDecl(
+          context, loc_id,
+          {.parent_scope_id = target.function.parent_scope_id,
+           .name_id = thunk_name_id,
+           .self_type_id = target.self_type_id,
+           .param_type_ids = thunk_param_type_ids,
+           .params_are_refs = true})
           .second;
 
   BuildThunkDefinitionForExport(
-      context, carbon_thunk_function_id, callee_function_id,
+      context, carbon_thunk_function_id, target.function_id,
       context.functions().Get(carbon_thunk_function_id).first_decl_id(),
-      callee.first_decl_id());
+      target.function.first_decl_id());
 
   return carbon_thunk_function_id;
 }
@@ -464,13 +557,6 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
     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;
-  }
-
   // Map the parent scope into the C++ AST.
   auto* decl_context =
       ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id);
@@ -478,24 +564,13 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
     return nullptr;
   }
 
-  // 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(
-        context.sem_ir(), context.insts().Get(callee_param_inst_id).type_id());
-    callee_param_type_ids.push_back(scrutinee_type_id);
-  }
+  FunctionInfo target_function_info(context, callee_function_id, callee,
+                                    decl_context);
 
   // 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_function_id, callee, callee_param_type_ids);
+  auto carbon_thunk_function_id =
+      BuildCarbonToCarbonThunk(context, loc_id, target_function_info);
 
   // Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
   auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
@@ -505,10 +580,9 @@ auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
   }
 
   // Create a C++ thunk that calls the Carbon thunk.
-  return BuildCppToCarbonThunk(context, loc_id, decl_context,
+  return BuildCppToCarbonThunk(context, loc_id, target_function_info,
                                context.names().GetFormatted(callee.name_id),
-                               carbon_function_decl, callee_param_type_ids,
-                               callee.GetDeclaredReturnType(context.sem_ir()));
+                               carbon_function_decl);
 }
 
 }  // namespace Carbon::Check

+ 1 - 10
toolchain/check/testdata/interop/cpp/function/export/function.carbon

@@ -101,22 +101,13 @@ void G() {
 }
 ''';
 
-// --- fail_todo_method.carbon
+// --- method.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_method.carbon:[[@LINE+5]]:1: in import [InImport]
-// CHECK:STDERR: other.carbon:11:3: error: semantics TODO: `unsupported: C++ calling a Carbon function with an implicit parameter` [SemanticsTodo]
-// CHECK:STDERR:   fn Method[self: Self]() { self; }
-// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
 import Other;
 import Cpp inline '''
 void G() {
-  // CHECK:STDERR: fail_todo_method.carbon:[[@LINE+4]]:22: error: no member named 'Method' in 'Carbon::Other::C' [CppInteropParseError]
-  // CHECK:STDERR:    16 |   Carbon::Other::C().Method();
-  // CHECK:STDERR:       |   ~~~~~~~~~~~~~~~~~~ ^
-  // CHECK:STDERR:
   Carbon::Other::C().Method();
 }
 ''';

+ 220 - 1
toolchain/lower/testdata/interop/cpp/reverse/method.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/int.carbon
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
@@ -10,6 +10,41 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/reverse/method.carbon
 
+// --- method.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  fn F[self: Self]() { self; }
+}
+
+inline Cpp '''
+void CallF() {
+  Carbon::A().F();
+}
+''';
+
+// --- method_with_args_and_return.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  fn F[self: Self](a: i32) -> i32 {
+    self;
+    return a;
+  }
+}
+
+inline Cpp '''
+int CallF() {
+  return Carbon::A().F(123);
+}
+''';
+
+fn CallCallF() -> i32 { return Cpp.CallF(); }
+
 // --- static.carbon
 library "[[@TEST_NAME]]";
 
@@ -27,6 +62,190 @@ void CallF() {
 
 fn CallCallF() { Cpp.CallF(); }
 
+// CHECK:STDOUT: ; ModuleID = 'method.carbon'
+// CHECK:STDOUT: source_filename = "method.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: %"class.Carbon::A" = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable
+// CHECK:STDOUT: define dso_local void @_Z5CallFv() #0 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %ref.tmp = alloca %"class.Carbon::A", align 1
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %ref.tmp) #3
+// CHECK:STDOUT:   call void @_ZN6Carbon1A1FEv(ptr noundef nonnull align 1 dereferenceable(1) %ref.tmp)
+// CHECK:STDOUT:   call void @llvm.lifetime.end.p0(ptr %ref.tmp) #3
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZN6Carbon1A1FEv(ptr noundef nonnull align 1 dereferenceable(1) %this) #2 align 2 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %this.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %this, ptr %this.addr, align 8, !tbaa !11
+// CHECK:STDOUT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK:STDOUT:   call void @_CF__carbon_thunk.A.Main(ptr noundef nonnull align 1 dereferenceable(1) %this1)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.end.p0(ptr captures(none)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CF.A.Main(ptr %self) #3 !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !20
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CF__carbon_thunk.A.Main(ptr %self) #3 !dbg !21 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CF.A.Main(ptr %self), !dbg !24
+// CHECK:STDOUT:   ret void, !dbg !24
+// 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 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// 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: attributes #3 = { nounwind }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT: !llvm.errno.tbaa = !{!7}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 8, !"PIC Level", i32 2}
+// CHECK:STDOUT: !3 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !4 = !{i32 7, !"uwtable", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "method.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!8, !8, i64 0}
+// 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 = !{!"p1 _ZTSN6Carbon1AE", !13, i64 0}
+// CHECK:STDOUT: !13 = !{!"any pointer", !9, i64 0}
+// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "F", linkageName: "_CF.A.Main", scope: null, file: !6, line: 6, type: !15, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !18)
+// CHECK:STDOUT: !15 = !DISubroutineType(types: !16)
+// CHECK:STDOUT: !16 = !{null, !17}
+// CHECK:STDOUT: !17 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !18 = !{!19}
+// CHECK:STDOUT: !19 = !DILocalVariable(arg: 1, scope: !14, type: !17)
+// CHECK:STDOUT: !20 = !DILocation(line: 6, column: 3, scope: !14)
+// CHECK:STDOUT: !21 = distinct !DISubprogram(name: "F__carbon_thunk", linkageName: "_CF__carbon_thunk.A.Main", scope: null, file: !6, line: 6, type: !15, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !22)
+// CHECK:STDOUT: !22 = !{!23}
+// CHECK:STDOUT: !23 = !DILocalVariable(arg: 1, scope: !21, type: !17)
+// CHECK:STDOUT: !24 = !DILocation(line: 6, column: 3, scope: !21)
+// CHECK:STDOUT: ; ModuleID = 'method_with_args_and_return.carbon'
+// CHECK:STDOUT: source_filename = "method_with_args_and_return.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: %"class.Carbon::A" = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable
+// CHECK:STDOUT: define dso_local noundef i32 @_Z5CallFv() #0 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %ref.tmp = alloca %"class.Carbon::A", align 1
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %ref.tmp) #3
+// CHECK:STDOUT:   %call = call noundef i32 @_ZN6Carbon1A1FEi(ptr noundef nonnull align 1 dereferenceable(1) %ref.tmp, i32 noundef 123)
+// CHECK:STDOUT:   call void @llvm.lifetime.end.p0(ptr %ref.tmp) #3
+// CHECK:STDOUT:   ret i32 %call
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal noundef i32 @_ZN6Carbon1A1FEi(ptr noundef nonnull align 1 dereferenceable(1) %this, i32 noundef %0) #2 align 2 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %retval = alloca i32, align 4
+// CHECK:STDOUT:   %this.addr = alloca ptr, align 8
+// CHECK:STDOUT:   %.addr = alloca i32, align 4
+// CHECK:STDOUT:   store ptr %this, ptr %this.addr, align 8, !tbaa !11
+// CHECK:STDOUT:   store i32 %0, ptr %.addr, align 4, !tbaa !7
+// CHECK:STDOUT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK:STDOUT:   call void @_CF__carbon_thunk.A.Main(ptr noundef nonnull align 1 dereferenceable(1) %this1, ptr noundef nonnull align 4 dereferenceable(4) %.addr, ptr noundef nonnull align 4 dereferenceable(4) %retval)
+// CHECK:STDOUT:   %1 = load i32, ptr %retval, align 4
+// CHECK:STDOUT:   ret i32 %1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.end.p0(ptr captures(none)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define i32 @_CF.A.Main(ptr %self, i32 %a) #3 !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret i32 %a, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CF__carbon_thunk.A.Main(ptr %self, ptr %_, ptr %_1) #3 !dbg !23 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc6_35.2 = load i32, ptr %_, align 4, !dbg !30
+// CHECK:STDOUT:   %A.F.call = call i32 @_CF.A.Main(ptr %self, i32 %.loc6_35.2), !dbg !30
+// CHECK:STDOUT:   store i32 %A.F.call, ptr %_1, align 4, !dbg !30
+// CHECK:STDOUT:   ret void, !dbg !30
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define i32 @_CCallCallF.Main() #3 !dbg !31 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %CallF.call = call i32 @_Z5CallFv(), !dbg !34
+// CHECK:STDOUT:   ret i32 %CallF.call, !dbg !35
+// 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 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// 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: attributes #3 = { nounwind }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT: !llvm.errno.tbaa = !{!7}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 8, !"PIC Level", i32 2}
+// CHECK:STDOUT: !3 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !4 = !{i32 7, !"uwtable", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "method_with_args_and_return.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!8, !8, i64 0}
+// 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 = !{!"p1 _ZTSN6Carbon1AE", !13, i64 0}
+// CHECK:STDOUT: !13 = !{!"any pointer", !9, i64 0}
+// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "F", linkageName: "_CF.A.Main", scope: null, file: !6, line: 6, type: !15, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !19)
+// CHECK:STDOUT: !15 = !DISubroutineType(types: !16)
+// CHECK:STDOUT: !16 = !{!17, !18, !17}
+// CHECK:STDOUT: !17 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+// CHECK:STDOUT: !18 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+// CHECK:STDOUT: !19 = !{!20, !21}
+// CHECK:STDOUT: !20 = !DILocalVariable(arg: 1, scope: !14, type: !18)
+// CHECK:STDOUT: !21 = !DILocalVariable(arg: 2, scope: !14, type: !17)
+// CHECK:STDOUT: !22 = !DILocation(line: 8, column: 5, scope: !14)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "F__carbon_thunk", linkageName: "_CF__carbon_thunk.A.Main", scope: null, file: !6, line: 6, type: !24, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !26)
+// CHECK:STDOUT: !24 = !DISubroutineType(types: !25)
+// CHECK:STDOUT: !25 = !{null, !18, !17, !17}
+// CHECK:STDOUT: !26 = !{!27, !28, !29}
+// CHECK:STDOUT: !27 = !DILocalVariable(arg: 1, scope: !23, type: !18)
+// CHECK:STDOUT: !28 = !DILocalVariable(arg: 2, scope: !23, type: !17)
+// CHECK:STDOUT: !29 = !DILocalVariable(arg: 3, scope: !23, type: !17)
+// CHECK:STDOUT: !30 = !DILocation(line: 6, column: 3, scope: !23)
+// CHECK:STDOUT: !31 = distinct !DISubprogram(name: "CallCallF", linkageName: "_CCallCallF.Main", scope: null, file: !6, line: 18, type: !32, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !32 = !DISubroutineType(types: !33)
+// CHECK:STDOUT: !33 = !{!17}
+// CHECK:STDOUT: !34 = !DILocation(line: 18, column: 32, scope: !31)
+// CHECK:STDOUT: !35 = !DILocation(line: 18, column: 25, scope: !31)
 // CHECK:STDOUT: ; ModuleID = 'static.carbon'
 // CHECK:STDOUT: source_filename = "static.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"