Преглед изворни кода

Add support for importing C++ inline functions (#5427)

This requires:
* Making `FunctionDecl` mutable since generating code
(`HandleTopLevelDecl()`) requires a mutable declaration and since we
manually add `used` attribute to force code generation.
* Passing the file system to `Lower` since it's needed by Clang code
generation.
* Creating an internal Clang LLVM module and link it against the Carbon
LLVM module.

Demo:

```c++
// hello_world.h

extern int puts;
inline void hello_world() {
  ((int (*)(const char*))&puts)("hello world");
}
```

```carbon
// main.carbon

library "Main";

import Cpp library "hello_world.h";

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

```shell
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link main.o --output=demo
$ ./demo
hello world
```

Based on https://github.com/carbon-language/carbon-lang/pull/5406.

Part of #5405.
Boaz Brickner пре 11 месеци
родитељ
комит
852d0191a9

+ 7 - 3
toolchain/check/check.cpp

@@ -326,7 +326,8 @@ auto CheckParseTrees(
     llvm::MutableArrayRef<Unit> units,
     llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
     bool prelude_import, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    llvm::raw_ostream* vlog_stream, bool fuzzing) -> void {
+    llvm::StringRef target, llvm::raw_ostream* vlog_stream, bool fuzzing)
+    -> void {
   // UnitAndImports is big due to its SmallVectors, so we default to 0 on the
   // stack.
   llvm::SmallVector<UnitAndImports, 0> unit_infos(
@@ -380,7 +381,8 @@ auto CheckParseTrees(
   for (int check_index = 0;
        check_index < static_cast<int>(ready_to_check.size()); ++check_index) {
     auto* unit_info = ready_to_check[check_index];
-    CheckUnit(unit_info, tree_and_subtrees_getters, fs, vlog_stream).Run();
+    CheckUnit(unit_info, tree_and_subtrees_getters, fs, target, vlog_stream)
+        .Run();
     for (auto* incoming_import : unit_info->incoming_imports) {
       --incoming_import->imports_remaining;
       if (incoming_import->imports_remaining == 0) {
@@ -427,7 +429,9 @@ auto CheckParseTrees(
     // incomplete imports.
     for (auto& unit_info : unit_infos) {
       if (unit_info.imports_remaining > 0) {
-        CheckUnit(&unit_info, tree_and_subtrees_getters, fs, vlog_stream).Run();
+        CheckUnit(&unit_info, tree_and_subtrees_getters, fs, target,
+                  vlog_stream)
+            .Run();
       }
     }
   }

+ 2 - 1
toolchain/check/check.h

@@ -35,7 +35,8 @@ auto CheckParseTrees(
     llvm::MutableArrayRef<Unit> units,
     llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
     bool prelude_import, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-    llvm::raw_ostream* vlog_stream, bool fuzzing) -> void;
+    llvm::StringRef target, llvm::raw_ostream* vlog_stream, bool fuzzing)
+    -> void;
 
 }  // namespace Carbon::Check
 

+ 3 - 2
toolchain/check/check_unit.cpp

@@ -53,7 +53,7 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int {
 CheckUnit::CheckUnit(
     UnitAndImports* unit_and_imports,
     llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
-    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, llvm::StringRef target,
     llvm::raw_ostream* vlog_stream)
     : unit_and_imports_(unit_and_imports),
       tree_and_subtrees_getter_(
@@ -61,6 +61,7 @@ CheckUnit::CheckUnit(
               [unit_and_imports->unit->sem_ir->check_ir_id().index]),
       total_ir_count_(tree_and_subtrees_getters.size()),
       fs_(std::move(fs)),
+      target_(target),
       vlog_stream_(vlog_stream),
       emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters,
                unit_and_imports_->unit->sem_ir),
@@ -154,7 +155,7 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
     CARBON_CHECK(!cpp_ast->get());
     *cpp_ast =
         ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
-                       cpp_import_names, fs_);
+                       cpp_import_names, fs_, target_);
   }
 }
 

+ 2 - 1
toolchain/check/check_unit.h

@@ -124,7 +124,7 @@ class CheckUnit {
       UnitAndImports* unit_and_imports,
       llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
       llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-      llvm::raw_ostream* vlog_stream);
+      llvm::StringRef target, llvm::raw_ostream* vlog_stream);
 
   // Produces and checks the IR for the provided unit.
   auto Run() -> void;
@@ -191,6 +191,7 @@ class CheckUnit {
   // The number of IRs being checked in total.
   int total_ir_count_;
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
+  llvm::StringRef target_;
   llvm::raw_ostream* vlog_stream_;
 
   DiagnosticEmitter emitter_;

+ 13 - 9
toolchain/check/import_cpp.cpp

@@ -179,7 +179,8 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
 // TODO: Consider to always have a (non-null) AST.
 static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
                         llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
-                        llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
+                        llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                        llvm::StringRef target)
     -> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
   // TODO: Use all import locations by referring each Clang diagnostic to the
   // relevant import.
@@ -190,9 +191,12 @@ static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
   // TODO: Share compilation flags with ClangRunner.
   auto ast = clang::tooling::buildASTFromCodeWithArgs(
       GenerateCppIncludesHeaderCode(context, imports),
-      // Parse C++ (and not C)
-      {"-x", "c++"}, (importing_file_path + ".generated.cpp_imports.h").str(),
-      "clang-tool", std::make_shared<clang::PCHContainerOperations>(),
+      // Parse C++ (and not C).
+      {"-x", "c++",
+       // Propagate the target to Clang.
+       "-target", target.str()},
+      (importing_file_path + ".generated.cpp_imports.h").str(), "clang-tool",
+      std::make_shared<clang::PCHContainerOperations>(),
       clang::tooling::getClangStripDependencyFileAdjuster(),
       clang::tooling::FileContentMappings(), &diagnostics_consumer, fs);
   // Remove link to the diagnostics consumer before its deletion.
@@ -235,8 +239,8 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
 
 auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
                     llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
-                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
-    -> std::unique_ptr<clang::ASTUnit> {
+                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                    llvm::StringRef target) -> std::unique_ptr<clang::ASTUnit> {
   if (imports.empty()) {
     return nullptr;
   }
@@ -244,7 +248,7 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
   CARBON_CHECK(!context.sem_ir().cpp_ast());
 
   auto [generated_ast, ast_has_error] =
-      GenerateAst(context, importing_file_path, imports, fs);
+      GenerateAst(context, importing_file_path, imports, fs, target);
 
   PackageNameId package_id = imports.front().package_id;
   CARBON_CHECK(
@@ -422,7 +426,7 @@ static auto GetReturnType(Context& context, SemIR::LocId loc_id,
 static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
                                SemIR::NameScopeId scope_id,
                                SemIR::NameId name_id,
-                               const clang::FunctionDecl* clang_decl)
+                               clang::FunctionDecl* clang_decl)
     -> SemIR::InstId {
   if (clang_decl->isVariadic()) {
     context.TODO(loc_id, "Unsupported: Variadic function");
@@ -603,7 +607,7 @@ static auto ImportCXXRecordDecl(Context& context, SemIR::LocId loc_id,
 static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
                            SemIR::NameScopeId scope_id, SemIR::NameId name_id,
                            clang::NamedDecl* clang_decl) -> SemIR::InstId {
-  if (const auto* clang_function_decl =
+  if (auto* clang_function_decl =
           clang::dyn_cast<clang::FunctionDecl>(clang_decl)) {
     return ImportFunctionDecl(context, loc_id, scope_id, name_id,
                               clang_function_decl);

+ 2 - 2
toolchain/check/import_cpp.h

@@ -16,8 +16,8 @@ namespace Carbon::Check {
 // and warnings. If successful, adds a `Cpp` namespace and returns the AST.
 auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
                     llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
-                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
-    -> std::unique_ptr<clang::ASTUnit>;
+                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+                    llvm::StringRef target) -> std::unique_ptr<clang::ASTUnit>;
 
 // Looks up the given name in the Clang AST generated when importing C++ code.
 // If successful, generates the instruction and returns the new `InstId`.

+ 4 - 3
toolchain/driver/compile_subcommand.cpp

@@ -712,9 +712,9 @@ auto CompilationUnit::RunLower() -> void {
     if (options_->include_debug_info) {
       subtrees = cache_->tree_and_subtrees_getters();
     }
-    module_ =
-        Lower::LowerToLLVM(*llvm_context_, subtrees, input_filename_, *sem_ir_,
-                           sem_ir_->cpp_ast(), &inst_namer, vlog_stream_);
+    module_ = Lower::LowerToLLVM(*llvm_context_, driver_env_->fs, subtrees,
+                                 input_filename_, *sem_ir_, sem_ir_->cpp_ast(),
+                                 &inst_namer, vlog_stream_);
   });
   if (vlog_stream_) {
     CARBON_VLOG("*** llvm::Module ***\n");
@@ -964,6 +964,7 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
   CARBON_VLOG_TO(driver_env.vlog_stream, "*** Check::CheckParseTrees ***\n");
   Check::CheckParseTrees(check_units, cache.tree_and_subtrees_getters(),
                          options_.prelude_import, driver_env.fs,
+                         options_.codegen_options.target,
                          driver_env.vlog_stream, driver_env.fuzzing);
   CARBON_VLOG_TO(driver_env.vlog_stream,
                  "*** Check::CheckParseTrees done ***\n");

+ 1 - 0
toolchain/language_server/BUILD

@@ -45,6 +45,7 @@ cc_library(
         "//toolchain/sem_ir:file",
         "//toolchain/source:source_buffer",
         "@llvm-project//clang-tools-extra/clangd:ClangDaemon",
+        "@llvm-project//llvm:TargetParser",
     ],
 )
 

+ 3 - 1
toolchain/language_server/context.cpp

@@ -10,6 +10,7 @@
 
 #include "common/check.h"
 #include "common/raw_string_ostream.h"
+#include "llvm/TargetParser/Host.h"
 #include "toolchain/base/shared_value_stores.h"
 #include "toolchain/check/check.h"
 #include "toolchain/diagnostics/diagnostic.h"
@@ -152,7 +153,8 @@ auto Context::File::SetText(Context& context, std::optional<int64_t> version,
   // TODO: Include the prelude.
   Check::CheckParseTrees(
       units, llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>(getter),
-      /*prelude_import=*/false, fs, context.vlog_stream(), /*fuzzing=*/false);
+      /*prelude_import=*/false, fs, llvm::sys::getDefaultTargetTriple(),
+      context.vlog_stream(), /*fuzzing=*/false);
 
   // Note we need to publish diagnostics even when empty.
   // TODO: Consider caching previously published diagnostics and only publishing

+ 4 - 0
toolchain/lower/BUILD

@@ -57,7 +57,11 @@ cc_library(
         "//toolchain/sem_ir:inst_namer",
         "//toolchain/sem_ir:typed_insts",
         "@llvm-project//clang:ast",
+        "@llvm-project//clang:basic",
+        "@llvm-project//clang:codegen",
+        "@llvm-project//clang:lex",
         "@llvm-project//llvm:Core",
+        "@llvm-project//llvm:Linker",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:TransformUtils",
     ],

+ 59 - 1
toolchain/lower/file_context.cpp

@@ -9,10 +9,12 @@
 #include <string>
 #include <utility>
 
+#include "clang/CodeGen/ModuleBuilder.h"
 #include "common/check.h"
 #include "common/vlog.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/Sequence.h"
+#include "llvm/Linker/Linker.h"
 #include "llvm/Transforms/Utils/ModuleUtils.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/lower/constant.h"
@@ -33,6 +35,7 @@ namespace Carbon::Lower {
 
 FileContext::FileContext(
     llvm::LLVMContext& llvm_context,
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
     std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
         tree_and_subtrees_getters_for_debug_info,
     llvm::StringRef module_name, const SemIR::File& sem_ir,
@@ -40,6 +43,7 @@ FileContext::FileContext(
     llvm::raw_ostream* vlog_stream)
     : llvm_context_(&llvm_context),
       llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
+      fs_(std::move(fs)),
       di_builder_(*llvm_module_),
       di_compile_unit_(
           tree_and_subtrees_getters_for_debug_info
@@ -51,6 +55,8 @@ FileContext::FileContext(
       cpp_ast_(cpp_ast),
       inst_namer_(inst_namer),
       vlog_stream_(vlog_stream) {
+  // Initialization that relies on invariants of the class.
+  cpp_code_generator_ = CreateCppCodeGenerator();
   CARBON_CHECK(!sem_ir.has_errors(),
                "Generating LLVM IR from invalid SemIR::File is unsupported.");
 }
@@ -59,6 +65,10 @@ FileContext::FileContext(
 auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
   CARBON_CHECK(llvm_module_, "Run can only be called once.");
 
+  if (cpp_code_generator_) {
+    cpp_code_generator_->Initialize(cpp_ast()->getASTContext());
+  }
+
   // Lower all types that were required to be complete.
   types_.resize(sem_ir_->insts().size());
   for (auto type_id : sem_ir_->types().complete_types()) {
@@ -124,6 +134,15 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
                               /*Priority=*/0);
   }
 
+  if (cpp_code_generator_) {
+    cpp_code_generator_->HandleTranslationUnit(cpp_ast()->getASTContext());
+    bool link_error = llvm::Linker::linkModules(
+        /*Dest=*/*llvm_module_,
+        /*Src=*/std::unique_ptr<llvm::Module>(
+            cpp_code_generator_->ReleaseModule()));
+    CARBON_CHECK(!link_error);
+  }
+
   return std::move(llvm_module_);
 }
 
@@ -146,6 +165,21 @@ auto FileContext::BuildDICompileUnit(llvm::StringRef module_name,
                                       /*RV=*/0);
 }
 
+auto FileContext::CreateCppCodeGenerator()
+    -> std::unique_ptr<clang::CodeGenerator> {
+  if (!cpp_ast()) {
+    return nullptr;
+  }
+
+  RawStringOstream clang_module_name_stream;
+  clang_module_name_stream << llvm_module_->getName() << ".clang";
+
+  return std::unique_ptr<clang::CodeGenerator>(clang::CreateLLVMCodeGen(
+      cpp_ast()->getASTContext().getDiagnostics(),
+      clang_module_name_stream.TakeStr(), fs_, cpp_header_search_options_,
+      cpp_preprocessor_options_, cpp_code_gen_options_, *llvm_context_));
+}
+
 auto FileContext::GetGlobal(SemIR::InstId inst_id,
                             SemIR::SpecificId specific_id) -> llvm::Value* {
   auto const_id = GetConstantValueInSpecific(sem_ir(), specific_id, inst_id);
@@ -367,7 +401,8 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id,
     -> void {
   const auto& function = sem_ir().functions().Get(function_id);
   const auto& body_block_ids = function.body_block_ids;
-  if (body_block_ids.empty()) {
+  if (body_block_ids.empty() &&
+      (!function.cpp_decl || !function.cpp_decl->isDefined())) {
     // Function is probably defined in another file; not an error.
     return;
   }
@@ -396,6 +431,29 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
                                     SemIR::SpecificId specific_id) -> void {
   const auto& body_block_ids = function.body_block_ids;
   CARBON_DCHECK(llvm_function, "LLVM Function not found when lowering body.");
+
+  if (function.cpp_decl) {
+    // TODO: To support recursive inline functions, collect all calls to
+    // `HandleTopLevelDecl()` in a custom `ASTConsumer` configured in the
+    // `ASTUnit`, and replay them in lowering in the `CodeGenerator`. See
+    // https://discord.com/channels/655572317891461132/768530752592805919/1370509111585935443
+    clang::FunctionDecl* cpp_def = function.cpp_decl->getDefinition();
+    CARBON_DCHECK(cpp_def, "No Clang function body found during lowering");
+
+    // Create the LLVM function (`CodeGenModule::GetOrCreateLLVMFunction()`) so
+    // that code generation (`CodeGenModule::EmitGlobal()`) would see this
+    // function name (`CodeGenModule::getMangledName()`), and will generate its
+    // definition.
+    llvm::Constant* function_address =
+        cpp_code_generator_->GetAddrOfGlobal(clang::GlobalDecl(cpp_def),
+                                             /*isForDefinition=*/false);
+    CARBON_DCHECK(function_address);
+
+    // Emit the function code.
+    cpp_code_generator_->HandleTopLevelDecl(clang::DeclGroupRef(cpp_def));
+    return;
+  }
+
   CARBON_DCHECK(!body_block_ids.empty(),
                 "No function body blocks found during lowering.");
 

+ 21 - 0
toolchain/lower/file_context.h

@@ -5,6 +5,9 @@
 #ifndef CARBON_TOOLCHAIN_LOWER_FILE_CONTEXT_H_
 #define CARBON_TOOLCHAIN_LOWER_FILE_CONTEXT_H_
 
+#include "clang/Basic/CodeGenOptions.h"
+#include "clang/CodeGen/ModuleBuilder.h"
+#include "clang/Lex/PreprocessorOptions.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DIBuilder.h"
 #include "llvm/IR/LLVMContext.h"
@@ -30,6 +33,7 @@ class FileContext {
 
   explicit FileContext(
       llvm::LLVMContext& llvm_context,
+      llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
       std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
           tree_and_subtrees_getters_for_debug_info,
       llvm::StringRef module_name, const SemIR::File& sem_ir,
@@ -45,6 +49,10 @@ class FileContext {
                           llvm::Module& llvm_module,
                           llvm::DIBuilder& di_builder) -> llvm::DICompileUnit*;
 
+  // Creates the Clang `CodeGenerator` to generate LLVM module from imported C++
+  // code. Returns null when not importing C++.
+  auto CreateCppCodeGenerator() -> std::unique_ptr<clang::CodeGenerator>;
+
   // Gets a callable's function. Returns nullptr for a builtin.
   auto GetFunction(SemIR::FunctionId function_id) -> llvm::Function* {
     return functions_[function_id.index];
@@ -161,6 +169,9 @@ class FileContext {
   llvm::LLVMContext* llvm_context_;
   std::unique_ptr<llvm::Module> llvm_module_;
 
+  // The filesystem for source code.
+  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
+
   // State for building the LLVM IR debug info metadata.
   llvm::DIBuilder di_builder_;
 
@@ -178,6 +189,16 @@ class FileContext {
   // modifies it.
   clang::ASTUnit* cpp_ast_;
 
+  // The options used to create the Clang Code Generator.
+  clang::HeaderSearchOptions cpp_header_search_options_;
+  clang::PreprocessorOptions cpp_preprocessor_options_;
+  clang::CodeGenOptions cpp_code_gen_options_;
+
+  // The Clang `CodeGenerator` to generate LLVM module from imported C++
+  // code. Should be initialized using `CreateCppCodeGenerator()`. Can be null
+  // if no C++ code is imported.
+  std::unique_ptr<clang::CodeGenerator> cpp_code_generator_;
+
   // The instruction namer, if given.
   const SemIR::InstNamer* const inst_namer_;
 

+ 4 - 2
toolchain/lower/lower.cpp

@@ -12,14 +12,16 @@
 namespace Carbon::Lower {
 
 auto LowerToLLVM(llvm::LLVMContext& llvm_context,
+                 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
                  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
                      tree_and_subtrees_getters_for_debug_info,
                  llvm::StringRef module_name, const SemIR::File& sem_ir,
                  clang::ASTUnit* cpp_ast, const SemIR::InstNamer* inst_namer,
                  llvm::raw_ostream* vlog_stream)
     -> std::unique_ptr<llvm::Module> {
-  FileContext context(llvm_context, tree_and_subtrees_getters_for_debug_info,
-                      module_name, sem_ir, cpp_ast, inst_namer, vlog_stream);
+  FileContext context(llvm_context, std::move(fs),
+                      tree_and_subtrees_getters_for_debug_info, module_name,
+                      sem_ir, cpp_ast, inst_namer, vlog_stream);
   return context.Run();
 }
 

+ 1 - 0
toolchain/lower/lower.h

@@ -16,6 +16,7 @@ namespace Carbon::Lower {
 
 // Lowers SemIR to LLVM IR.
 auto LowerToLLVM(llvm::LLVMContext& llvm_context,
+                 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
                  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
                      tree_and_subtrees_getters_for_debug_info,
                  llvm::StringRef module_name, const SemIR::File& sem_ir,

+ 256 - 30
toolchain/lower/testdata/interop/cpp/function_decl.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
+// ARGS: compile --phase=lower --dump-llvm-ir  --exclude-dump-file-prefix=%{core_package_dir} --target=x86_64-unknown-linux-gnu %s
+//
 // 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
@@ -34,60 +36,284 @@ fn MyF() {
 
 inline void foo() {}
 
-// --- todo_import_inline_function_decl.carbon
+// --- import_inline_function_decl.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "inline_function_decl.h";
 
 fn MyF() {
-  // TODO: This should generate the definition of the inline function `foo()`.
   Cpp.foo();
 }
 
+// ============================================================================
+// inline_used_function_decl
+// ============================================================================
+
+// --- inline_used_function_decl.h
+
+inline __attribute__((used)) void foo() {}
+
+// --- import_inline_used_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "inline_used_function_decl.h";
+
+fn MyF() {
+  Cpp.foo();
+}
+
+// ============================================================================
+// multiple_inline_function_decls
+// ============================================================================
+
+// --- multiple_inline_function_decls.h
+
+inline void foo1() {}
+inline void foo2() {}
+inline void foo3() {}
+
+// --- import_multiple_inline_function_decls.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "multiple_inline_function_decls.h";
+
+fn MyF() {
+  Cpp.foo1();
+  Cpp.foo2();
+  Cpp.foo3();
+}
+
+// ============================================================================
+// inline_recursive_function_decl
+// ============================================================================
+
+// --- inline_recursive_function_decl.h
+
+inline void foo1() {}
+inline void foo2() { foo1(); }
+
+// --- todo_import_inline_recursive_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "inline_recursive_function_decl.h";
+
+fn MyF() {
+  // TODO: This should generate the definition of the inline function `foo1()`.
+  Cpp.foo2();
+}
+
 // CHECK:STDOUT: ; ModuleID = 'import_function_decl.carbon'
 // CHECK:STDOUT: source_filename = "import_function_decl.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: define void @_CMyF.Main() !dbg !4 {
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !8 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_Z3foov(), !dbg !7
-// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT:   call void @_Z3foov(), !dbg !11
+// CHECK:STDOUT:   ret void, !dbg !12
 // 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: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT: !llvm.ident = !{!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 = 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)
-// CHECK:STDOUT: ; ModuleID = 'todo_import_inline_function_decl.carbon'
-// CHECK:STDOUT: source_filename = "todo_import_inline_function_decl.carbon"
-// CHECK:STDOUT:
-// CHECK:STDOUT: define void @_CMyF.Main() !dbg !4 {
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_function_decl.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!"clang version 21.0.0git"}
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !9, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
+// CHECK:STDOUT: !10 = !{}
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !8)
+// CHECK:STDOUT: !12 = !DILocation(line: 6, column: 1, scope: !8)
+// CHECK:STDOUT: ; ModuleID = 'import_inline_function_decl.carbon'
+// CHECK:STDOUT: source_filename = "import_inline_function_decl.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: $_Z3foov = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !8 {
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_Z3foov(), !dbg !7
-// CHECK:STDOUT:   ret void, !dbg !8
+// CHECK:STDOUT:   call void @_Z3foov(), !dbg !11
+// CHECK:STDOUT:   ret void, !dbg !12
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_Z3foov()
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z3foov() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "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}
+// CHECK:STDOUT: !llvm.ident = !{!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 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_inline_function_decl.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!"clang version 21.0.0git"}
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !9, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
+// CHECK:STDOUT: !10 = !{}
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !8)
+// CHECK:STDOUT: !12 = !DILocation(line: 6, column: 1, scope: !8)
+// CHECK:STDOUT: ; ModuleID = 'import_inline_used_function_decl.carbon'
+// CHECK:STDOUT: source_filename = "import_inline_used_function_decl.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: $_Z3foov = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: @llvm.compiler.used = appending global [1 x ptr] [ptr @_Z3foov], section "llvm.metadata"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !8 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z3foov(), !dbg !11
+// CHECK:STDOUT:   ret void, !dbg !12
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z3foov() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @_Z3foov, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "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}
+// CHECK:STDOUT: !llvm.ident = !{!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 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_inline_used_function_decl.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!"clang version 21.0.0git"}
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !9, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
+// CHECK:STDOUT: !10 = !{}
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !8)
+// CHECK:STDOUT: !12 = !DILocation(line: 6, column: 1, scope: !8)
+// CHECK:STDOUT: ; ModuleID = 'import_multiple_inline_function_decls.carbon'
+// CHECK:STDOUT: source_filename = "import_multiple_inline_function_decls.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: $_Z4foo1v = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: $_Z4foo2v = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: $_Z4foo3v = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !8 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z4foo1v(), !dbg !11
+// CHECK:STDOUT:   call void @_Z4foo2v(), !dbg !12
+// CHECK:STDOUT:   call void @_Z4foo3v(), !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z4foo1v() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z4foo2v() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z4foo3v() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "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}
+// CHECK:STDOUT: !llvm.ident = !{!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 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_multiple_inline_function_decls.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!"clang version 21.0.0git"}
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !9, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
+// CHECK:STDOUT: !10 = !{}
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !8)
+// CHECK:STDOUT: !12 = !DILocation(line: 8, column: 3, scope: !8)
+// CHECK:STDOUT: !13 = !DILocation(line: 9, column: 3, scope: !8)
+// CHECK:STDOUT: !14 = !DILocation(line: 6, column: 1, scope: !8)
+// CHECK:STDOUT: ; ModuleID = 'todo_import_inline_recursive_function_decl.carbon'
+// CHECK:STDOUT: source_filename = "todo_import_inline_recursive_function_decl.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: $_Z4foo2v = comdat any
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CMyF.Main() !dbg !8 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z4foo2v(), !dbg !11
+// CHECK:STDOUT:   ret void, !dbg !12
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline optnone
+// CHECK:STDOUT: define linkonce_odr dso_local void @_Z4foo2v() #0 comdat {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z4foo1v()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_Z4foo1v() #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { mustprogress noinline optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "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"="0" "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}
-// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT: !llvm.ident = !{!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 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
-// CHECK:STDOUT: !3 = !DIFile(filename: "todo_import_inline_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: 8, column: 3, scope: !4)
-// CHECK:STDOUT: !8 = !DILocation(line: 6, column: 1, scope: !4)
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "todo_import_inline_recursive_function_decl.carbon", directory: "")
+// CHECK:STDOUT: !7 = !{!"clang version 21.0.0git"}
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "MyF", linkageName: "_CMyF.Main", scope: null, file: !6, line: 6, type: !9, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !9 = !DISubroutineType(types: !10)
+// CHECK:STDOUT: !10 = !{}
+// CHECK:STDOUT: !11 = !DILocation(line: 8, column: 3, scope: !8)
+// CHECK:STDOUT: !12 = !DILocation(line: 6, column: 1, scope: !8)

+ 4 - 3
toolchain/sem_ir/function.h

@@ -76,11 +76,12 @@ struct FunctionFields {
   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.
+  // the AST. Used for mangling and inline function definition code generation.
+  // 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;
+  clang::FunctionDecl* cpp_decl = nullptr;
 };
 
 // A function. See EntityWithParamsBase regarding the inheritance here.