Эх сурвалжийг харах

Superficial support for exporting complete class types to C++. (#7029)

We don't yet populate the bases or fields, so the class types show up as
empty classes in C++ for now. But we do allow calls to static member
functions.
Richard Smith 3 долоо хоног өмнө
parent
commit
b74e0d1260

+ 37 - 18
toolchain/check/cpp/export.cpp

@@ -21,7 +21,6 @@ namespace Carbon::Check {
 // 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* {
@@ -57,11 +56,9 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
                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);
+      context.ast_context(), context.ast_context().getTranslationUnitDecl(),
+      /*StartLoc=*/clang_loc, /*NLoc=*/clang_loc, identifier_info,
+      cpp_function_type, /*TInfo=*/nullptr, clang::SC_Extern);
 
   // Build parameter decls.
   llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
@@ -85,22 +82,42 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context,
 
 // Create the declaration of the C++ thunk.
 static auto BuildCppToCarbonThunkDecl(
-    Context& context, SemIR::LocId loc_id, clang::DeclarationName thunk_name,
+    Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
+    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);
 
+  clang::DeclarationNameInfo name_info(thunk_name, clang_loc);
+
   auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
   clang::QualType thunk_function_type = ast_context.getFunctionType(
       ast_context.VoidTy, thunk_param_types, ext_proto_info);
 
