Skip to content

Commit 852d019

Browse files
authored
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 #5406. Part of #5405.
1 parent 5b884ae commit 852d019

File tree

16 files changed

+386
-58
lines changed

16 files changed

+386
-58
lines changed

toolchain/check/check.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ auto CheckParseTrees(
326326
llvm::MutableArrayRef<Unit> units,
327327
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
328328
bool prelude_import, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
329-
llvm::raw_ostream* vlog_stream, bool fuzzing) -> void {
329+
llvm::StringRef target, llvm::raw_ostream* vlog_stream, bool fuzzing)
330+
-> void {
330331
// UnitAndImports is big due to its SmallVectors, so we default to 0 on the
331332
// stack.
332333
llvm::SmallVector<UnitAndImports, 0> unit_infos(
@@ -380,7 +381,8 @@ auto CheckParseTrees(
380381
for (int check_index = 0;
381382
check_index < static_cast<int>(ready_to_check.size()); ++check_index) {
382383
auto* unit_info = ready_to_check[check_index];
383-
CheckUnit(unit_info, tree_and_subtrees_getters, fs, vlog_stream).Run();
384+
CheckUnit(unit_info, tree_and_subtrees_getters, fs, target, vlog_stream)
385+
.Run();
384386
for (auto* incoming_import : unit_info->incoming_imports) {
385387
--incoming_import->imports_remaining;
386388
if (incoming_import->imports_remaining == 0) {
@@ -427,7 +429,9 @@ auto CheckParseTrees(
427429
// incomplete imports.
428430
for (auto& unit_info : unit_infos) {
429431
if (unit_info.imports_remaining > 0) {
430-
CheckUnit(&unit_info, tree_and_subtrees_getters, fs, vlog_stream).Run();
432+
CheckUnit(&unit_info, tree_and_subtrees_getters, fs, target,
433+
vlog_stream)
434+
.Run();
431435
}
432436
}
433437
}

toolchain/check/check.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ auto CheckParseTrees(
3535
llvm::MutableArrayRef<Unit> units,
3636
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
3737
bool prelude_import, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
38-
llvm::raw_ostream* vlog_stream, bool fuzzing) -> void;
38+
llvm::StringRef target, llvm::raw_ostream* vlog_stream, bool fuzzing)
39+
-> void;
3940

4041
} // namespace Carbon::Check
4142

toolchain/check/check_unit.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int {
5353
CheckUnit::CheckUnit(
5454
UnitAndImports* unit_and_imports,
5555
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
56-
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
56+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, llvm::StringRef target,
5757
llvm::raw_ostream* vlog_stream)
5858
: unit_and_imports_(unit_and_imports),
5959
tree_and_subtrees_getter_(
6060
tree_and_subtrees_getters
6161
[unit_and_imports->unit->sem_ir->check_ir_id().index]),
6262
total_ir_count_(tree_and_subtrees_getters.size()),
6363
fs_(std::move(fs)),
64+
target_(target),
6465
vlog_stream_(vlog_stream),
6566
emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters,
6667
unit_and_imports_->unit->sem_ir),
@@ -154,7 +155,7 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
154155
CARBON_CHECK(!cpp_ast->get());
155156
*cpp_ast =
156157
ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
157-
cpp_import_names, fs_);
158+
cpp_import_names, fs_, target_);
158159
}
159160
}
160161

toolchain/check/check_unit.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class CheckUnit {
124124
UnitAndImports* unit_and_imports,
125125
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
126126
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
127-
llvm::raw_ostream* vlog_stream);
127+
llvm::StringRef target, llvm::raw_ostream* vlog_stream);
128128

