mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 07:14:42 +01:00
Introduce basic linking with the Clang driver. (#3922)
This adds the most rudimentary support for linking in the Carbon driver by calling out to the Clang driver and letting it do the linking. This is a super minimal version to start with, just enough to be somewhat useful and to exercise calling into Clang for this. Also adds a Clang runner to help run the Clang driver. This is a bit more complete, although it sill needs some significant work. For example, it will need some significant enhancements to be able to compile C++ code, etc. One thing that will likely change here is to better manage streams and work better as a library. For now, this is a bit limited because Clang and LLVM don't provide the necessary support for this.
This commit is contained in:
@@ -13,6 +13,41 @@ filegroup(
|
||||
data = glob(["testdata/**/*.carbon"]),
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "clang_runner",
|
||||
srcs = ["clang_runner.cpp"],
|
||||
hdrs = ["clang_runner.h"],
|
||||
deps = [
|
||||
"//common:command_line",
|
||||
"//common:ostream",
|
||||
"//common:vlog",
|
||||
"@llvm-project//clang:basic",
|
||||
"@llvm-project//clang:driver",
|
||||
"@llvm-project//clang:frontend",
|
||||
"@llvm-project//llvm:Core",
|
||||
"@llvm-project//llvm:Support",
|
||||
"@llvm-project//llvm:TargetParser",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "clang_runner_test",
|
||||
size = "small",
|
||||
srcs = ["clang_runner_test.cpp"],
|
||||
deps = [
|
||||
":clang_runner",
|
||||
"//common:all_llvm_targets",
|
||||
"//common:check",
|
||||
"//common:ostream",
|
||||
"//testing/base:gtest_main",
|
||||
"//testing/base:test_raw_ostream",
|
||||
"@googletest//:gtest",
|
||||
"@llvm-project//llvm:Object",
|
||||
"@llvm-project//llvm:Support",
|
||||
"@llvm-project//llvm:TargetParser",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "driver",
|
||||
srcs = ["driver.cpp"],
|
||||
@@ -20,6 +55,7 @@ cc_library(
|
||||
data = ["//core:prelude"],
|
||||
textual_hdrs = ["flags.def"],
|
||||
deps = [
|
||||
":clang_runner",
|
||||
"//common:command_line",
|
||||
"//common:vlog",
|
||||
"//toolchain/base:value_store",
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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/Path.h"
|
||||
#include "llvm/Support/Program.h"
|
||||
#include "llvm/Support/VirtualFileSystem.h"
|
||||
#include "llvm/TargetParser/Host.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
static auto GetExecutablePath(llvm::StringRef exe_name) -> std::string {
|
||||
// If the `exe_name` isn't already a valid path, look it up.
|
||||
if (!llvm::sys::fs::exists(exe_name)) {
|
||||
if (llvm::ErrorOr<std::string> path_result =
|
||||
llvm::sys::findProgramByName(exe_name)) {
|
||||
return *path_result;
|
||||
}
|
||||
}
|
||||
|
||||
return exe_name.str();
|
||||
}
|
||||
|
||||
ClangRunner::ClangRunner(llvm::StringRef exe_name, llvm::StringRef target,
|
||||
llvm::raw_ostream* vlog_stream)
|
||||
: exe_name_(exe_name),
|
||||
exe_path_(GetExecutablePath(exe_name)),
|
||||
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_CHECK(!args.empty());
|
||||
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.
|
||||
std::array<llvm::StringRef, 1> exe_arg = {exe_name_};
|
||||
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).drop_front()) {
|
||||
CARBON_VLOG() << " '" << cstr_arg << "'\n";
|
||||
}
|
||||
|
||||
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(exe_path_, target_, diagnostics);
|
||||
|
||||
// TODO: Directly run in-process rather than using a subprocess. This is both
|
||||
// more efficient and makes debugging (much) easier. Needs code like:
|
||||
// driver.CC1Main = [](llvm::SmallVectorImpl<const char*>& argv) {};
|
||||
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: " << result << "\n";
|
||||
for (const auto& [command_result, failing_command] : failing_commands) {
|
||||
CARBON_VLOG() << "Failing command '" << failing_command->getExecutable()
|
||||
<< "' with code '" << command_result << "' was:\n";
|
||||
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
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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_RUNNER_H_
|
||||
#define CARBON_TOOLCHAIN_DRIVER_CLANG_RUNNER_H_
|
||||
|
||||
#include "clang/Basic/DiagnosticIDs.h"
|
||||
#include "common/ostream.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
// Runs Clang in a similar fashion to invoking it with the provided arguments on
|
||||
// the command line. We use a textual command line interface to allow easily
|
||||
// incorporating custom command line flags from user invocations that we don't
|
||||
// parse, but will pass transparently along to Clang itself.
|
||||
//
|
||||
// This doesn't literally use a subprocess to invoke Clang; it instead tries to
|
||||
// directly use the Clang command line driver library. We also work to simplify
|
||||
// how that driver operates and invoke it in an opinionated way to get the best
|
||||
// behavior for our expected use cases in the Carbon driver:
|
||||
//
|
||||
// - Minimize canonicalization of file names to try to preserve the paths as
|
||||
// users type them.
|
||||
// - Minimize the use of subprocess invocations which are expensive on some
|
||||
// operating systems. To the extent possible, we try to directly invoke the
|
||||
// Clang logic within this process.
|
||||
// - Provide programmatic API to control defaults of Clang. For example, causing
|
||||
// verbose output.
|
||||
//
|
||||
// Note that this makes the current process behave like running Clang -- it uses
|
||||
// standard output and standard error, and otherwise can only read and write
|
||||
// files based on their names described in the arguments. It doesn't provide any
|
||||
// higher-level abstraction such as streams for inputs or outputs.
|
||||
class ClangRunner {
|
||||
public:
|
||||
// Build a Clang runner that uses the provided `exe_name` and `err_stream`.
|
||||
//
|
||||
// If `verbose` is passed as true, will enable verbose logging to the
|
||||
// `err_stream` both from the runner and Clang itself.
|
||||
ClangRunner(llvm::StringRef exe_name, llvm::StringRef target,
|
||||
llvm::raw_ostream* vlog_stream = nullptr);
|
||||
|
||||
// Run Clang with the provided arguments.
|
||||
auto Run(llvm::ArrayRef<llvm::StringRef> args) -> bool;
|
||||
|
||||
private:
|
||||
llvm::StringRef exe_name_;
|
||||
std::string exe_path_;
|
||||
llvm::StringRef target_;
|
||||
llvm::raw_ostream* vlog_stream_;
|
||||
|
||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagnostic_ids_;
|
||||
};
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_DRIVER_CLANG_RUNNER_H_
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <utility>
|
||||
|
||||
#include "common/check.h"
|
||||
#include "common/ostream.h"
|
||||
#include "llvm/ADT/ScopeExit.h"
|
||||
#include "llvm/Object/Binary.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "llvm/Support/Program.h"
|
||||
#include "llvm/TargetParser/Host.h"
|
||||
#include "testing/base/test_raw_ostream.h"
|
||||
|
||||
namespace Carbon {
|
||||
namespace {
|
||||
|
||||
using ::Carbon::Testing::TestRawOstream;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::StrEq;
|
||||
|
||||
// While these are marked as "internal" APIs, they seem to work and be pretty
|
||||
// widely used for their exact documented behavior.
|
||||
using ::testing::internal::CaptureStderr;
|
||||
using ::testing::internal::CaptureStdout;
|
||||
using ::testing::internal::GetCapturedStderr;
|
||||
using ::testing::internal::GetCapturedStdout;
|
||||
|
||||
// Calls the provided lambda with `stderr` and `stdout` captured and saved into
|
||||
// the provided output parameters. The lambda's result is returned. It is
|
||||
// important to not put anything inside the lambda whose output would be useful
|
||||
// in interpreting test errors such as Google Test assertions as their output
|
||||
// will end up captured as well.
|
||||
template <typename CallableT>
|
||||
static auto RunWithCapturedOutput(std::string& out, std::string& err,
|
||||
CallableT callable) {
|
||||
CaptureStderr();
|
||||
CaptureStdout();
|
||||
auto result = callable();
|
||||
// No need to flush stderr.
|
||||
err = GetCapturedStderr();
|
||||
llvm::outs().flush();
|
||||
out = GetCapturedStdout();
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(ClangRunnerTest, Version) {
|
||||
TestRawOstream test_os;
|
||||
std::string target = llvm::sys::getDefaultTargetTriple();
|
||||
ClangRunner runner("./toolchain/driver/run_clang_test", target, &test_os);
|
||||
|
||||
std::string out;
|
||||
std::string err;
|
||||
EXPECT_TRUE(RunWithCapturedOutput(out, err,
|
||||
[&] { return runner.Run({"--version"}); }));
|
||||
// The arguments to Clang should be part of the verbose log.
|
||||
EXPECT_THAT(test_os.TakeStr(), HasSubstr("--version"));
|
||||
|
||||
// No need to flush stderr, just check its contents.
|
||||
EXPECT_THAT(err, StrEq(""));
|
||||
|
||||
// Flush and get the captured stdout to test that this command worked.
|
||||
// We don't care about any particular version, just that it is printed.
|
||||
EXPECT_THAT(out, HasSubstr("clang version"));
|
||||
// The target should match what we provided.
|
||||
EXPECT_THAT(out, HasSubstr((llvm::Twine("Target: ") + target).str()));
|
||||
// The installation should come from the above path of the test binary.
|
||||
EXPECT_THAT(out, HasSubstr("InstalledDir: ./toolchain/driver"));
|
||||
}
|
||||
|
||||
// Utility to write a test file. We don't need the full power provided here yet,
|
||||
// but we anticipate adding more tests such as compiling basic C++ code in the
|
||||
// future and this provides a basis for building those tests.
|
||||
static auto WriteTestFile(llvm::StringRef name_suffix, llvm::Twine contents)
|
||||
-> std::filesystem::path {
|
||||
std::filesystem::path test_tmpdir;
|
||||
if (char* tmpdir_env = getenv("TEST_TMPDIR"); tmpdir_env != nullptr) {
|
||||
test_tmpdir = std::string(tmpdir_env);
|
||||
} else {
|
||||
test_tmpdir = std::filesystem::temp_directory_path();
|
||||
}
|
||||
|
||||
const auto* unit_test = ::testing::UnitTest::GetInstance();
|
||||
const auto* test_info = unit_test->current_test_info();
|
||||
std::filesystem::path test_file =
|
||||
test_tmpdir / llvm::formatv("{0}_{1}_{2}", test_info->test_suite_name(),
|
||||
test_info->name(), name_suffix)
|
||||
.str();
|
||||
// Make debugging a bit easier by cleaning up any files from previous runs.
|
||||
// This is only necessary when not run in Bazel's test environment.
|
||||
std::filesystem::remove(test_file);
|
||||
CARBON_CHECK(!std::filesystem::exists(test_file));
|
||||
|
||||
{
|
||||
std::error_code ec;
|
||||
llvm::raw_fd_ostream test_file_stream(test_file.string(), ec);
|
||||
CARBON_CHECK(!ec) << "Test file error: " << ec.message();
|
||||
test_file_stream << contents;
|
||||
}
|
||||
return test_file;
|
||||
}
|
||||
|
||||
// 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
|
||||
// is echoed back with plausible contents.
|
||||
//
|
||||
// TODO: We should eventually strive to have a more complete setup that lets us
|
||||
// test more complete Clang functionality here.
|
||||
TEST(ClangRunnerTest, LinkCommandEcho) {
|
||||
// Just create some empty files to use in a synthetic link command below.
|
||||
std::filesystem::path foo_file = WriteTestFile("foo.o", "");
|
||||
std::filesystem::path bar_file = WriteTestFile("bar.o", "");
|
||||
|
||||
std::string verbose_out;
|
||||
llvm::raw_string_ostream verbose_os(verbose_out);
|
||||
std::string target = llvm::sys::getDefaultTargetTriple();
|
||||
ClangRunner runner("./toolchain/driver/run_clang_test", target, &verbose_os);
|
||||
std::string out;
|
||||
std::string err;
|
||||
EXPECT_TRUE(RunWithCapturedOutput(out, err,
|
||||
[&] {
|
||||
return runner.Run({"-###", "-o", "binary",
|
||||
foo_file.string(),
|
||||
bar_file.string()});
|
||||
}))
|
||||
<< "Verbose output from runner:\n"
|
||||
<< verbose_out << "\n";
|
||||
|
||||
// Because we use `-###' above, we should just see the command that the Clang
|
||||
// driver would have run in a subprocess. This will be very architecture
|
||||
// dependent and have lots of variety, but we expect to see both file strings
|
||||
// in it the command at least.
|
||||
EXPECT_THAT(err, HasSubstr(foo_file.string())) << err;
|
||||
EXPECT_THAT(err, HasSubstr(bar_file.string())) << err;
|
||||
|
||||
// And no non-stderr output should be produced.
|
||||
EXPECT_THAT(out, StrEq(""));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace Carbon
|
||||
+153
-27
@@ -17,10 +17,12 @@
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/TargetParser/Host.h"
|
||||
#include "llvm/TargetParser/Triple.h"
|
||||
#include "toolchain/base/value_store.h"
|
||||
#include "toolchain/check/check.h"
|
||||
#include "toolchain/codegen/codegen.h"
|
||||
#include "toolchain/diagnostics/sorting_diagnostic_consumer.h"
|
||||
#include "toolchain/driver/clang_runner.h"
|
||||
#include "toolchain/lex/lex.h"
|
||||
#include "toolchain/lower/lower.h"
|
||||
#include "toolchain/parse/parse.h"
|
||||
@@ -68,6 +70,30 @@ auto Driver::FindPreludeFiles(llvm::StringRef data_dir,
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Driver::CodegenOptions {
|
||||
void Build(CommandLine::CommandBuilder& b) {
|
||||
b.AddStringOption(
|
||||
{
|
||||
.name = "target",
|
||||
.help = R"""(
|
||||
Select a target platform. Uses the LLVM target syntax. Also known as a "triple"
|
||||
for historical reasons.
|
||||
|
||||
This corresponds to the `target` flag to Clang and accepts the same strings
|
||||
documented there:
|
||||
https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.Default(host);
|
||||
arg_b.Set(&target);
|
||||
});
|
||||
}
|
||||
|
||||
std::string host = llvm::sys::getDefaultTargetTriple();
|
||||
llvm::StringRef target;
|
||||
};
|
||||
|
||||
struct Driver::CompileOptions {
|
||||
static constexpr CommandLine::CommandInfo Info = {
|
||||
.name = "compile",
|
||||
@@ -114,7 +140,7 @@ can be written to standard output as these phases progress.
|
||||
return out;
|
||||
}
|
||||
|
||||
void Build(CommandLine::CommandBuilder& b) {
|
||||
void Build(CommandLine::CommandBuilder& b, CodegenOptions& codegen_options) {
|
||||
b.AddStringPositionalArg(
|
||||
{
|
||||
.name = "FILE",
|
||||
@@ -168,22 +194,10 @@ object output can be forced by enabling `--force-obj-output`.
|
||||
},
|
||||
[&](auto& arg_b) { arg_b.Set(&output_filename); });
|
||||
|
||||
b.AddStringOption(
|
||||
{
|
||||
.name = "target",
|
||||
.help = R"""(
|
||||
Select a target platform. Uses the LLVM target syntax. Also known as a "triple"
|
||||
for historical reasons.
|
||||
|
||||
This corresponds to the `target` flag to Clang and accepts the same strings
|
||||
documented there:
|
||||
https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.Default(host);
|
||||
arg_b.Set(&target);
|
||||
});
|
||||
// Include the common code generation options at this point to render it
|
||||
// after the more common options above, but before the more unusual options
|
||||
// below.
|
||||
codegen_options.Build(b);
|
||||
|
||||
b.AddFlag(
|
||||
{
|
||||
@@ -317,9 +331,6 @@ Excludes files with the given prefix from dumps.
|
||||
|
||||
Phase phase;
|
||||
|
||||
std::string host = llvm::sys::getDefaultTargetTriple();
|
||||
llvm::StringRef target;
|
||||
|
||||
llvm::StringRef output_filename;
|
||||
llvm::SmallVector<llvm::StringRef> input_filenames;
|
||||
|
||||
@@ -340,6 +351,52 @@ Excludes files with the given prefix from dumps.
|
||||
llvm::StringRef exclude_dump_file_prefix;
|
||||
};
|
||||
|
||||
struct Driver::LinkOptions {
|
||||
static constexpr CommandLine::CommandInfo Info = {
|
||||
.name = "link",
|
||||
.help = R"""(
|
||||
Link Carbon executables.
|
||||
|
||||
This subcommand links Carbon executables by combining object files.
|
||||
|
||||
TODO: Support linking binary libraries, both archives and shared libraries.
|
||||
TODO: Support linking against binary libraries.
|
||||
)""",
|
||||
};
|
||||
|
||||
void Build(CommandLine::CommandBuilder& b, CodegenOptions& codegen_options) {
|
||||
b.AddStringPositionalArg(
|
||||
{
|
||||
.name = "OBJECT_FILE",
|
||||
.help = R"""(
|
||||
The input object files.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Append(&object_filenames);
|
||||
});
|
||||
|
||||
b.AddStringOption(
|
||||
{
|
||||
.name = "output",
|
||||
.value_name = "FILE",
|
||||
.help = R"""(
|
||||
The linked file name. The output is always a linked binary.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&output_filename);
|
||||
});
|
||||
|
||||
codegen_options.Build(b);
|
||||
}
|
||||
|
||||
llvm::StringRef output_filename;
|
||||
llvm::SmallVector<llvm::StringRef> object_filenames;
|
||||
};
|
||||
|
||||
struct Driver::Options {
|
||||
static constexpr CommandLine::CommandInfo Info = {
|
||||
.name = "carbon",
|
||||
@@ -362,6 +419,7 @@ For questions, issues, or bug reports, please use our GitHub project:
|
||||
|
||||
enum class Subcommand : int8_t {
|
||||
Compile,
|
||||
Link,
|
||||
};
|
||||
|
||||
void Build(CommandLine::CommandBuilder& b) {
|
||||
@@ -375,17 +433,24 @@ For questions, issues, or bug reports, please use our GitHub project:
|
||||
|
||||
b.AddSubcommand(CompileOptions::Info,
|
||||
[&](CommandLine::CommandBuilder& sub_b) {
|
||||
compile_options.Build(sub_b);
|
||||
compile_options.Build(sub_b, codegen_options);
|
||||
sub_b.Do([&] { subcommand = Subcommand::Compile; });
|
||||
});
|
||||
|
||||
b.AddSubcommand(LinkOptions::Info, [&](CommandLine::CommandBuilder& sub_b) {
|
||||
link_options.Build(sub_b, codegen_options);
|
||||
sub_b.Do([&] { subcommand = Subcommand::Link; });
|
||||
});
|
||||
|
||||
b.RequiresSubcommand();
|
||||
}
|
||||
|
||||
bool verbose;
|
||||
Subcommand subcommand;
|
||||
|
||||
CodegenOptions codegen_options;
|
||||
CompileOptions compile_options;
|
||||
LinkOptions link_options;
|
||||
};
|
||||
|
||||
auto Driver::ParseArgs(llvm::ArrayRef<llvm::StringRef> args, Options& options)
|
||||
@@ -411,7 +476,9 @@ auto Driver::RunCommand(llvm::ArrayRef<llvm::StringRef> args) -> RunResult {
|
||||
|
||||
switch (options.subcommand) {
|
||||
case Options::Subcommand::Compile:
|
||||
return Compile(options.compile_options);
|
||||
return Compile(options.compile_options, options.codegen_options);
|
||||
case Options::Subcommand::Link:
|
||||
return Link(options.link_options, options.codegen_options);
|
||||
}
|
||||
llvm_unreachable("All subcommands handled!");
|
||||
}
|
||||
@@ -456,10 +523,12 @@ auto Driver::ValidateCompileOptions(const CompileOptions& options) const
|
||||
class Driver::CompilationUnit {
|
||||
public:
|
||||
explicit CompilationUnit(Driver* driver, const CompileOptions& options,
|
||||
const CodegenOptions& codegen_options,
|
||||
DiagnosticConsumer* consumer,
|
||||
llvm::StringRef input_filename)
|
||||
: driver_(driver),
|
||||
options_(options),
|
||||
codegen_options_(codegen_options),
|
||||
input_filename_(input_filename),
|
||||
vlog_stream_(driver_->vlog_stream_) {
|
||||
if (vlog_stream_ != nullptr || options_.stream_errors) {
|
||||
@@ -605,8 +674,8 @@ class Driver::CompilationUnit {
|
||||
private:
|
||||
// Do codegen. Returns true on success.
|
||||
auto RunCodeGenHelper() -> bool {
|
||||
std::optional<CodeGen> codegen =
|
||||
CodeGen::Make(*module_, options_.target, driver_->error_stream_);
|
||||
std::optional<CodeGen> codegen = CodeGen::Make(
|
||||
*module_, codegen_options_.target, driver_->error_stream_);
|
||||
if (!codegen) {
|
||||
return false;
|
||||
}
|
||||
@@ -689,6 +758,7 @@ class Driver::CompilationUnit {
|
||||
Driver* driver_;
|
||||
SharedValueStores value_stores_;
|
||||
const CompileOptions& options_;
|
||||
const CodegenOptions& codegen_options_;
|
||||
std::string input_filename_;
|
||||
|
||||
// Copied from driver_ for CARBON_VLOG.
|
||||
@@ -709,7 +779,8 @@ class Driver::CompilationUnit {
|
||||
std::unique_ptr<llvm::Module> module_;
|
||||
};
|
||||
|
||||
auto Driver::Compile(const CompileOptions& options) -> RunResult {
|
||||
auto Driver::Compile(const CompileOptions& options,
|
||||
const CodegenOptions& codegen_options) -> RunResult {
|
||||
if (!ValidateCompileOptions(options)) {
|
||||
return {.success = false};
|
||||
}
|
||||
@@ -733,13 +804,13 @@ auto Driver::Compile(const CompileOptions& options) -> RunResult {
|
||||
// Add the prelude files.
|
||||
for (const auto& input_filename : prelude) {
|
||||
units.push_back(std::make_unique<CompilationUnit>(
|
||||
this, options, &stream_consumer, input_filename));
|
||||
this, options, codegen_options, &stream_consumer, input_filename));
|
||||
}
|
||||
|
||||
// Add the input source files.
|
||||
for (const auto& input_filename : options.input_filenames) {
|
||||
units.push_back(std::make_unique<CompilationUnit>(
|
||||
this, options, &stream_consumer, input_filename));
|
||||
this, options, codegen_options, &stream_consumer, input_filename));
|
||||
}
|
||||
|
||||
auto on_exit = llvm::make_scope_exit([&]() {
|
||||
@@ -829,4 +900,59 @@ auto Driver::Compile(const CompileOptions& options) -> RunResult {
|
||||
return make_result();
|
||||
}
|
||||
|
||||
static void AddOSFlags(llvm::StringRef target,
|
||||
llvm::SmallVectorImpl<llvm::StringRef>& args) {
|
||||
llvm::Triple triple(target);
|
||||
switch (triple.getOS()) {
|
||||
case llvm::Triple::Darwin:
|
||||
case llvm::Triple::MacOSX:
|
||||
// On macOS we need to set the sysroot to a viable SDK. Currently, this
|
||||
// hard codes the path to be the unversioned symlink. The prefix is also
|
||||
// hard coded in Homebrew and so this seems likely to work reasonably
|
||||
// well. Homebrew and I suspect the Xcode Clang both have this hard coded
|
||||
// at build time, so this seems reasonably safe but we can revisit if/when
|
||||
// needed.
|
||||
args.push_back(
|
||||
"--sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk");
|
||||
// We also need to insist on a modern linker, otherwise the driver tries
|
||||
// too old and deprecated flags. The specific number here comes from an
|
||||
// inspection of the Clang driver source code to understand where features
|
||||
// were enabled, and this appears to be the latest version to control
|
||||
// driver behavior.
|
||||
//
|
||||
// TODO: We should replace this with use of `lld` eventually.
|
||||
args.push_back("-mlinker-version=705");
|
||||
break;
|
||||
|
||||
default:
|
||||
// By default, just let the Clang driver handle everything.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto Driver::Link(const LinkOptions& options,
|
||||
const CodegenOptions& codegen_options) -> RunResult {
|
||||
// TODO: Currently we use the Clang driver to link. This works well on Unix
|
||||
// OSes but we likely need to directly build logic to invoke `link.exe` on
|
||||
// Windows where `cl.exe` doesn't typically cover that logic.
|
||||
|
||||
// Use a reasonably large small vector here to minimize allocations. We expect
|
||||
// to link reasonably large numbers of object files.
|
||||
llvm::SmallVector<llvm::StringRef, 128> clang_args;
|
||||
|
||||
// We link using a C++ mode of the driver.
|
||||
clang_args.push_back("--driver-mode=g++");
|
||||
|
||||
// Add OS-specific flags based on the target.
|
||||
AddOSFlags(codegen_options.target, clang_args);
|
||||
|
||||
clang_args.push_back("-o");
|
||||
clang_args.push_back(options.output_filename);
|
||||
clang_args.append(options.object_filenames.begin(),
|
||||
options.object_filenames.end());
|
||||
|
||||
ClangRunner runner("FIXME", codegen_options.target, vlog_stream_);
|
||||
return {.success = runner.Run(clang_args)};
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
@@ -57,7 +57,9 @@ class Driver {
|
||||
|
||||
private:
|
||||
struct Options;
|
||||
struct CodegenOptions;
|
||||
struct CompileOptions;
|
||||
struct LinkOptions;
|
||||
class CompilationUnit;
|
||||
|
||||
// Delegates to the command line library to parse the arguments and store the
|
||||
@@ -70,7 +72,12 @@ class Driver {
|
||||
auto ValidateCompileOptions(const CompileOptions& options) const -> bool;
|
||||
|
||||
// Implements the compile subcommand of the driver.
|
||||
auto Compile(const CompileOptions& options) -> RunResult;
|
||||
auto Compile(const CompileOptions& options,
|
||||
const CodegenOptions& codegen_options) -> RunResult;
|
||||
|
||||
// Implements the link subcommand of the driver.
|
||||
auto Link(const LinkOptions& options, const CodegenOptions& codegen_options)
|
||||
-> RunResult;
|
||||
|
||||
// The filesystem for source code.
|
||||
llvm::vfs::FileSystem& fs_;
|
||||
|
||||
Reference in New Issue
Block a user