From 77808cd5d7de5da0046c7894acab542cec402cc3 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Mon, 17 Nov 2025 22:08:14 -0800 Subject: [PATCH] Refactor Clang runtimes building into async builder (#6380) Previously, the Clang runtimes building only considered building the target resource directory, and was only _internally_ asynchronous. Because the asynchrony was only internal, it could use the function frame as a context object throughout the build of the resource dir. This is simple but doesn't generalize well to more runtimes: if we want to add 2 or 3 more runtimes, we want them to _all_ build asynchronously. That means using some asynchronous builder that maintains the context and allows them to proceed concurrently with other work. This also factors all the runtimes building code into a separate set of files. These aren't separate libraries at this point due to the `ClangRunner` in some cases wanting to build runtimes on-demand, but it at least lets us organize the code more cleanly. Because this splits code between `clang_runner.*` and `clang_runtimes.*`, it also works to update the `#include`s for both to be roughly accurate. I used ClangD's include cleaner for this and it probably also did some latent cleaning as it went, but that's the reason for the churn of `#include` lines. The archive building is also factored out into a re-usable helper. This is a bit "over factored" in this PR, but supports the next PR that uses the same code to build archives for other runtimes. This also overhauls the synchronization used -- it uses a simple `Latch` construct introduced in a previous PR to coordinate between the steps of building the runtimes. Last but not least, it factors the "enable leaking" state out of a boolean in the runner to a parameter. This is important in the face of concurrent calls as otherwise toggling this boolean can create a race. The next PR will layer building more runtimes on top of this new factoring. --------- Co-authored-by: David Blaikie --- toolchain/driver/BUILD | 40 +- .../driver/build_runtimes_subcommand.cpp | 12 +- toolchain/driver/clang_runner.cpp | 350 ++++----------- toolchain/driver/clang_runner.h | 61 +-- toolchain/driver/clang_runner_test.cpp | 91 +--- toolchain/driver/clang_runtimes.cpp | 403 ++++++++++++++++++ toolchain/driver/clang_runtimes.h | 259 +++++++++++ toolchain/driver/clang_runtimes_test.cpp | 153 +++++++ toolchain/driver/clang_subcommand.cpp | 13 +- 9 files changed, 960 insertions(+), 422 deletions(-) create mode 100644 toolchain/driver/clang_runtimes.cpp create mode 100644 toolchain/driver/clang_runtimes.h create mode 100644 toolchain/driver/clang_runtimes_test.cpp diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index ff22e3c9b0fd..0d10a50d4bee 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -19,14 +19,22 @@ filegroup( cc_library( name = "clang_runner", - srcs = ["clang_runner.cpp"], - hdrs = ["clang_runner.h"], + srcs = [ + "clang_runner.cpp", + "clang_runtimes.cpp", + ], + hdrs = [ + "clang_runner.h", + "clang_runtimes.h", + ], deps = [ ":llvm_runner", ":runtimes_cache", ":tool_runner_base", + "//common:check", "//common:error", "//common:filesystem", + "//common:latch", "//common:ostream", "//common:vlog", "//toolchain/base:runtime_sources", @@ -53,6 +61,7 @@ cc_test( deps = [ ":clang_runner", ":llvm_runner", + ":runtimes_cache", "//common:all_llvm_targets", "//common:check", "//common:ostream", @@ -61,6 +70,33 @@ cc_test( "//testing/base:file_helpers", "//testing/base:global_exe_path", "//testing/base:gtest_main", + "//toolchain/install:install_paths", + "@googletest//:gtest", + "@llvm-project//llvm:Object", + "@llvm-project//llvm:Support", + "@llvm-project//llvm:TargetParser", + ], +) + +cc_test( + name = "clang_runtimes_test", + size = "small", + srcs = ["clang_runtimes_test.cpp"], + data = ["//toolchain/install:install_data"], + deps = [ + ":clang_runner", + ":llvm_runner", + ":runtimes_cache", + "//common:all_llvm_targets", + "//common:check", + "//common:ostream", + "//common:raw_string_ostream", + "//testing/base:capture_std_streams", + "//testing/base:file_helpers", + "//testing/base:global_exe_path", + "//testing/base:gtest_main", + "//toolchain/base:llvm_tools", + "//toolchain/install:install_paths", "@googletest//:gtest", "@llvm-project//llvm:Object", "@llvm-project//llvm:Support", diff --git a/toolchain/driver/build_runtimes_subcommand.cpp b/toolchain/driver/build_runtimes_subcommand.cpp index d186848b5ab3..af3dd2d33b6d 100644 --- a/toolchain/driver/build_runtimes_subcommand.cpp +++ b/toolchain/driver/build_runtimes_subcommand.cpp @@ -4,10 +4,9 @@ #include "toolchain/driver/build_runtimes_subcommand.h" -#include - #include "llvm/TargetParser/Triple.h" #include "toolchain/driver/clang_runner.h" +#include "toolchain/driver/clang_runtimes.h" namespace Carbon { @@ -92,8 +91,13 @@ auto BuildRuntimesSubcommand::RunInternal(DriverEnv& driver_env) : Runtimes::Make(explicit_output_path, driver_env.vlog_stream)); CARBON_ASSIGN_OR_RETURN(auto tmp_dir, Filesystem::MakeTmpDir()); - return runner.BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path(), - *driver_env.thread_pool); + ClangResourceDirBuilder resource_dir_builder(&runner, driver_env.thread_pool, + llvm::Triple(features.target), + &runtimes); + + CARBON_RETURN_IF_ERROR(std::move(resource_dir_builder).Wait()); + + return runtimes.base_path(); } } // namespace Carbon diff --git a/toolchain/driver/clang_runner.cpp b/toolchain/driver/clang_runner.cpp index 472ec9a1e099..85dd132649e3 100644 --- a/toolchain/driver/clang_runner.cpp +++ b/toolchain/driver/clang_runner.cpp @@ -4,19 +4,18 @@ #include "toolchain/driver/clang_runner.h" +#include #include -#include #include #include -#include #include #include -#include #include -#include #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticDriver.h" +#include "clang/Basic/DiagnosticIDs.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/CodeGen/ObjectFilePCHContainerWriter.h" #include "clang/Driver/Compilation.h" @@ -28,25 +27,42 @@ #include "clang/Frontend/Utils.h" #include "clang/FrontendTool/Utils.h" #include "clang/Serialization/ObjectFilePCHContainerReader.h" -#include "common/filesystem.h" +#include "clang/Serialization/PCHContainerOperations.h" +#include "common/check.h" +#include "common/error.h" #include "common/vlog.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/LLVMContext.h" -#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/BuryPointer.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/FormatAdapters.h" #include "llvm/Support/LLVMDriver.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Program.h" +#include "llvm/Support/ThreadPool.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/Timer.h" +#include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Host.h" -#include "toolchain/base/runtime_sources.h" +#include "toolchain/driver/clang_runtimes.h" +#include "toolchain/driver/runtimes_cache.h" +#include "toolchain/driver/tool_runner_base.h" +#include "toolchain/install/install_paths.h" + +// Defined in: +// https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/driver.cpp +// +// While not in a header, this is the API used by llvm-driver.cpp for +// busyboxing. +// +// NOLINTNEXTLINE(readability-identifier-naming) +auto clang_main(int Argc, char** Argv, const llvm::ToolContext& ToolContext) + -> int; namespace Carbon { @@ -127,33 +143,35 @@ static auto IsNonLinkCommand(llvm::ArrayRef args) -> bool { } auto ClangRunner::RunWithPrebuiltRuntimes(llvm::ArrayRef args, - Runtimes& prebuilt_runtimes) + Runtimes& prebuilt_runtimes, + bool enable_leaking) -> ErrorOr { // Check the args to see if we have a known target-independent command. If so, // directly dispatch it to avoid the cost of building the target resource // directory. // TODO: Maybe handle response file expansion similar to the Clang CLI? if (args.empty() || args[0].starts_with("-cc1") || IsNonLinkCommand(args)) { - return RunWithNoRuntimes(args); + return RunWithNoRuntimes(args, enable_leaking); } std::string target = ComputeClangTarget(args); CARBON_ASSIGN_OR_RETURN(std::filesystem::path prebuilt_resource_dir_path, prebuilt_runtimes.Get(Runtimes::ClangResourceDir)); - return RunInternal(args, target, prebuilt_resource_dir_path.native()); + return RunInternal(args, target, prebuilt_resource_dir_path.native(), + enable_leaking); } auto ClangRunner::Run(llvm::ArrayRef args, Runtimes::Cache& runtimes_cache, - llvm::ThreadPoolInterface& runtimes_build_thread_pool) - -> ErrorOr { + llvm::ThreadPoolInterface& runtimes_build_thread_pool, + bool enable_leaking) -> ErrorOr { // Check the args to see if we have a known target-independent command. If so, // directly dispatch it to avoid the cost of building the target resource // directory. // TODO: Maybe handle response file expansion similar to the Clang CLI? if (args.empty() || args[0].starts_with("-cc1") || IsNonLinkCommand(args)) { - return RunWithNoRuntimes(args); + return RunWithNoRuntimes(args, enable_leaking); } std::string target = ComputeClangTarget(args); @@ -168,95 +186,26 @@ auto ClangRunner::Run(llvm::ArrayRef args, // not once we are running Clang with the built runtime. std::filesystem::path resource_dir_path; { - // Build the temporary directory and threadpool needed. - CARBON_ASSIGN_OR_RETURN(Filesystem::RemovingDir tmp_dir, - Filesystem::MakeTmpDir()); - - CARBON_ASSIGN_OR_RETURN( - resource_dir_path, - BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path(), - runtimes_build_thread_pool)); + ClangResourceDirBuilder builder(this, &runtimes_build_thread_pool, + llvm::Triple(features.target), &runtimes); + CARBON_ASSIGN_OR_RETURN(resource_dir_path, std::move(builder).Wait()); } // Note that this function always successfully runs `clang` and returns a bool // to indicate whether `clang` itself succeeded, not whether the runner was // able to run it. As a consequence, even a `false` here is a non-`Error` // return. - return RunInternal(args, target, resource_dir_path.native()); + return RunInternal(args, target, resource_dir_path.native(), enable_leaking); } -auto ClangRunner::RunWithNoRuntimes(llvm::ArrayRef args) - -> bool { +auto ClangRunner::RunWithNoRuntimes(llvm::ArrayRef args, + bool enable_leaking) -> bool { std::string target = ComputeClangTarget(args); - return RunInternal(args, target, std::nullopt); + return RunInternal(args, target, std::nullopt, enable_leaking); } -auto ClangRunner::BuildTargetResourceDir( - const Runtimes::Cache::Features& features, Runtimes& runtimes, - const std::filesystem::path& tmp_path, llvm::ThreadPoolInterface& threads) - -> ErrorOr { - // Disable any leaking of memory while building the target resource dir, and - // restore the previous setting at the end. - auto restore_leak_flag = llvm::make_scope_exit( - [&, orig_flag = enable_leaking_] { enable_leaking_ = orig_flag; }); - enable_leaking_ = false; - - CARBON_ASSIGN_OR_RETURN(auto build_dir, - runtimes.Build(Runtimes::ClangResourceDir)); - if (std::holds_alternative(build_dir)) { - // Found cached build. - return std::get(std::move(build_dir)); - } - - auto builder = std::get(std::move(build_dir)); - std::string target = features.target; - - // Symlink the installation's `include` and `share` directories. - std::filesystem::path install_resource_path = - installation_->clang_resource_path(); - CARBON_RETURN_IF_ERROR( - builder.dir().Symlink("include", install_resource_path / "include")); - CARBON_RETURN_IF_ERROR( - builder.dir().Symlink("share", install_resource_path / "share")); - - // Create the target's `lib` directory. - std::filesystem::path lib_path = - std::filesystem::path("lib") / std::string_view(target); - CARBON_ASSIGN_OR_RETURN(Filesystem::Dir lib_dir, - builder.dir().CreateDirectories(lib_path)); - - llvm::Triple target_triple(target); - if (target_triple.isOSWindows()) { - return Error("TODO: Windows runtimes are untested and not yet supported."); - } - - llvm::ThreadPoolTaskGroup task_group(threads); - - // For Linux targets, the system libc (typically glibc) doesn't necessarily - // provide the CRT begin/end files, and so we need to build them. - if (target_triple.isOSLinux()) { - task_group.async( - [this, target, - path = builder.path() / lib_path / "clang_rt.crtbegin.o"] { - BuildCrtFile(target, RuntimeSources::CrtBegin, path); - }); - task_group.async( - [this, target, path = builder.path() / lib_path / "clang_rt.crtend.o"] { - BuildCrtFile(target, RuntimeSources::CrtEnd, path); - }); - } - - CARBON_RETURN_IF_ERROR( - BuildBuiltinsLib(target, target_triple, tmp_path, lib_dir, threads)); - - // Now wait for all the queued builds to complete before we commit the - // runtimes into the cache. - task_group.wait(); - - return std::move(builder).Commit(); -} - -auto ClangRunner::RunCC1(llvm::SmallVectorImpl& cc1_args) -> int { +auto ClangRunner::RunCC1(llvm::SmallVectorImpl& cc1_args, + bool enable_leaking) -> int { llvm::BumpPtrAllocator allocator; llvm::cl::ExpansionContext expansion_context( allocator, llvm::cl::TokenizeGNUCommandLine); @@ -300,7 +249,7 @@ auto ClangRunner::RunCC1(llvm::SmallVectorImpl& cc1_args) -> int { std::move(invocation), std::move(pch_ops)); // Override the disabling of free when we don't want to leak memory. - if (!enable_leaking_) { + if (!enable_leaking) { clang_instance->getFrontendOpts().DisableFree = false; clang_instance->getCodeGenOpts().DisableFree = false; } @@ -400,7 +349,8 @@ auto ClangRunner::RunCC1(llvm::SmallVectorImpl& cc1_args) -> int { auto ClangRunner::RunInternal( llvm::ArrayRef args, llvm::StringRef target, - std::optional target_resource_dir_path) -> bool { + std::optional target_resource_dir_path, + bool enable_leaking) -> bool { std::string clang_path = installation_->clang_path(); // Rebuild the args as C-string args. @@ -410,8 +360,27 @@ auto ClangRunner::RunInternal( // Handle special dispatch for CC1 commands as they don't use the driver. if (!args.empty() && args[0].starts_with("-cc1")) { - CARBON_VLOG("Calling Clang's CC1..."); - int exit_code = RunCC1(cstr_args); + if (args[0] == "-cc1") { + CARBON_VLOG("Dispatching `-cc1` command line"); + int exit_code = RunCC1(cstr_args, enable_leaking); + // TODO: Should this be forwarding the full exit code? + return exit_code == 0; + } + + // Other CC1-based invocations need to dispatch into the `clang_main` + // routine to work correctly. This means they're not reliable in a library + // context but currently there is too much logic to reasonably extract here. + // This at least allows simple cases (often when directly used on the + // command line) to work correctly. + // + // TODO: Factor the relevant code paths into a library API or move this into + // the busybox dispatch logic. + CARBON_VLOG("Calling clang_main for a cc1-based invocation..."); + // cstr_args[0] will be the `clang_path` so we don't need the prepend arg. + llvm::ToolContext tool_context = { + .Path = cstr_args[0], .PrependArg = "clang", .NeedsPrependArg = false}; + int exit_code = clang_main( + cstr_args.size(), const_cast(cstr_args.data()), tool_context); // TODO: Should this be forwarding the full exit code? return exit_code == 0; } @@ -481,8 +450,9 @@ auto ClangRunner::RunInternal( // // Also note that we only do `-disable-free` filtering in the in-process // execution here, as subprocesses leaking memory won't impact this process. - auto cc1_main = [this](llvm::SmallVectorImpl& cc1_args) -> int { - return RunCC1(cc1_args); + auto cc1_main = [this, enable_leaking]( + llvm::SmallVectorImpl& cc1_args) -> int { + return RunCC1(cc1_args, enable_leaking); }; driver.CC1Main = cc1_main; @@ -525,180 +495,4 @@ auto ClangRunner::RunInternal( return result == 0 && failing_commands.empty(); } -auto ClangRunner::BuildCrtFile(llvm::StringRef target, llvm::StringRef src_file, - const std::filesystem::path& out_path) -> void { - std::filesystem::path src_path = - installation_->llvm_runtime_srcs() / std::string_view(src_file); - CARBON_VLOG("Building `{0}' from `{1}`...\n", out_path, src_path); - - std::string target_arg = llvm::formatv("--target={0}", target).str(); - CARBON_CHECK(RunWithNoRuntimes({ - "-no-canonical-prefixes", - target_arg, - "-DCRT_HAS_INITFINI_ARRAY", - "-DEH_USE_FRAME_REGISTRY", - "-O3", - "-fPIC", - "-ffreestanding", - "-std=c11", - "-w", - "-c", - "-o", - out_path.native(), - src_path.native(), - })); -} - -auto ClangRunner::CollectBuiltinsSrcFiles(const llvm::Triple& target_triple) - -> llvm::SmallVector { - llvm::SmallVector src_files; - auto append_src_files = - [&](auto input_srcs, - llvm::function_ref filter_out = {}) { - for (llvm::StringRef input_src : input_srcs) { - if (!input_src.ends_with(".c") && !input_src.ends_with(".S")) { - // Not a compiled file. - continue; - } - if (filter_out && filter_out(input_src)) { - // Filtered out. - continue; - } - - src_files.push_back(input_src); - } - }; - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsGenericSrcs)); - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsBf16Srcs)); - if (target_triple.isArch64Bit()) { - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsTfSrcs)); - } - auto filter_out_chkstk = [&](llvm::StringRef src) { - return !target_triple.isOSWindows() || !src.ends_with("chkstk.S"); - }; - if (target_triple.isAArch64()) { - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsAarch64Srcs), - filter_out_chkstk); - } else if (target_triple.isX86()) { - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsX86ArchSrcs)); - if (target_triple.isArch64Bit()) { - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsX86_64Srcs), - filter_out_chkstk); - } else { - // TODO: This should be turned into a nice user-facing diagnostic about an - // unsupported target. - CARBON_CHECK( - target_triple.isArch32Bit(), - "The Carbon toolchain doesn't currently support 16-bit x86."); - append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsI386Srcs), - filter_out_chkstk); - } - } else { - // TODO: This should be turned into a nice user-facing diagnostic about an - // unsupported target. - CARBON_FATAL("Target architecture is not supported: {0}", - target_triple.str()); - } - return src_files; -} - -auto ClangRunner::BuildBuiltinsFile(llvm::StringRef target, - llvm::StringRef src_file, - const std::filesystem::path& out_path) - -> void { - std::filesystem::path src_path = - installation_->llvm_runtime_srcs() / std::string_view(src_file); - CARBON_VLOG("Building `{0}' from `{1}`...\n", out_path, src_path); - - std::string target_arg = llvm::formatv("--target={0}", target).str(); - CARBON_CHECK(RunWithNoRuntimes({ - "-no-canonical-prefixes", - target_arg, - "-O3", - "-fPIC", - "-ffreestanding", - "-fno-builtin", - "-fomit-frame-pointer", - "-fvisibility=hidden", - "-std=c11", - "-w", - "-c", - "-o", - out_path.native(), - src_path.native(), - })); -} - -auto ClangRunner::BuildBuiltinsLib(llvm::StringRef target, - const llvm::Triple& target_triple, - const std::filesystem::path& tmp_path, - Filesystem::DirRef lib_dir, - llvm::ThreadPoolInterface& threads) - -> ErrorOr { - llvm::SmallVector src_files = - CollectBuiltinsSrcFiles(target_triple); - - CARBON_ASSIGN_OR_RETURN(Filesystem::Dir tmp_dir, - Filesystem::Cwd().OpenDir(tmp_path)); - - // `NewArchiveMember` isn't default constructable unfortunately, so we first - // build the objects using an optional wrapper. - llvm::SmallVector> objs; - objs.resize(src_files.size()); - llvm::ThreadPoolTaskGroup member_group(threads); - for (auto [src_file, obj] : llvm::zip_equal(src_files, objs)) { - // Create any subdirectories needed for this file. - std::filesystem::path src_path = src_file.str(); - if (src_path.has_parent_path()) { - CARBON_RETURN_IF_ERROR(tmp_dir.CreateDirectories(src_path.parent_path())); - } - - member_group.async([this, target, src_file, &obj, &tmp_path] { - std::filesystem::path obj_path = tmp_path / std::string_view(src_file); - obj_path += ".o"; - BuildBuiltinsFile(target, src_file, obj_path); - - auto obj_result = llvm::NewArchiveMember::getFile(obj_path.native(), - /*Deterministic=*/true); - CARBON_CHECK(obj_result, "TODO: Diagnose this: {0}", - llvm::fmt_consume(obj_result.takeError())); - obj = std::move(*obj_result); - }); - } - - // Now build an archive out of the `.o` files for the builtins. Note that we - // build this directly into the `lib_dir` as this is expected to be a staging - // directory and cleaned up on errors. - std::filesystem::path builtins_a_path = "libclang_rt.builtins.a"; - CARBON_ASSIGN_OR_RETURN( - Filesystem::WriteFile builtins_a_file, - lib_dir.OpenWriteOnly(builtins_a_path, Filesystem::CreateAlways)); - - // Wait for all the object compiles to complete, and then move the objects out - // of their optional wrappers to match the API required by the archive writer. - member_group.wait(); - llvm::SmallVector unwrapped_objs; - unwrapped_objs.reserve(objs.size()); - for (auto& obj : objs) { - unwrapped_objs.push_back(*std::move(obj)); - } - objs.clear(); - - // Write the actual archive. - { - llvm::raw_fd_ostream builtins_a_os = builtins_a_file.WriteStream(); - llvm::Error archive_err = llvm::writeArchiveToStream( - builtins_a_os, unwrapped_objs, llvm::SymtabWritingMode::NormalSymtab, - target_triple.isOSDarwin() ? llvm::object::Archive::K_DARWIN - : llvm::object::Archive::K_GNU, - /*Deterministic=*/true, /*Thin=*/false); - // The presence of an error is `true`. - if (archive_err) { - return Error(llvm::toString(std::move(archive_err))); - } - } - CARBON_RETURN_IF_ERROR(std::move(builtins_a_file).Close()); - return Success(); -} - } // namespace Carbon diff --git a/toolchain/driver/clang_runner.h b/toolchain/driver/clang_runner.h index 10250d0e962f..7a9981dfe4ea 100644 --- a/toolchain/driver/clang_runner.h +++ b/toolchain/driver/clang_runner.h @@ -6,11 +6,13 @@ #define CARBON_TOOLCHAIN_DRIVER_CLANG_RUNNER_H_ #include +#include -#include "clang/Basic/DiagnosticIDs.h" #include "common/error.h" #include "common/ostream.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Support/VirtualFileSystem.h" @@ -78,15 +80,16 @@ class ClangRunner : ToolRunnerBase { // whether than Clang invocation succeeded is returned. auto Run(llvm::ArrayRef args, Runtimes::Cache& runtimes_cache, - llvm::ThreadPoolInterface& runtimes_build_thread_pool) - -> ErrorOr; + llvm::ThreadPoolInterface& runtimes_build_thread_pool, + bool enable_leaking = false) -> ErrorOr; // Run Clang with the provided arguments and prebuilt runtimes. // // Similar to `Run`, but requires and uses pre-built runtimes rather than a // cache or building them on demand. auto RunWithPrebuiltRuntimes(llvm::ArrayRef args, - Runtimes& prebuilt_runtimes) -> ErrorOr; + Runtimes& prebuilt_runtimes, + bool enable_leaking = false) -> ErrorOr; // Run Clang with the provided arguments and without any target runtimes. // @@ -97,68 +100,38 @@ class ClangRunner : ToolRunnerBase { // // This function simply returns true or false depending on whether Clang runs // successfully, as it should display any needed error messages. - auto RunWithNoRuntimes(llvm::ArrayRef args) -> bool; - - // Builds the target-specific resource directory for Clang. - // - // There is a resource directory installed along side the Clang binary that - // contains all the target independent files such as headers. However, for - // target-specific files like runtimes, we build those on demand here and - // return the path. - auto BuildTargetResourceDir(const Runtimes::Cache::Features& features, - Runtimes& runtimes, - const std::filesystem::path& tmp_path, - llvm::ThreadPoolInterface& threads) - -> ErrorOr; - - // Enable leaking memory. - // - // Clang can avoid deallocating some of its memory to improve compile time. - // However, this isn't compatible with library-based invocations. When using - // the runner in a context where memory leaks are acceptable, such as from a - // command line driver, you can use this to enable that leaking behavior. Note - // that this will not override _explicit_ `args` in a run invocation that - // cause leaking, it will merely disable Clang's libraries injecting that - // behavior. - auto EnableLeakingMemory() -> void { enable_leaking_ = true; } + auto RunWithNoRuntimes(llvm::ArrayRef args, + bool enable_leaking = false) -> bool; private: + friend class ClangRuntimesBuilderBase; + // Emulates `cc1_main` but in a way that doesn't assume it is running in the // main thread and can more easily fit into library calls to do compiles. // // TODO: Much of the logic here should be factored out of the CC1 // implementation in Clang's driver and into a reusable part of its libraries. // That should allow reducing the code here to a minimal amount. - auto RunCC1(llvm::SmallVectorImpl& cc1_args) -> int; + auto RunCC1(llvm::SmallVectorImpl& cc1_args, bool enable_leaking) + -> int; // Handles building the Clang driver and passing the arguments down to it. auto RunInternal(llvm::ArrayRef args, llvm::StringRef target, - std::optional target_resource_dir_path) - -> bool; - - // Helper to compile a single file of the CRT runtimes. - auto BuildCrtFile(llvm::StringRef target, llvm::StringRef src_file, - const std::filesystem::path& out_path) -> void; + std::optional target_resource_dir_path, + bool enable_leaking) -> bool; // Returns the target-specific source files for the builtins runtime library. auto CollectBuiltinsSrcFiles(const llvm::Triple& target_triple) -> llvm::SmallVector; - // Helper to compile a single file of the compiler builtins runtimes. - auto BuildBuiltinsFile(llvm::StringRef target, llvm::StringRef src_file, - const std::filesystem::path& out_path) -> void; - // Builds the builtins runtime library into the provided archive file path, // using the provided objects path for intermediate object files. auto BuildBuiltinsLib(llvm::StringRef target, const llvm::Triple& target_triple, - const std::filesystem::path& tmp_path, - Filesystem::DirRef lib_dir, - llvm::ThreadPoolInterface& threads) -> ErrorOr; + const std::filesystem::path& runtimes_path, + llvm::ThreadPoolTaskGroup& threads) -> ErrorOr; llvm::IntrusiveRefCntPtr fs_; - - bool enable_leaking_ = false; }; } // namespace Carbon diff --git a/toolchain/driver/clang_runner_test.cpp b/toolchain/driver/clang_runner_test.cpp index fb50e1107714..2429e274a052 100644 --- a/toolchain/driver/clang_runner_test.cpp +++ b/toolchain/driver/clang_runner_test.cpp @@ -8,30 +8,27 @@ #include #include -#include #include #include -#include "common/check.h" #include "common/ostream.h" #include "common/raw_string_ostream.h" -#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/Twine.h" #include "llvm/Object/Binary.h" #include "llvm/Object/ObjectFile.h" -#include "llvm/Support/FormatVariadic.h" -#include "llvm/Support/Program.h" +#include "llvm/Support/VirtualFileSystem.h" #include "llvm/TargetParser/Host.h" #include "testing/base/capture_std_streams.h" #include "testing/base/file_helpers.h" #include "testing/base/global_exe_path.h" -#include "toolchain/driver/llvm_runner.h" +#include "toolchain/driver/runtimes_cache.h" +#include "toolchain/install/install_paths.h" namespace Carbon { namespace { -using ::testing::Eq; using ::testing::HasSubstr; -using ::testing::IsSupersetOf; using ::testing::StrEq; // NOLINTNEXTLINE(modernize-use-trailing-return-type): Macro based function. @@ -178,84 +175,6 @@ TEST_F(ClangRunnerTest, CompileMultipleFiles) { compile("test3.cpp", "int test3() { return 0; }"); } -TEST_F(ClangRunnerTest, BuildResourceDir) { - ClangRunner runner(&install_paths_, vfs_, &llvm::errs()); - - // Note that we can't test arbitrary targets here as we need to be able to - // compile the builtin functions for the target. We use the default target as - // the most likely to pass. - std::string target = llvm::sys::getDefaultTargetTriple(); - llvm::Triple target_triple(target); - Runtimes::Cache::Features features = {.target = target}; - auto runtimes = *runtimes_cache_.Lookup(features); - auto tmp_dir = *Filesystem::MakeTmpDir(); - llvm::DefaultThreadPool threads(llvm::optimal_concurrency()); - auto build_result = runner.BuildTargetResourceDir( - features, runtimes, tmp_dir.abs_path(), threads); - ASSERT_TRUE(build_result.ok()) << build_result.error(); - std::filesystem::path resource_dir_path = std::move(*build_result); - - // For Linux we can directly check the CRT begin/end object files. - if (target_triple.isOSLinux()) { - std::filesystem::path crt_begin_path = - resource_dir_path / "lib" / target / "clang_rt.crtbegin.o"; - ASSERT_TRUE(std::filesystem::is_regular_file(crt_begin_path)); - auto begin_result = - llvm::object::ObjectFile::createObjectFile(crt_begin_path.native()); - llvm::object::ObjectFile& crtbegin = *begin_result->getBinary(); - EXPECT_TRUE(crtbegin.isELF()); - EXPECT_TRUE(crtbegin.isObject()); - EXPECT_THAT(crtbegin.getArch(), Eq(target_triple.getArch())); - - llvm::SmallVector symbols(crtbegin.symbols()); - // The first symbol should come from the source file. - EXPECT_THAT(*symbols.front().getName(), Eq("crtbegin.c")); - - // Check for representative symbols of `crtbegin.o` -- we always use - // `.init_array` in our runtimes build so we have predictable functions. - EXPECT_THAT(symbols, IsSupersetOf({TextSymbolNamed("__do_init"), - TextSymbolNamed("__do_fini")})); - - std::filesystem::path crt_end_path = - resource_dir_path / "lib" / target / "clang_rt.crtend.o"; - ASSERT_TRUE(std::filesystem::is_regular_file(crt_end_path)); - auto end_result = - llvm::object::ObjectFile::createObjectFile(crt_end_path.native()); - llvm::object::ObjectFile& crtend = *end_result->getBinary(); - EXPECT_TRUE(crtend.isELF()); - EXPECT_TRUE(crtend.isObject()); - EXPECT_THAT(crtend.getArch(), Eq(target_triple.getArch())); - - // Just check the source file symbol, not much of interest in the end. - llvm::object::SymbolRef crtend_front_symbol = *crtend.symbol_begin(); - EXPECT_THAT(*crtend_front_symbol.getName(), Eq("crtend.c")); - } - - // Across all targets, check that the builtins archive exists, and contains a - // relevant symbol by running the `llvm-nm` tool over it. Using `nm` rather - // than directly inspecting the objects is a bit awkward, but lets us easily - // ignore the wrapping in an archive file. - std::filesystem::path builtins_path = - resource_dir_path / "lib" / target / "libclang_rt.builtins.a"; - LLVMRunner llvm_runner(&install_paths_, &llvm::errs()); - std::string out; - std::string err; - EXPECT_TRUE(Testing::CallWithCapturedOutput(out, err, [&] { - return llvm_runner.Run(LLVMTool::Nm, {builtins_path.native()}); - })); - - // Check that we found a definition of `__mulodi4`, a builtin function - // provided by Compiler-RT, but not `libgcc` historically. Note that on macOS - // there is a leading `_` due to mangling. - EXPECT_THAT(out, HasSubstr(target_triple.isMacOSX() ? "T ___mulodi4\n" - : "T __mulodi4\n")); - - // Check that we don't include the `chkstk` builtins outside of Windows. - if (!target_triple.isOSWindows()) { - EXPECT_THAT(out, Not(HasSubstr("chkstk"))); - } -} - // It's hard to write a portable and reliable unittest for all the layers of the // Clang driver because they work hard to interact with the underlying // filesystem and operating system. For now, we just check that a link command diff --git a/toolchain/driver/clang_runtimes.cpp b/toolchain/driver/clang_runtimes.cpp new file mode 100644 index 000000000000..06ab5570ab90 --- /dev/null +++ b/toolchain/driver/clang_runtimes.cpp @@ -0,0 +1,403 @@ +// 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 "toolchain/driver/clang_runtimes.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/check.h" +#include "common/error.h" +#include "common/filesystem.h" +#include "common/latch.h" +#include "common/vlog.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatAdapters.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" +#include "toolchain/base/runtime_sources.h" +#include "toolchain/driver/clang_runner.h" +#include "toolchain/driver/runtimes_cache.h" + +namespace Carbon { + +auto ClangRuntimesBuilderBase::ArchiveBuilder::Setup(Latch::Handle latch_handle) + -> void { + // `NewArchiveMember` isn't default constructable unfortunately, so we have to + // manually populate the vector with errors that we'll replace with the actual + // result in each thread. + objs_.reserve(src_files_.size()); + for (auto _ : src_files_) { + objs_.push_back(Error("Never constructed archive member!")); + } + + // Finish building the archive when the last compile finishes. + Latch::Handle comp_latch_handle = + compilation_latch_.Init([this, latch_handle] { result_ = Finish(); }); + + // Add all the compiles to the thread pool to run concurrently. The latch + // handle ensures the last one triggers the finishing closure above. + for (auto [src_file, obj] : llvm::zip_equal(src_files_, objs_)) { + builder_->tasks_.async([this, comp_latch_handle, src_file, &obj]() mutable { + obj = CompileMember(src_file); + }); + } +} + +auto ClangRuntimesBuilderBase::ArchiveBuilder::Finish() -> ErrorOr { + // We build this directly into the desired location as this is expected to be + // a staging directory and cleaned up on errors. We do need to create any + // intermediate directories. + Filesystem::DirRef runtimes_dir = builder_->runtimes_builder_->dir(); + if (archive_path_.has_parent_path()) { + CARBON_RETURN_IF_ERROR( + runtimes_dir.CreateDirectories(archive_path_.parent_path())); + } + + // Check if any compilations ended up producing an error. If so, return the + // first error for the entire function. Otherwise, move the archive member + // into a direct vector to match the required archive building API. + llvm::SmallVector unwrapped_objs; + unwrapped_objs.reserve(objs_.size()); + for (auto& obj : objs_) { + if (!obj.ok()) { + return std::move(obj).error(); + } + unwrapped_objs.push_back(*std::move(obj)); + } + objs_.clear(); + + // Remove any directories created for the object files, the files should + // already be removed. We walk the sorted list of these in reverse so we + // remove child directories before parent directories. + for (const auto& obj_dir : llvm::reverse(obj_dirs_)) { + auto rmdir_result = runtimes_dir.Rmdir(obj_dir); + // Don't return an error on failure here as this has no problematic + // effect, just log that we couldn't clean up a directory. + if (!rmdir_result.ok()) { + CARBON_VLOG("Unable to remove object directory `{0}` in the runtime: {1}", + obj_dir.native(), rmdir_result.error()); + } + } + + // Write the actual archive. + CARBON_ASSIGN_OR_RETURN( + Filesystem::WriteFile archive_file, + runtimes_dir.OpenWriteOnly(archive_path_, Filesystem::CreateAlways)); + { + llvm::raw_fd_ostream archive_os = archive_file.WriteStream(); + llvm::Error archive_err = llvm::writeArchiveToStream( + archive_os, unwrapped_objs, llvm::SymtabWritingMode::NormalSymtab, + builder_->target_triple_.isOSDarwin() ? llvm::object::Archive::K_DARWIN + : llvm::object::Archive::K_GNU, + /*Deterministic=*/true, /*Thin=*/false); + // The presence of an error is `true`. + if (archive_err) { + (void)std::move(archive_file).Close(); + return Error(llvm::toString(std::move(archive_err))); + } + } + // Close and return any errors, potentially from the writes above. + CARBON_RETURN_IF_ERROR(std::move(archive_file).Close()); + return Success(); +} + +auto ClangRuntimesBuilderBase::ArchiveBuilder::CreateObjDir( + const std::filesystem::path& src_path) -> ErrorOr { + auto obj_dir_path = src_path.parent_path(); + if (obj_dir_path.empty()) { + return Success(); + } + + std::scoped_lock lock(obj_dirs_mu_); + auto* it = std::lower_bound(obj_dirs_.begin(), obj_dirs_.end(), obj_dir_path); + if (it != obj_dirs_.end() && *it == obj_dir_path) { + return Success(); + } + + auto create_result = + builder_->runtimes_builder_->dir().CreateDirectories(obj_dir_path); + if (!create_result.ok()) { + return Error(llvm::formatv( + "Unable to create object directory mirroring source file `{0}`: {1}", + src_path, create_result.error())); + } + + it = obj_dirs_.insert(it, obj_dir_path); + + // Also insert any parent paths. These should always sort earlier. + CARBON_DCHECK(!obj_dir_path.has_parent_path() || + obj_dir_path.parent_path() < obj_dir_path); + obj_dir_path = obj_dir_path.parent_path(); + while (!obj_dir_path.empty()) { + it = std::lower_bound(obj_dirs_.begin(), it, obj_dir_path); + if (*it != obj_dir_path) { + it = obj_dirs_.insert(it, obj_dir_path); + } + obj_dir_path = obj_dir_path.parent_path(); + } + return Success(); +} + +auto ClangRuntimesBuilderBase::ArchiveBuilder::CompileMember( + llvm::StringRef src_file) -> ErrorOr { + // Create any obj subdirectories needed for this file. + CARBON_RETURN_IF_ERROR(CreateObjDir(src_file.str())); + + std::filesystem::path obj_path = + builder_->runtimes_builder_->path() / std::string_view(src_file); + obj_path += ".o"; + std::filesystem::path src_path = srcs_path_ / std::string_view(src_file); + CARBON_VLOG("Building `{0}' from `{1}`...\n", obj_path, src_path); + + llvm::SmallVector args(cflags_); + + // Add language-specific flags based on file extension. + if (src_file.ends_with(".c")) { + args.push_back("-std=c11"); + } else if (src_file.ends_with(".cpp")) { + args.push_back("-std=c++20"); + } + + // Collect the additional required flags and dynamic flags for this builder. + args.append({ + "-c", + builder_->target_flag_, + "-o", + obj_path.native(), + src_path.native(), + }); + if (!builder_->clang_->RunWithNoRuntimes(args)) { + return Error( + llvm::formatv("Failed to compile runtime source file '{0}'", src_file)); + } + + auto obj_result = llvm::NewArchiveMember::getFile(obj_path.native(), + /*Deterministic=*/true); + if (!obj_result) { + return Error(llvm::formatv("Unable to read `{0}` object file: {1}", + src_file, + llvm::fmt_consume(obj_result.takeError()))); + } + + // Unlink the object file once we've read it. However, we log and ignore + // any errors here as they aren't fatal. + auto unlink_result = builder_->runtimes_builder_->dir().Unlink(obj_path); + if (!unlink_result.ok()) { + CARBON_VLOG("Unable to unlink object file `{0}`: {1}\n", obj_path, + unlink_result.error()); + } + + return std::move(*obj_result); +} + +ClangResourceDirBuilder::ClangResourceDirBuilder( + ClangRunner* clang, llvm::ThreadPoolInterface* threads, + llvm::Triple target_triple, Runtimes* runtimes) + : ClangRuntimesBuilderBase(clang, threads, std::move(target_triple)), + crt_begin_result_(Error("Never built CRT begin file!")), + crt_end_result_(Error("Never built CRT end file!")) { + // Ensure we're on a platform where we _can_ build a working runtime. + if (target_triple_.isOSWindows()) { + result_ = + Error("TODO: Windows runtimes are untested and not yet supported."); + return; + } + + auto build_dir_or_error = runtimes->Build(Runtimes::ClangResourceDir); + if (!build_dir_or_error.ok()) { + result_ = std::move(build_dir_or_error).error(); + return; + } + auto build_dir = *std::move(build_dir_or_error); + if (std::holds_alternative(build_dir)) { + // Found cached build. + result_ = std::get(std::move(build_dir)); + return; + } + + runtimes_builder_ = std::get(std::move(build_dir)); + lib_path_ = std::filesystem::path("lib") / target_triple_.str(); + archive_.emplace(this, lib_path_ / "libclang_rt.builtins.a", + installation().llvm_runtime_srcs(), + CollectBuiltinsSrcFiles(), /*cflags=*/ + llvm::SmallVector{ + "-no-canonical-prefixes", + "-O3", + "-fPIC", + "-ffreestanding", + "-fno-builtin", + "-fomit-frame-pointer", + "-fvisibility=hidden", + "-w", + }); + tasks_.async([this]() { Setup(); }); +} + +auto ClangResourceDirBuilder::CollectBuiltinsSrcFiles() + -> llvm::SmallVector { + llvm::SmallVector src_files; + auto append_src_files = + [&](auto input_srcs, + llvm::function_ref filter_out = {}) { + for (llvm::StringRef input_src : input_srcs) { + if (!input_src.ends_with(".c") && !input_src.ends_with(".S")) { + // Not a compiled file. + continue; + } + if (filter_out && filter_out(input_src)) { + // Filtered out. + continue; + } + + src_files.push_back(input_src); + } + }; + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsGenericSrcs)); + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsBf16Srcs)); + if (target_triple_.isArch64Bit()) { + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsTfSrcs)); + } + auto filter_out_chkstk = [&](llvm::StringRef src) { + return !target_triple_.isOSWindows() || !src.ends_with("chkstk.S"); + }; + if (target_triple_.isAArch64()) { + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsAarch64Srcs), + filter_out_chkstk); + } else if (target_triple_.isX86()) { + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsX86ArchSrcs)); + if (target_triple_.isArch64Bit()) { + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsX86_64Srcs), + filter_out_chkstk); + } else { + // TODO: This should be turned into a nice user-facing diagnostic about an + // unsupported target. + CARBON_CHECK( + target_triple_.isArch32Bit(), + "The Carbon toolchain doesn't currently support 16-bit x86."); + append_src_files(llvm::ArrayRef(RuntimeSources::BuiltinsI386Srcs), + filter_out_chkstk); + } + } else { + // TODO: This should be turned into a nice user-facing diagnostic about an + // unsupported target. + CARBON_FATAL("Target architecture is not supported: {0}", + target_triple_.str()); + } + return src_files; +} + +auto ClangResourceDirBuilder::Setup() -> void { + // Symlink the installation's `include` and `share` directories. + std::filesystem::path install_resource_path = + installation().clang_resource_path(); + if (auto result = runtimes_builder_->dir().Symlink( + "include", install_resource_path / "include"); + !result.ok()) { + result_ = std::move(result).error(); + return; + } + if (auto result = runtimes_builder_->dir().Symlink( + "share", install_resource_path / "share"); + !result.ok()) { + result_ = std::move(result).error(); + return; + } + + // Create the target's `lib` directory. + auto lib_dir_result = runtimes_builder_->dir().CreateDirectories(lib_path_); + if (!lib_dir_result.ok()) { + result_ = std::move(lib_dir_result).error(); + return; + } + lib_dir_ = *std::move(lib_dir_result); + + Latch::Handle latch_handle = + step_counter_.Init([this] { tasks_.async([this] { Finish(); }); }); + + // For Linux targets, the system libc (typically glibc) doesn't necessarily + // provide the CRT begin/end files, and so we need to build them. + if (target_triple_.isOSLinux()) { + tasks_.async([this, latch_handle] { + crt_begin_result_ = BuildCrtFile(RuntimeSources::CrtBegin); + }); + tasks_.async([this, latch_handle] { + crt_end_result_ = BuildCrtFile(RuntimeSources::CrtEnd); + }); + } + + archive_->Setup(std::move(latch_handle)); +} + +auto ClangResourceDirBuilder::Finish() -> void { + CARBON_VLOG("Finished building resource dir...\n"); + if (!archive_->result().ok()) { + result_ = std::move(archive_->result()).error(); + return; + } + if (target_triple_.isOSLinux()) { + for (ErrorOr* result : {&crt_begin_result_, &crt_end_result_}) { + if (!result->ok()) { + result_ = std::move(*result).error(); + return; + } + } + } + + result_ = (*std::move(runtimes_builder_)).Commit(); +} + +auto ClangResourceDirBuilder::BuildCrtFile(llvm::StringRef src_file) + -> ErrorOr { + CARBON_CHECK(src_file == RuntimeSources::CrtBegin || + src_file == RuntimeSources::CrtEnd); + std::filesystem::path out_path = + runtimes_builder_->path() / lib_path_ / + (src_file == RuntimeSources::CrtBegin ? "clang_rt.crtbegin.o" + : "clang_rt.crtend.o"); + std::filesystem::path src_path = + installation().llvm_runtime_srcs() / std::string_view(src_file); + CARBON_VLOG("Building `{0}' from `{1}`...\n", out_path, src_path); + + bool success = clang_->RunWithNoRuntimes({ + "-no-canonical-prefixes", + "-DCRT_HAS_INITFINI_ARRAY", + "-DEH_USE_FRAME_REGISTRY", + "-O3", + "-fPIC", + "-ffreestanding", + "-std=c11", + "-w", + "-c", + target_flag_, + "-o", + out_path.native(), + src_path.native(), + }); + + if (success) { + return Success(); + } + return Error(llvm::formatv("Failed to compile CRT file: {0}", src_file)); +} + +} // namespace Carbon diff --git a/toolchain/driver/clang_runtimes.h b/toolchain/driver/clang_runtimes.h new file mode 100644 index 000000000000..b2aa3272015f --- /dev/null +++ b/toolchain/driver/clang_runtimes.h @@ -0,0 +1,259 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_DRIVER_CLANG_RUNTIMES_H_ +#define CARBON_TOOLCHAIN_DRIVER_CLANG_RUNTIMES_H_ + +#include +#include +#include +#include +#include + +#include "common/error.h" +#include "common/filesystem.h" +#include "common/latch.h" +#include "common/ostream.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/TargetParser/Triple.h" +#include "toolchain/driver/clang_runner.h" +#include "toolchain/driver/runtimes_cache.h" +#include "toolchain/install/install_paths.h" + +namespace Carbon { + +// Common APIs and utilities factored out of all of the Clang runtimes builders. +// +// The Clang runtimes builders are _asynchronous_ builders at their core, and so +// their primary API is to construct the builder (kicking off work across +// threads) and then `Wait` for it to finish. +class ClangRuntimesBuilderBase { + public: + // Waits until the runtimes are finished being built and then return either + // any error encountered or the path of the built runtimes. + // + // If a path is returned, it will be the same as `Runtimes::Builder::Commit` + // would return. + auto Wait() && -> ErrorOr { + tasks_.wait(); + return std::move(result_); + } + + protected: + class ArchiveBuilder; + + // Initializes the common state of a runtimes builder. + // + // Both the `clang` runner and the `threads` need to outlive this object. + ClangRuntimesBuilderBase(ClangRunner* clang, + llvm::ThreadPoolInterface* threads, + llvm::Triple target_triple) + : clang_(clang), + vlog_stream_(clang_->vlog_stream_), + tasks_(*threads), + target_triple_(std::move(target_triple)), + target_flag_(llvm::formatv("--target={0}", target_triple_.str())), + result_(Error("Did not finish building the runtime!")) {} + + auto installation() -> const InstallPaths& { return *clang_->installation_; } + + // We use protected members as this base is just factoring out common + // implementation details of other runners. + // + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + + ClangRunner* clang_; + llvm::raw_ostream* vlog_stream_; + + // This task group will be used both to launch asynchronous work and to wait + // for all of the work for a particular build to complete. + llvm::ThreadPoolTaskGroup tasks_; + + llvm::Triple target_triple_; + std::string target_flag_; + + ErrorOr result_; + + // If runtimes already exist, we may never need a builder for them. But if we + // do need to build the runtimes, store the `Runtimes::Builder` here. + std::optional runtimes_builder_; + + // When building runtimes, this latch synchronizes all the steps required + // to build the runtimes into the above builder's staging directory. Once + // satisfied, it is typically used to schedule committing the runtimes as the + // last task. + Latch step_counter_; + // NOLINTEND(misc-non-private-member-variables-in-classes) +}; + +// Helper class that factors out the logic to build an archive as part of a set +// of Clang runtimes. +// +// Runtimes can consist of one or more archives, and potentially other +// artifacts, but because archives are very common we factor out logic to build +// them here. +class ClangRuntimesBuilderBase::ArchiveBuilder { + public: + // Initialize an archive builder. + // + // - The `builder` must outlive this object. + // - `archive_path` is the _relative_ path of the archive file within the + // built + // runtimes directory. + // - `srcs_path` is the _absolute_ root of source files used to build the + // archive. + // - `src_files` is a list of the file paths to build into the archive, + // relative to the `srcs_path`. These are `StringRef`s so they can reference + // constant `StringLiteral`s in common cases. + // - `cflags` are the compile flags that should be used for all the compiles + // in this archive. + ArchiveBuilder(ClangRuntimesBuilderBase* builder, + std::filesystem::path archive_path, + std::filesystem::path srcs_path, + llvm::ArrayRef src_files, + llvm::ArrayRef cflags) + : builder_(builder), + vlog_stream_(builder_->vlog_stream_), + archive_path_(std::move(archive_path)), + srcs_path_(std::move(srcs_path)), + src_files_(src_files), + cflags_(cflags) {} + + // Start building the archive, with a latch handle to signal its completion. + // + // This will launch asynchronous tasks on the `builder_->tasks` task group to + // first compile all the members of the archive, and once compiled to put them + // into the archive file. Only when this last step is complete will the + // provided handle be destroyed, signaling this step of any concurrent build + // is done. + // + // This must only be called once per instance. + auto Setup(Latch::Handle latch_handle) -> void; + + // Accessor for the result of building the archive. + // + // This will return an error if accessed prior to `Start`-ing the archive's + // build. + // + // Once `Start` has been called, this must not be called until the + // `latch_handle` provided to `Start` is destroyed as doing so will race with + // producing the result. + auto result() -> ErrorOr& { return result_; } + + private: + // Helper for finishing the build of the archive. + // + // Must only be called once all members have been compiled, and returns the + // result of forming the archive file from those members. + auto Finish() -> ErrorOr; + + // Given a specific `src_path` relative to our `srcs_path`, create any + // necessary directories relative to the build's runtimes root to allow the + // object file for this source file to be written there. + // + // Returns any errors encountered creating the necessary directories. + // + // Uses a cache to avoid redundant directory creations, and stores a list of + // all the directories created so they can be removed at the end of the build. + // + // This method is thread-safe, and designed to be called concurrently from + // each source file's compilation. + auto CreateObjDir(const std::filesystem::path& src_path) -> ErrorOr; + + // Compiles the `src_file` and read the resulting object as a new archive + // member. + // + // The argument should be one of the elements of `src_files_`. The compile is + // performed using the `builder_->clang_` runner and the provided `cflags_`. + // + // Any errors encountered are returned. + auto CompileMember(llvm::StringRef src_file) + -> ErrorOr; + + ClangRuntimesBuilderBase* builder_; + llvm::raw_ostream* vlog_stream_; + + std::filesystem::path archive_path_; + + std::filesystem::path srcs_path_; + llvm::SmallVector src_files_; + + llvm::SmallVector cflags_; + + // A latch used to synchronize building the archive once all members have been + // compiled. + Latch compilation_latch_; + // Storage for either the archive members or the error encountered while + // compiling them. + llvm::SmallVector> objs_; + + // A mutex and vector used to maintain a thread-safe cache of directories + // created to hold object files when compiling. + std::mutex obj_dirs_mu_; + llvm::SmallVector obj_dirs_; + + // Storage for the final result of building the requested archive. + ErrorOr result_ = Error("No archive built!"); +}; + +// Builds the target-specific resource directory for Clang. +// +// There is a resource directory installed along side the Clang binary that +// contains all the target independent files such as headers. However, for +// target-specific files like the runtimes that are part of the resource +// directory, we build those on demand as runtimes. +// +class ClangResourceDirBuilder : public ClangRuntimesBuilderBase { + public: + // Constructing this class will attempt to build the Clang resource directory + // into `runtimes`. + // + // If an existing build is found, it will immediately be available. + // Otherwise, constructing this class will schedule asynchronous work on + // `threads` to build them on-demand using `clang`. + // + // Once constructed, callers may call `Wait` (from the base class) to wait + // until the asynchronous work is complete and the runtimes are available. If + // they were already available, the call to `Wait` will not block. + ClangResourceDirBuilder(ClangRunner* clang, + llvm::ThreadPoolInterface* threads, + llvm::Triple target_triple, Runtimes* runtimes); + + private: + // Helper method to encapsulate the logic of configuring the list of source + // files to use in the `builtins` archive within these runtimes on a + // particular target. + auto CollectBuiltinsSrcFiles() -> llvm::SmallVector; + + // Helper to encapsulate the initial, but still asynchronous setup work. + auto Setup() -> void; + + // Helper to encapsulate the final asynchronous step in building the resource + // directory. + auto Finish() -> void; + + // Helper to compile a single file of the CRT runtimes. + auto BuildCrtFile(llvm::StringRef src_file) -> ErrorOr; + + // The `lib` path and subdirectory of the being-built runtimes. + std::filesystem::path lib_path_; + Filesystem::Dir lib_dir_; + + // The results of compiling the CRT `begin` and `end` files. + ErrorOr crt_begin_result_; + ErrorOr crt_end_result_; + + // The archive builder for the builtins archive in the resource directory. + std::optional archive_; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_DRIVER_CLANG_RUNTIMES_H_ diff --git a/toolchain/driver/clang_runtimes_test.cpp b/toolchain/driver/clang_runtimes_test.cpp new file mode 100644 index 000000000000..af6214d4fe6e --- /dev/null +++ b/toolchain/driver/clang_runtimes_test.cpp @@ -0,0 +1,153 @@ +// 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 "toolchain/driver/clang_runtimes.h" + +#include +#include + +#include +#include +#include + +#include "common/ostream.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" +#include "testing/base/capture_std_streams.h" +#include "testing/base/global_exe_path.h" +#include "toolchain/base/llvm_tools.h" +#include "toolchain/driver/clang_runner.h" +#include "toolchain/driver/llvm_runner.h" +#include "toolchain/driver/runtimes_cache.h" +#include "toolchain/install/install_paths.h" + +namespace Carbon { +namespace { + +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsSupersetOf; + +// NOLINTNEXTLINE(modernize-use-trailing-return-type): Macro based function. +MATCHER_P(TextSymbolNamed, name_matcher, "") { + llvm::Expected name = arg.getName(); + if (auto error = name.takeError()) { + *result_listener << "with an error instead of a name: " << error; + return false; + } + if (!testing::ExplainMatchResult(name_matcher, *name, result_listener)) { + return false; + } + // We have to dig out the section to determine if this was a text symbol. + auto expected_section_it = arg.getSection(); + if (auto error = expected_section_it.takeError()) { + *result_listener << "without a section: " << error; + return false; + } + llvm::object::SectionRef section = **expected_section_it; + if (!section.isText()) { + *result_listener << "in the non-text section: " << *section.getName(); + return false; + } + return true; +} + +class ClangRuntimesTest : public ::testing::Test { + public: + InstallPaths install_paths_ = + InstallPaths::MakeForBazelRunfiles(Testing::GetExePath()); + Runtimes::Cache runtimes_cache_ = + *Runtimes::Cache::MakeSystem(install_paths_); + llvm::IntrusiveRefCntPtr vfs_ = + llvm::vfs::getRealFileSystem(); +}; + +TEST_F(ClangRuntimesTest, ResourceDir) { + ClangRunner runner(&install_paths_, vfs_, &llvm::errs()); + + // Note that we can't test arbitrary targets here as we need to be able to + // compile the builtin functions for the target. We use the default target as + // the most likely to pass. + std::string target = llvm::sys::getDefaultTargetTriple(); + llvm::Triple target_triple(target); + Runtimes::Cache::Features features = {.target = target}; + auto runtimes = *runtimes_cache_.Lookup(features); + llvm::DefaultThreadPool threads(llvm::optimal_concurrency()); + + ClangResourceDirBuilder resource_dir_builder(&runner, &threads, target_triple, + &runtimes); + auto build_result = std::move(resource_dir_builder).Wait(); + ASSERT_TRUE(build_result.ok()) << build_result.error(); + std::filesystem::path resource_dir_path = std::move(*build_result); + + // For Linux we can directly check the CRT begin/end object files. + if (target_triple.isOSLinux()) { + std::filesystem::path crt_begin_path = + resource_dir_path / "lib" / target / "clang_rt.crtbegin.o"; + ASSERT_TRUE(std::filesystem::is_regular_file(crt_begin_path)); + auto begin_result = + llvm::object::ObjectFile::createObjectFile(crt_begin_path.native()); + llvm::object::ObjectFile& crtbegin = *begin_result->getBinary(); + EXPECT_TRUE(crtbegin.isELF()); + EXPECT_TRUE(crtbegin.isObject()); + EXPECT_THAT(crtbegin.getArch(), Eq(target_triple.getArch())); + + llvm::SmallVector symbols(crtbegin.symbols()); + // The first symbol should come from the source file. + EXPECT_THAT(*symbols.front().getName(), Eq("crtbegin.c")); + + // Check for representative symbols of `crtbegin.o` -- we always use + // `.init_array` in our runtimes build so we have predictable functions. + EXPECT_THAT(symbols, IsSupersetOf({TextSymbolNamed("__do_init"), + TextSymbolNamed("__do_fini")})); + + std::filesystem::path crt_end_path = + resource_dir_path / "lib" / target / "clang_rt.crtend.o"; + ASSERT_TRUE(std::filesystem::is_regular_file(crt_end_path)); + auto end_result = + llvm::object::ObjectFile::createObjectFile(crt_end_path.native()); + llvm::object::ObjectFile& crtend = *end_result->getBinary(); + EXPECT_TRUE(crtend.isELF()); + EXPECT_TRUE(crtend.isObject()); + EXPECT_THAT(crtend.getArch(), Eq(target_triple.getArch())); + + // Just check the source file symbol, not much of interest in the end. + llvm::object::SymbolRef crtend_front_symbol = *crtend.symbol_begin(); + EXPECT_THAT(*crtend_front_symbol.getName(), Eq("crtend.c")); + } + + // Across all targets, check that the builtins archive exists, and contains a + // relevant symbol by running the `llvm-nm` tool over it. Using `nm` rather + // than directly inspecting the objects is a bit awkward, but lets us easily + // ignore the wrapping in an archive file. + std::filesystem::path builtins_path = + resource_dir_path / "lib" / target / "libclang_rt.builtins.a"; + LLVMRunner llvm_runner(&install_paths_, &llvm::errs()); + std::string out; + std::string err; + EXPECT_TRUE(Testing::CallWithCapturedOutput(out, err, [&] { + return llvm_runner.Run(LLVMTool::Nm, {builtins_path.native()}); + })); + + // Check that we found a definition of `__mulodi4`, a builtin function + // provided by Compiler-RT, but not `libgcc` historically. Note that on macOS + // there is a leading `_` due to mangling. + EXPECT_THAT(out, HasSubstr(target_triple.isMacOSX() ? "T ___mulodi4\n" + : "T __mulodi4\n")); + + // Check that we don't include the `chkstk` builtins outside of Windows. + if (!target_triple.isOSWindows()) { + EXPECT_THAT(out, Not(HasSubstr("chkstk"))); + } +} + +} // namespace +} // namespace Carbon diff --git a/toolchain/driver/clang_subcommand.cpp b/toolchain/driver/clang_subcommand.cpp index 3959452eb091..101105c4122b 100644 --- a/toolchain/driver/clang_subcommand.cpp +++ b/toolchain/driver/clang_subcommand.cpp @@ -78,20 +78,17 @@ auto ClangSubcommand::Run(DriverEnv& driver_env) -> DriverResult { return {.success = false}; } - // Only enable Clang's leaking of memory if the driver can support that. - if (driver_env.enable_leaking) { - runner.EnableLeakingMemory(); - } - ErrorOr run_result = false; if (driver_env.prebuilt_runtimes) { run_result = runner.RunWithPrebuiltRuntimes(options_.args, - *driver_env.prebuilt_runtimes); + *driver_env.prebuilt_runtimes, + driver_env.enable_leaking); } else if (options_.build_runtimes_on_demand) { run_result = runner.Run(options_.args, driver_env.runtimes_cache, - *driver_env.thread_pool); + *driver_env.thread_pool, driver_env.enable_leaking); } else { - run_result = runner.RunWithNoRuntimes(options_.args); + run_result = + runner.RunWithNoRuntimes(options_.args, driver_env.enable_leaking); } if (!run_result.ok()) { // This is not a Clang failure, but a failure to even run Clang, so we need