129129
// Produces and checks the IR for the provided unit.
130130
auto Run() -> void;
@@ -191,6 +191,7 @@ class CheckUnit {
191191
// The number of IRs being checked in total.
192192
int total_ir_count_;
193193
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
194+
llvm::StringRef target_;
194195
llvm::raw_ostream* vlog_stream_;
195196

196197
DiagnosticEmitter emitter_;

toolchain/check/import_cpp.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
179179
// TODO: Consider to always have a (non-null) AST.
180180
static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
181181
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
182-
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
182+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
183+
llvm::StringRef target)
183184
-> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
184185
// TODO: Use all import locations by referring each Clang diagnostic to the
185186
// relevant import.
@@ -190,9 +191,12 @@ static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
190191
// TODO: Share compilation flags with ClangRunner.
191192
auto ast = clang::tooling::buildASTFromCodeWithArgs(
192193
GenerateCppIncludesHeaderCode(context, imports),
193-
// Parse C++ (and not C)
194-
{"-x", "c++"}, (importing_file_path + ".generated.cpp_imports.h").str(),
195-
"clang-tool", std::make_shared<clang::PCHContainerOperations>(),
194+
// Parse C++ (and not C).
195+
{"-x", "c++",
196+
// Propagate the target to Clang.
197+
"-target", target.str()},
198+
(importing_file_path + ".generated.cpp_imports.h").str(), "clang-tool",
199+
std::make_shared<clang::PCHContainerOperations>(),
196200
clang::tooling::getClangStripDependencyFileAdjuster(),
197201
clang::tooling::FileContentMappings(), &diagnostics_consumer, fs);
198202
// Remove link to the diagnostics consumer before its deletion.
@@ -235,16 +239,16 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
235239

