From 156ab889f8502ce6430a16554a137f1ee2e67e14 Mon Sep 17 00:00:00 2001 From: Boaz Brickner Date: Thu, 6 Mar 2025 10:51:01 +0100 Subject: [PATCH] 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 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! ``` --- toolchain/check/import_cpp.cpp | 3 +- toolchain/lower/BUILD | 1 + toolchain/lower/mangler.cpp | 18 +++++++ toolchain/lower/mangler.h | 9 ++++ .../testdata/interop/cpp/function_decl.carbon | 47 +++++++++++++++++++ toolchain/sem_ir/BUILD | 1 + toolchain/sem_ir/function.h | 8 ++++ 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 toolchain/lower/testdata/interop/cpp/function_decl.carbon diff --git a/toolchain/check/import_cpp.cpp b/toolchain/check/import_cpp.cpp index 7aa5f8275076..79ff2676f4ca 100644 --- a/toolchain/check/import_cpp.cpp +++ b/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); diff --git a/toolchain/lower/BUILD b/toolchain/lower/BUILD index 049d9c3aa870..af3404d77dc9 100644 --- a/toolchain/lower/BUILD +++ b/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", diff --git a/toolchain/lower/mangler.cpp b/toolchain/lower/mangler.cpp index 89c8f75e42a9..87a898815e87 100644 --- a/toolchain/lower/mangler.cpp +++ b/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 diff --git a/toolchain/lower/mangler.h b/toolchain/lower/mangler.h index fa582abf89fb..6345e3d4372e 100644 --- a/toolchain/lower/mangler.h +++ b/toolchain/lower/mangler.h @@ -7,6 +7,7 @@ #include +#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 cpp_mangle_context_; }; } // namespace Carbon::Lower diff --git a/toolchain/lower/testdata/interop/cpp/function_decl.carbon b/toolchain/lower/testdata/interop/cpp/function_decl.carbon new file mode 100644 index 000000000000..016a1b8c091f --- /dev/null +++ b/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) diff --git a/toolchain/sem_ir/BUILD b/toolchain/sem_ir/BUILD index 48f34e4cedfb..f00b6ca92224 100644 --- a/toolchain/sem_ir/BUILD +++ b/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", ], diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index f738105588b2..e91590babd58 100644 --- a/toolchain/sem_ir/function.h +++ b/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 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.