-  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);
+
+  const bool uses_fp_intrin = false;
+  const bool inline_specified = true;
+  const auto constexpr_kind = clang::ConstexprSpecKind::Unspecified;
+  const 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.
+    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,
+        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,
+        /*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause);
+  }
   decl_context->addDecl(thunk_function_decl);
 
   llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
@@ -113,9 +130,11 @@ static auto BuildCppToCarbonThunkDecl(
   }
   thunk_function_decl->setParams(param_var_decls);
 
-  // Set always_inline.
+  // Force the thunk to be inlined and discarded.
   thunk_function_decl->addAttr(
       clang::AlwaysInlineAttr::CreateImplicit(ast_context));
+  thunk_function_decl->addAttr(
+      clang::InternalLinkageAttr::CreateImplicit(ast_context));
 
   return thunk_function_decl;
 }
@@ -151,8 +170,8 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
 // 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,
+    Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
+    llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl,
     llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids)
     -> clang::FunctionDecl* {
   // Create the thunk's name.
@@ -170,8 +189,8 @@ static auto BuildCppToCarbonThunk(
     param_types.push_back(cpp_type);
   }
 
-  auto* thunk_function_decl =
-      BuildCppToCarbonThunkDecl(context, loc_id, &thunk_ident, param_types);
+  auto* thunk_function_decl = BuildCppToCarbonThunkDecl(
+      context, loc_id, decl_context, &thunk_ident, param_types);
 
   // Build the thunk function body.
   clang::Sema& sema = context.clang_sema();
@@ -258,13 +277,13 @@ auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
 
   // 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);
+      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,
+  return BuildCppToCarbonThunk(context, loc_id, &decl_context,
                                context.names().GetFormatted(callee.name_id),
                                carbon_function_decl, callee_param_type_ids);
 }

+ 73 - 2
toolchain/check/cpp/generate_ast.cpp

@@ -32,6 +32,7 @@
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/name_lookup.h"
+#include "toolchain/check/type_completion.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/emitter.h"
 #include "toolchain/diagnostics/format_providers.h"
@@ -324,14 +325,15 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
                                    clang::ASTContext* ast_context)
       : context_(context), ast_context_(ast_context) {}
 
+  auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
+
   // Look up decls for `decl_name` inside `decl_context`, adding the decls to
   // `decl_context`. Returns true if any decls were added.
   auto FindExternalVisibleDeclsByName(
       const clang::DeclContext* decl_context, clang::DeclarationName decl_name,
       const clang::DeclContext* original_decl_context) -> bool override;
 
-  // See clang::ExternalASTSource.
-  auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
+  auto CompleteType(clang::TagDecl* tag_decl) -> void override;
 
  private:
   // Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot
@@ -339,6 +341,28 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
   auto MapInstIdToClangDecl(clang::DeclContext& decl_context,
                             LookupResult lookup) -> clang::NamedDecl*;
 
+  // Get a current best-effort location for the current position within C++
+  // processing.
+  auto GetCurrentCppLocId() -> SemIR::LocId {
+    auto* cpp_context = context_->cpp_context();
+    CARBON_CHECK(cpp_context);
+
+    // Use the current token location when parsing.
+    auto clang_source_loc = cpp_context->parser().getCurToken().getLocation();
+    if (auto& code_synthesis_contexts =
+            cpp_context->sema().CodeSynthesisContexts;
+        !code_synthesis_contexts.empty()) {
+      // Use the current point of instantiation during template instantiation.
+      clang_source_loc = code_synthesis_contexts.back().PointOfInstantiation;
+    }
+
+    // TODO: Refactor with AddImportIRInst in import.cpp.
+    SemIR::ClangSourceLocId clang_source_loc_id =
+        context_->sem_ir().clang_source_locs().Add(clang_source_loc);
+    return context_->import_ir_insts().Add(
+        SemIR::ImportIRInst(clang_source_loc_id));
+  }
+
   Check::Context* context_;
   clang::ASTContext* ast_context_;
 
@@ -419,11 +443,19 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
           GetClangIdentifierInfo(*context_, class_info.name_id);
       CARBON_CHECK(identifier_info, "class with non-identifier name {0}",
                    class_info.name_id);
+      // TODO: Check whether we've already mapped this class and if so, return
+      // the prior mapping.
       auto* record_decl = clang::CXXRecordDecl::Create(
           *ast_context_, clang::TagTypeKind::Class, &decl_context,
           clang::SourceLocation(), clang::SourceLocation(), identifier_info);
       auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(record_decl);
       context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
+      record_decl->setHasExternalLexicalStorage();
+      record_decl->setHasExternalVisibleStorage();
+      if (isa<clang::CXXRecordDecl>(decl_context)) {
+        // TODO: Map Carbon access to C++ access.
+        record_decl->setAccess(clang::AS_public);
+      }
       return record_decl;
     }
     case SemIR::StructValue::Kind: {
@@ -536,6 +568,45 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
   return true;
 }
 
+auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void {
+  auto* class_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl);
+  if (!class_decl) {
+    // TODO: If we start producing clang EnumTypes, we may have to handle them
+    // here too.
+    return;
+  }
+
+  auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(tag_decl->getFirstDecl());
+  auto clang_decl_id = context_->clang_decls().Lookup(key);
+  if (!clang_decl_id.has_value()) {
+    return;
+  }
+
+  auto inst_id = context_->clang_decls().Get(clang_decl_id).inst_id;
+  auto const_id = context_->constant_values().Get(inst_id);
+  if (!const_id.has_value()) {
+    return;
+  }
+
+  auto class_type =
+      context_->constant_values().TryGetInstAs<SemIR::ClassType>(const_id);
+  if (!class_type) {
+    return;
+  }
+
+  auto class_type_id = context_->types().GetTypeIdForTypeConstantId(const_id);
+  auto context_fn = [](DiagnosticContextBuilder& /*builder*/) -> void {};
+  if (!RequireCompleteType(*context_, class_type_id, GetCurrentCppLocId(),
+                           context_fn)) {
+    return;
+  }
+
+  class_decl->startDefinition();
+  // TODO: Import base class and fields, plus any special member functions that
+  // affect class properties.
+  class_decl->completeDefinition();
+}
+
 // Parses a sequence of top-level declarations and forms a corresponding
 // representation in the Clang AST. Unlike clang::ParseAST, does not finish the
 // translation unit when EOF is reached.

+ 109 - 0
toolchain/check/testdata/interop/cpp/reverse/class.carbon