236240
auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
237241
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
238-
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
239-
-> std::unique_ptr<clang::ASTUnit> {
242+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
243+
llvm::StringRef target) -> std::unique_ptr<clang::ASTUnit> {
240244
if (imports.empty()) {
241245
return nullptr;
242246
}
243247

244248
CARBON_CHECK(!context.sem_ir().cpp_ast());
245249

246250
auto [generated_ast, ast_has_error] =
247-
GenerateAst(context, importing_file_path, imports, fs);
251+
GenerateAst(context, importing_file_path, imports, fs, target);
248252

249253
PackageNameId package_id = imports.front().package_id;
250254
CARBON_CHECK(
@@ -422,7 +426,7 @@ static auto GetReturnType(Context& context, SemIR::LocId loc_id,
422426
static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
423427
SemIR::NameScopeId scope_id,
424428
SemIR::NameId name_id,
425-
const clang::FunctionDecl* clang_decl)
429+
clang::FunctionDecl* clang_decl)
426430
-> SemIR::InstId {
427431
if (clang_decl->isVariadic()) {
428432
context.TODO(loc_id, "Unsupported: Variadic function");
@@ -603,7 +607,7 @@ static auto ImportCXXRecordDecl(Context& context, SemIR::LocId loc_id,
603607
static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
604608
SemIR::NameScopeId scope_id, SemIR::NameId name_id,
605609
clang::NamedDecl* clang_decl) -> SemIR::InstId {
606-
if (const auto* clang_function_decl =
610+
if (auto* clang_function_decl =
607611
clang::dyn_cast<clang::FunctionDecl>(clang_decl)) {
608612
return ImportFunctionDecl(context, loc_id, scope_id, name_id,
609613
clang_function_decl);

toolchain/check/import_cpp.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ namespace Carbon::Check {
1616
// and warnings. If successful, adds a `Cpp` namespace and returns the AST.
1717
auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
1818
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
19-
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
20-
-> std::unique_ptr<clang::ASTUnit>;
19+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
20+
llvm::StringRef target) -> std::unique_ptr<clang::ASTUnit>;
2121

2222
// Looks up the given name in the Clang AST generated when importing C++ code.
2323
// If successful, generates the instruction and returns the new `InstId`.

toolchain/driver/compile_subcommand.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -712,9 +712,9 @@ auto CompilationUnit::RunLower() -> void {
712712
if (options_->include_debug_info) {
713713
subtrees = cache_->tree_and_subtrees_getters();
714714
}
715-
module_ =
716-
Lower::LowerToLLVM(*llvm_context_, subtrees, input_filename_, *sem_ir_,
717-
sem_ir_->cpp_ast(), &inst_namer, vlog_stream_);
715+
module_ = Lower::LowerToLLVM(*llvm_context_, driver_env_->fs, subtrees,
716+
input_filename_, *sem_ir_, sem_ir_->cpp_ast(),
717+
&inst_namer, vlog_stream_);
718718
});
719719
if (vlog_stream_) {
720720
CARBON_VLOG("*** llvm::Module ***\n");
@@ -964,6 +964,7 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
964964
CARBON_VLOG_TO(driver_env.vlog_stream, "*** Check::CheckParseTrees ***\n");
965965
Check::CheckParseTrees(check_units, cache.tree_and_subtrees_getters(),
966966
options_.prelude_import, driver_env.fs,
967+
options_.codegen_options.target,
967968
driver_env.vlog_stream, driver_env.fuzzing);
968969
CARBON_VLOG_TO(driver_env.vlog_stream,
969970
"*** Check::CheckParseTrees done ***\n");

toolchain/language_server/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ cc_library(
4545
"//toolchain/sem_ir:file",
4646
"//toolchain/source:source_buffer",
4747
"@llvm-project//clang-tools-extra/clangd:ClangDaemon",
48+
"@llvm-project//llvm:TargetParser",
4849
],
4950
)
5051

toolchain/language_server/context.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "common/check.h"
1212
#include "common/raw_string_ostream.h"
13+
#include "llvm/TargetParser/Host.h"
1314
#include "toolchain/base/shared_value_stores.h"
1415
#include "toolchain/check/check.h"
1516
#include "toolchain/diagnostics/diagnostic.h"
@@ -152,7 +153,8 @@ auto Context::File::SetText(Context& context, std::optional<int64_t> version,
152153
// TODO: Include the prelude.
153154
Check::CheckParseTrees(
154155
units, llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>(getter),
155-
/*prelude_import=*/false, fs, context.vlog_stream(), /*fuzzing=*/false);
156+
/*prelude_import=*/false, fs, llvm::sys::getDefaultTargetTriple(),
157+
context.vlog_stream(), /*fuzzing=*/false);
156158

157159
// Note we need to publish diagnostics even when empty.
158160
// TODO: Consider caching previously published diagnostics and only publishing

toolchain/lower/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ cc_library(
5757
"//toolchain/sem_ir:inst_namer",
5858
"//toolchain/sem_ir:typed_insts",
5959
"@llvm-project//clang:ast",
60+
"@llvm-project//clang:basic",
61+
"@llvm-project//clang:codegen",
62+
"@llvm-project//clang:lex",
6063
"@llvm-project//llvm:Core",
64+
"@llvm-project//llvm:Linker",
6165
"@llvm-project//llvm:Support",
6266
"@llvm-project//llvm:TransformUtils",
6367
],

toolchain/lower/file_context.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
#include <string>
1010
#include <utility>
1111

12+
#include "clang/CodeGen/ModuleBuilder.h"
1213
#include "common/check.h"
1314
#include "common/vlog.h"
1415
#include "llvm/ADT/STLExtras.h"
1516
#include "llvm/ADT/Sequence.h"
17+
#include "llvm/Linker/Linker.h"
1618
#include "llvm/Transforms/Utils/ModuleUtils.h"
1719
#include "toolchain/base/kind_switch.h"
1820
#include "toolchain/lower/constant.h"
@@ -33,13 +35,15 @@ namespace Carbon::Lower {
3335

3436
FileContext::FileContext(
3537
llvm::LLVMContext& llvm_context,
38+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
3639
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
3740
tree_and_subtrees_getters_for_debug_info,
3841
llvm::StringRef module_name, const SemIR::File& sem_ir,
3942
clang::ASTUnit* cpp_ast, const SemIR::InstNamer* inst_namer,
4043
llvm::raw_ostream* vlog_stream)
4144
: llvm_context_(&llvm_context),
4245
llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
46+
fs_(std::move(fs)),
4347
di_builder_(*llvm_module_),
4448
di_compile_unit_(
4549
tree_and_subtrees_getters_for_debug_info
@@ -51,6 +55,8 @@ FileContext::FileContext(
5155
cpp_ast_(cpp_ast),
5256
inst_namer_(inst_namer),
5357
vlog_stream_(vlog_stream) {
58+
// Initialization that relies on invariants of the class.
59+
cpp_code_generator_ = CreateCppCodeGenerator();
5460
CARBON_CHECK(!sem_ir.has_errors(),
5561
"Generating LLVM IR from invalid SemIR::File is unsupported.");
5662
}
@@ -59,6 +65,10 @@ FileContext::FileContext(
5965
auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
6066
CARBON_CHECK(llvm_module_, "Run can only be called once.");
6167

68+
if (cpp_code_generator_) {
69+
cpp_code_generator_->Initialize(cpp_ast()->getASTContext());
70+
}
71+
6272
// Lower all types that were required to be complete.
6373
types_.resize(sem_ir_->insts().size());
6474
for (auto type_id : sem_ir_->types().complete_types()) {
@@ -124,6 +134,15 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
124134
/*Priority=*/0);
125135
}
126136

137+
if (cpp_code_generator_) {
138+
cpp_code_generator_->HandleTranslationUnit(cpp_ast()->getASTContext());
139+
bool link_error = llvm::Linker::linkModules(
140+
/*Dest=*/*llvm_module_,
141+
/*Src=*/std::unique_ptr<llvm::Module>(
142+
cpp_code_generator_->ReleaseModule()));
143+
CARBON_CHECK(!link_error);
144+
}
145+
127146
return std::move(llvm_module_);
128147
}
129148

@@ -146,6 +165,21 @@ auto FileContext::BuildDICompileUnit(llvm::StringRef module_name,
146165
/*RV=*/0);
147166
}
148167

168+
auto FileContext::CreateCppCodeGenerator()
169+
-> std::unique_ptr<clang::CodeGenerator> {
170+
if (!cpp_ast()) {
171+
return nullptr;
172+
}
173+
174+
RawStringOstream clang_module_name_stream;
175+
clang_module_name_stream << llvm_module_->getName() << ".clang";
176+
177+
return std::unique_ptr<clang::CodeGenerator>(clang::CreateLLVMCodeGen(
178+
cpp_ast()->getASTContext().getDiagnostics(),
179+
clang_module_name_stream.TakeStr(), fs_, cpp_header_search_options_,
180+
cpp_preprocessor_options_, cpp_code_gen_options_, *llvm_context_));
181+
}
182+
149183
auto FileContext::GetGlobal(SemIR::InstId inst_id,
150184
SemIR::SpecificId specific_id) -> llvm::Value* {
151185
auto const_id = GetConstantValueInSpecific(sem_ir(), specific_id, inst_id);
@@ -367,7 +401,8 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id,
367401
-> void {
368402
const auto& function = sem_ir().functions().Get(function_id);
369403
const auto& body_block_ids = function.body_block_ids;
370-
if (body_block_ids.empty()) {
404+
if (body_block_ids.empty() &&
405+
(!function.cpp_decl || !function.cpp_decl->isDefined())) {
371406
// Function is probably defined in another file; not an error.
372407
return;
373408
}
@@ -396,6 +431,29 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
396431
SemIR::SpecificId specific_id) -> void {
397432
const auto& body_block_ids = function.body_block_ids;
398433
CARBON_DCHECK(llvm_function, "LLVM Function not found when lowering body.");
434+
435+
if (function.cpp_decl) {
436+
// TODO: To support recursive inline functions, collect all calls to
437+
// `HandleTopLevelDecl()` in a custom `ASTConsumer` configured in the
438+
// `ASTUnit`, and replay them in lowering in the `CodeGenerator`. See
439+
// https://discord.com/channels/655572317891461132/768530752592805919/1370509111585935443
440+
clang::FunctionDecl* cpp_def = function.cpp_decl->getDefinition();
441+
CARBON_DCHECK(cpp_def, "No Clang function body found during lowering");
442+
443+
// Create the LLVM function (`CodeGenModule::GetOrCreateLLVMFunction()`) so
444+
// that code generation (`CodeGenModule::EmitGlobal()`) would see this
445+
// function name (`CodeGenModule::getMangledName()`), and will generate its
446+
// definition.
447+
llvm::Constant* function_address =
448+
cpp_code_generator_->GetAddrOfGlobal(clang::GlobalDecl(cpp_def),
449+
/*isForDefinition=*/false);
450+
CARBON_DCHECK(function_address);
451+
452+
// Emit the function code.
453+
cpp_code_generator_->HandleTopLevelDecl(clang::DeclGroupRef(cpp_def));
454+
return;
455+
}
456+
399457
CARBON_DCHECK(!body_block_ids.empty(),
400458
"No function body blocks found during lowering.");
401459

0 commit comments

Comments
 (0)