Browse Source

Support mangling imported C++ functions using Clang's `MangleContext` (#5062)

Keep a pointer to the Clang declaration in Carbon's function declaration
and use it in Carbon mangling by calling Clang mangling.
Create Clang's `MangleContext` once on demand.

Part of #4666.

C++ Interop Demo:

```c++
// hello_world.h

void hello_world();
```

```c++
// hello_world.cpp

#include <cstdio>

void hello_world() { printf("Hello World!\n"); }
```

```
// main.carbon

library "Main";

import Cpp library "hello_world.h";

fn Run() -> i32 {
  Cpp.hello_world();
  return 0;
}
```

```shell
$ clang -c hello_world.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link hello_world.o main.o --output=demo
$ ./demo
Hello World!
```
Boaz Brickner 1 year ago
parent
commit
156ab889f8

+ 2 - 1
toolchain/check/import_cpp.cpp

@@ -237,7 +237,8 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
        .definition_id = SemIR::InstId::None},
       {.return_slot_pattern_id = SemIR::InstId::None,
        .virtual_modifier = SemIR::FunctionFields::VirtualModifier::None,
-       .self_param_id = SemIR::InstId::None}};
+       .self_param_id = SemIR::InstId::None,
+       .cpp_decl = clang_decl}};
 
   function_decl.function_id = context.functions().Add(function_info);
 

+ 1 - 0
toolchain/lower/BUILD

@@ -57,6 +57,7 @@ cc_library(
         "//toolchain/sem_ir:inst",
         "//toolchain/sem_ir:inst_namer",
         "//toolchain/sem_ir:typed_insts",
+        "@llvm-project//clang:ast",
         "@llvm-project//llvm:Core",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:TransformUtils",

+ 18 - 0
toolchain/lower/mangler.cpp

@@ -136,6 +136,9 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
     CARBON_CHECK(!specific_id.has_value(), "entry point should not be generic");
     return "main";
   }
+  if (function.cpp_decl) {
+    return MangleCppClang(function.cpp_decl);
+  }
   RawStringOstream os;
   os << "_C";
 
@@ -158,4 +161,19 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
   return os.TakeStr();
 }
 
+auto Mangler::MangleCppClang(const clang::NamedDecl* decl) -> std::string {
+  if (!cpp_mangle_context_) {
+    // We assume all declarations are from the same AST Context.
+    // TODO: Consider initializing this in the constructor. This is related to:
+    // https://github.com/carbon-language/carbon-lang/issues/4666. See
+    // https://github.com/carbon-language/carbon-lang/pull/5062/files/89e56d51858bcc18d4242d4e5c9ee0e7496d887e#r1979993815
+    cpp_mangle_context_.reset(decl->getASTContext().createMangleContext());
+  }
+
+  RawStringOstream cpp_mangled_name;
+  cpp_mangle_context_->mangleName(decl, cpp_mangled_name);
+
+  return cpp_mangled_name.TakeStr();
+}
+
 }  // namespace Carbon::Lower

+ 9 - 0
toolchain/lower/mangler.h

@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "clang/AST/Mangle.h"
 #include "toolchain/lower/file_context.h"
 #include "toolchain/sem_ir/constant.h"
 #include "toolchain/sem_ir/ids.h"
@@ -37,6 +38,9 @@ class Mangler {
                                        SemIR::NameScopeId name_scope_id)
       -> void;
 
+  // Generates a mangled name using Clang mangling for imported C++ functions.
+  auto MangleCppClang(const clang::NamedDecl* decl) -> std::string;
+
   auto sem_ir() const -> const SemIR::File& { return file_context_.sem_ir(); }
 
   auto names() const -> SemIR::NameStoreWrapper { return sem_ir().names(); }
@@ -54,6 +58,11 @@ class Mangler {
   // TODO: If `file_context_` has an `InstNamer`, we could share its
   // fingerprinter.
   SemIR::InstFingerprinter fingerprinter_;
+
+  // Clang Mangler lazily initialized when necessary. We create it once under
+  // the assumption all declarations we need to mangle can use the same Mangler
+  // (same AST Context).
+  std::unique_ptr<clang::MangleContext> cpp_mangle_context_;
 };
 
 }  // namespace Carbon::Lower

+ 47 - 0
toolchain/lower/testdata/interop/cpp/function_decl.carbon

@@ -0,0 +1,47 @@
+// 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
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/function_decl.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/function_decl.carbon
+
+// --- function_decl.h
+
+void foo();
+
+// --- import_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "function_decl.h";
+
+fn MyF() {
+  Cpp.foo();
+}
+
+// CHECK:STDOUT: ; ModuleID = 'import_function_decl.carbon'
+// CHECK:STDOUT: source_filename = "import_function_decl.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z3foov(), !dbg !7
+// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_Z3foov()
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "import_function_decl.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 7, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 6, column: 1, scope: !4)

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -126,6 +126,7 @@ cc_library(
         "//toolchain/lex:token_kind",
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
+        "@llvm-project//clang:ast",
         "@llvm-project//clang:frontend",
         "@llvm-project//llvm:Support",
     ],

+ 8 - 0
toolchain/sem_ir/function.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_TOOLCHAIN_SEM_IR_FUNCTION_H_
 #define CARBON_TOOLCHAIN_SEM_IR_FUNCTION_H_
 
+#include "clang/AST/Decl.h"
 #include "toolchain/sem_ir/builtin_function_kind.h"
 #include "toolchain/sem_ir/entity_with_params_base.h"
 #include "toolchain/sem_ir/ids.h"
@@ -49,6 +50,13 @@ struct FunctionFields {
   // function, in lexical order. The first block is the entry block. This will
   // be empty for declarations that don't have a visible definition.
   llvm::SmallVector<InstBlockId> body_block_ids = {};
+
+  // If the function is imported from C++, points to the Clang declaration in
+  // the AST. Used for mangling. The AST is owned by `CompileSubcommand` so we
+  // expect it to be live from `Function` creation to mangling.
+  // TODO: #4666 Ensure we can easily serialize/deserialize this. Consider decl
+  // ID to point into the AST.
+  const clang::NamedDecl* cpp_decl = nullptr;
 };
 
 // A function. See EntityWithParamsBase regarding the inheritance here.