@@ -0,0 +1,109 @@
+// 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
+//
+// 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/class.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/reverse/class.carbon
+
+// --- static_members.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  fn F() {}
+}
+
+inline Cpp '''
+void G() {
+  Carbon::A::F();
+}
+''';
+
+// --- nested.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  class B {
+    class C {
+    }
+  }
+}
+
+inline Cpp '''
+void G() {
+  Carbon::A::B::C *p = nullptr;
+}
+''';
+
+// --- fail_incomplete_concrete.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+// CHECK:STDERR: fail_incomplete_concrete.carbon:[[@LINE+4]]:1: error: class was forward declared here [ClassForwardDeclaredHere]
+// CHECK:STDERR: class A;
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR:
+class A;
+
+inline Cpp '''
+// CHECK:STDERR: fail_incomplete_concrete.carbon:[[@LINE+4]]:11: error: variable has incomplete type 'Carbon::A' [CppInteropParseError]
+// CHECK:STDERR:    16 | Carbon::A a;
+// CHECK:STDERR:       |           ^
+// CHECK:STDERR:
+Carbon::A a;
+''';
+
+// --- fail_todo_monomorphization_failure.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class B(N:! i32) {
+  var a: array(i8, N);
+}
+
+// TODO: Once we allow this, we should produce an "invalid array bound" error
+// during monomorphization triggered by the C++ code.
+// CHECK:STDERR: fail_todo_monomorphization_failure.carbon:[[@LINE+4]]:11: error: alias initializer must be a name reference [AliasRequiresNameRef]
+// CHECK:STDERR: alias T = B(-1);
+// CHECK:STDERR:           ^~~~~
+// CHECK:STDERR:
+alias T = B(-1);
+
+inline Cpp '''
+// CHECK:STDERR: fail_todo_monomorphization_failure.carbon:[[@LINE+4]]:9: error: no type named 'T' in namespace 'Carbon' [CppInteropParseError]
+// CHECK:STDERR:    22 | Carbon::T t;
+// CHECK:STDERR:       | ~~~~~~~~^
+// CHECK:STDERR:
+Carbon::T t;
+''';
+
+// --- fail_todo_fields.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  var a: i32;
+  var b: i32;
+}
+
+inline Cpp '''
+// CHECK:STDERR: fail_todo_fields.carbon:[[@LINE+7]]:15: error: static assertion failed due to requirement 'sizeof(Carbon::A) == 2 * sizeof(int)' [CppInteropParseError]
+// CHECK:STDERR:    18 | static_assert(sizeof(Carbon::A) == 2 * sizeof(int));
+// CHECK:STDERR:       |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR: fail_todo_fields.carbon:[[@LINE+4]]:33: note: expression evaluates to '1 == 8' [CppInteropParseNote]
+// CHECK:STDERR:    18 | static_assert(sizeof(Carbon::A) == 2 * sizeof(int));
+// CHECK:STDERR:       |               ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+static_assert(sizeof(Carbon::A) == 2 * sizeof(int));
+''';

+ 8 - 7
toolchain/check/testdata/interop/cpp/reverse/function.carbon

@@ -114,16 +114,17 @@ void G() {
 
 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+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: 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();
 }

+ 84 - 0
toolchain/lower/testdata/interop/cpp/reverse/class.carbon

