Files
carbon-lang/toolchain/driver/clang_runner.cpp
T
Jon Ross-Perkins 957599b2ab Implement a basic busybox for carbon/clang. (#4406)
For reference, we're going down the busyboxing route because Carbon
depends on Clang, and we want both to be available as binaries.
Busyboxing allows this while avoiding duplicating symbols between
multiple binaries.

I'm removing the `cc_binary` for `driver:carbon` because I want to avoid
a significant increase in binary outputs; `bazel run //toolchain` still
works great.

This still doesn't have great test coverage (but non-zero:
`//examples:sieve` still builds/runs, for example). The problem is that
we want to avoid subprocessing for performance, but this mainly deals
with subprocessing. I'm still thinking about good approaches for that,
since we'll probably want more significant testing for `clang`
interaction... the solution might involve busyboxing `file_test` too.

Note development on this ran into the argv issue being fixed in #4405
2024-10-30 17:07:55 +00:00

186 lines
7.2 KiB
C++

// 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_runner.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <optional>
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "common/command_line.h"
#include "common/vlog.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LLVMDriver.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/TargetParser/Host.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 {
ClangRunner::ClangRunner(const InstallPaths* install_paths,
llvm::StringRef target, llvm::raw_ostream* vlog_stream)
: installation_(install_paths),
target_(target),
vlog_stream_(vlog_stream),
diagnostic_ids_(new clang::DiagnosticIDs()) {}
auto ClangRunner::Run(llvm::ArrayRef<llvm::StringRef> args) -> bool {
// TODO: Maybe handle response file expansion similar to the Clang CLI?
// If we have a verbose logging stream, and that stream is the same as
// `llvm::errs`, then add the `-v` flag so that the driver also prints verbose
// information.
bool inject_v_arg = vlog_stream_ == &llvm::errs();
std::array<llvm::StringRef, 1> v_arg_storage;
llvm::ArrayRef<llvm::StringRef> maybe_v_arg;
if (inject_v_arg) {
v_arg_storage[0] = "-v";
maybe_v_arg = v_arg_storage;
}
CARBON_VLOG("Running Clang driver with arguments: \n");
// Render the arguments into null-terminated C-strings for use by the Clang
// driver. Command lines can get quite long in build systems so this tries to
// minimize the memory allocation overhead.
// Provide the wrapped `clang` path in order to support subprocessing. We also
// set the install directory below.
std::string clang_path = installation_->clang_path();
std::array<llvm::StringRef, 1> exe_arg = {clang_path};
auto args_range =
llvm::concat<const llvm::StringRef>(exe_arg, maybe_v_arg, args);
int total_size = 0;
for (llvm::StringRef arg : args_range) {
// Accumulate both the string size and a null terminator byte.
total_size += arg.size() + 1;
}
// Allocate one chunk of storage for the actual C-strings and a vector of
// pointers into the storage.
llvm::OwningArrayRef<char> cstr_arg_storage(total_size);
llvm::SmallVector<const char*, 64> cstr_args;
cstr_args.reserve(args.size() + inject_v_arg + 1);
for (ssize_t i = 0; llvm::StringRef arg : args_range) {
cstr_args.push_back(&cstr_arg_storage[i]);
memcpy(&cstr_arg_storage[i], arg.data(), arg.size());
i += arg.size();
cstr_arg_storage[i] = '\0';
++i;
}
for (const char* cstr_arg : llvm::ArrayRef(cstr_args)) {
CARBON_VLOG(" '{0}'\n", cstr_arg);
}
if (!args.empty() && args[0].starts_with("-cc1")) {
CARBON_VLOG("Calling clang_main for cc1...");
// 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<char**>(cstr_args.data()), tool_context);
// TODO: Should this be forwarding the full exit code?
return exit_code == 0;
}
CARBON_VLOG("Preparing Clang driver...\n");
// Create the diagnostic options and parse arguments controlling them out of
// our arguments.
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> diagnostic_options =
clang::CreateAndPopulateDiagOpts(cstr_args);
// TODO: We don't yet support serializing diagnostics the way the actual
// `clang` command line does. Unclear if we need to or not, but it would need
// a bit more logic here to set up chained consumers.
clang::TextDiagnosticPrinter diagnostic_client(llvm::errs(),
diagnostic_options.get());
clang::DiagnosticsEngine diagnostics(
diagnostic_ids_, diagnostic_options.get(), &diagnostic_client,
/*ShouldOwnClient=*/false);
clang::ProcessWarningOptions(diagnostics, *diagnostic_options);
clang::driver::Driver driver(clang_path, target_, diagnostics);
// Configure the install directory to find other tools and data files.
//
// We directly override the detected directory as we use a synthetic path
// above. This makes it appear that our binary was in the installed binaries
// directory, and allows finding tools relative to it.
driver.Dir = installation_->llvm_install_bin();
CARBON_VLOG("Setting bin directory to: {0}\n", driver.Dir);
// When there's only one command being run, this will run it in-process.
// However, a `clang` invocation may cause multiple `cc1` invocations, which
// still subprocess. See `InProcess` comment at:
// https://github.com/llvm/llvm-project/blob/86ce8e4504c06ecc3cc42f002ad4eb05cac10925/clang/lib/Driver/Job.cpp#L411-L413
//
// Note the subprocessing will effectively call `clang -cc1`, which turns into
// `carbon-busybox clang -cc1`, which results in an equivalent `clang_main`
// call.
driver.CC1Main = [](llvm::SmallVectorImpl<const char*>& cc1_args) -> int {
// cc1_args[0] will be the `clang_path` so we don't need the prepend arg.
llvm::ToolContext tool_context = {
.Path = cc1_args[0], .PrependArg = "clang", .NeedsPrependArg = false};
return clang_main(cc1_args.size(), const_cast<char**>(cc1_args.data()),
tool_context);
};
std::unique_ptr<clang::driver::Compilation> compilation(
driver.BuildCompilation(cstr_args));
CARBON_CHECK(compilation, "Should always successfully allocate!");
if (compilation->containsError()) {
// These should have been diagnosed by the driver.
return false;
}
CARBON_VLOG("Running Clang driver...\n");
llvm::SmallVector<std::pair<int, const clang::driver::Command*>>
failing_commands;
int result = driver.ExecuteCompilation(*compilation, failing_commands);
// Finish diagnosing any failures before we verbosely log the source of those
// failures.
diagnostic_client.finish();
CARBON_VLOG("Execution result code: {0}\n", result);
for (const auto& [command_result, failing_command] : failing_commands) {
CARBON_VLOG("Failing command '{0}' with code '{1}' was:\n",
failing_command->getExecutable(), command_result);
if (vlog_stream_) {
failing_command->Print(*vlog_stream_, "\n\n", /*Quote=*/true);
}
}
// Return whether the command was executed successfully.
return result == 0 && failing_commands.empty();
}
} // namespace Carbon