@@ -0,0 +1,84 @@
+// 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
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/reverse/class.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/reverse/class.carbon
+
+// --- pointer.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  var x: i32;
+  var y: i32;
+}
+
+inline Cpp '''
+Carbon::A *pass(Carbon::A *p) { return p; }
+void call() { pass(nullptr); }
+''';
+
+fn DoIt() { Cpp.call(); }
+
+// CHECK:STDOUT: ; ModuleID = 'pointer.carbon'
+// CHECK:STDOUT: source_filename = "pointer.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress nounwind uwtable
+// CHECK:STDOUT: define dso_local noundef ptr @_Z4passPN6Carbon1AE(ptr noundef %p) #0 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %p.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %p, ptr %p.addr, align 8, !tbaa !11
+// CHECK:STDOUT:   %0 = load ptr, ptr %p.addr, align 8, !tbaa !11
+// CHECK:STDOUT:   ret ptr %0
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress nounwind uwtable
+// CHECK:STDOUT: define dso_local void @_Z4callv() #0 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %call = call noundef ptr @_Z4passPN6Carbon1AE(ptr noundef null)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CDoIt.Main() #1 !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z4callv(), !dbg !17
+// CHECK:STDOUT:   ret void, !dbg !18
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { 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 = { 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: "pointer.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: "DoIt", linkageName: "_CDoIt.Main", scope: null, file: !6, line: 16, type: !15, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !15 = !DISubroutineType(types: !16)
+// CHECK:STDOUT: !16 = !{null}
+// CHECK:STDOUT: !17 = !DILocation(line: 16, column: 13, scope: !14)
+// CHECK:STDOUT: !18 = !DILocation(line: 16, column: 1, scope: !14)

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

@@ -124,22 +124,22 @@ 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 @_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:   call void @_ZN6Carbon5OtherL17NoArgs__cpp_thunkEv()
+// CHECK:STDOUT:   call void @_ZN6Carbon5OtherL18BoolArg__cpp_thunkEb(i1 noundef zeroext true)
+// CHECK:STDOUT:   call void @_ZN6Carbon5OtherL17IntArg__cpp_thunkEi(i32 noundef 123)
+// CHECK:STDOUT:   call void @_ZN6Carbon5OtherL19FloatArg__cpp_thunkEf(float noundef 1.500000e+00)
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
-// CHECK:STDOUT: define internal void @_ZL17NoArgs__cpp_thunkv() #1 {
+// CHECK:STDOUT: define internal void @_ZN6Carbon5OtherL17NoArgs__cpp_thunkEv() #1 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   call void @_CNoArgs__carbon_thunk.Other()
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
-// CHECK:STDOUT: define internal void @_ZL18BoolArg__cpp_thunkb(i1 noundef zeroext %0) #1 {
+// CHECK:STDOUT: define internal void @_ZN6Carbon5OtherL18BoolArg__cpp_thunkEb(i1 noundef zeroext %0) #1 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.addr = alloca i8, align 1
 // CHECK:STDOUT:   %storedv = zext i1 %0 to i8
@@ -149,7 +149,7 @@ fn H() { Cpp.G2(); }
 // 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: define internal void @_ZN6Carbon5OtherL17IntArg__cpp_thunkEi(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
@@ -158,7 +158,7 @@ fn H() { Cpp.G2(); }
 // 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: define internal void @_ZN6Carbon5OtherL19FloatArg__cpp_thunkEf(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
@@ -291,12 +291,12 @@ fn H() { Cpp.G2(); }
 // 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:   call void @_ZN6CarbonL12F__cpp_thunkEv()
 // 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: define internal void @_ZN6CarbonL12F__cpp_thunkEv() #2 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   call void @_CF__carbon_thunk.Main()
 // CHECK:STDOUT:   ret void

+ 96 - 0
toolchain/lower/testdata/interop/cpp/reverse/method.carbon

@@ -0,0 +1,96 @@
+// 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/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/reverse/method.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/reverse/method.carbon
+
+// --- static.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {
+  fn F() {}
+}
+
+inline Cpp '''
+void CallF() {
+  Carbon::A::F();
+}
+''';
+
+fn CallCallF() { Cpp.CallF(); }
+
+// 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"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable
+// CHECK:STDOUT: define dso_local void @_Z5CallFv() #0 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_ZN6Carbon1A12F__cpp_thunkEv()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
+// CHECK:STDOUT: define internal void @_ZN6Carbon1A12F__cpp_thunkEv() #1 align 2 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CF__carbon_thunk.A.Main()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CF.A.Main() #2 !dbg !11 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CF__carbon_thunk.A.Main() #2 !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CF.A.Main(), !dbg !16
+// CHECK:STDOUT:   ret void, !dbg !16
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CCallCallF.Main() #2 !dbg !17 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z5CallFv(), !dbg !18
+// CHECK:STDOUT:   ret void, !dbg !19
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT: attributes #2 = { nounwind }
+// 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: "static.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 = distinct !DISubprogram(name: "F", linkageName: "_CF.A.Main", scope: null, file: !6, line: 6, type: !12, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !12 = !DISubroutineType(types: !13)
+// CHECK:STDOUT: !13 = !{null}
+// CHECK:STDOUT: !14 = !DILocation(line: 6, column: 3, scope: !11)
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "F__carbon_thunk", linkageName: "_CF__carbon_thunk.A.Main", scope: null, file: !6, line: 6, type: !12, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !16 = !DILocation(line: 6, column: 3, scope: !15)
+// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "CallCallF", linkageName: "_CCallCallF.Main", scope: null, file: !6, line: 15, type: !12, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !18 = !DILocation(line: 15, column: 18, scope: !17)
+// CHECK:STDOUT: !19 = !DILocation(line: 15, column: 1, scope: !17)