diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index 7c3582ee07e5..b0d41362611a 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -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", diff --git a/toolchain/driver/clang_runner.cpp b/toolchain/driver/clang_runner.cpp new file mode 100644 index 000000000000..f047e9480ce1 --- /dev/null +++ b/toolchain/driver/clang_runner.cpp @@ -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 +#include +#include +#include + +#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 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 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 v_arg_storage; + llvm::ArrayRef 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 exe_arg = {exe_name_}; + auto args_range = + llvm::concat(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 cstr_arg_storage(total_size); + llvm::SmallVector 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 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& argv) {}; + std::unique_ptr 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> + 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 diff --git a/toolchain/driver/clang_runner.h b/toolchain/driver/clang_runner.h new file mode 100644 index 000000000000..c43d25e9ae49 --- /dev/null +++ b/toolchain/driver/clang_runner.h @@ -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 args) -> bool; + + private: + llvm::StringRef exe_name_; + std::string exe_path_; + llvm::StringRef target_; + llvm::raw_ostream* vlog_stream_; + + llvm::IntrusiveRefCntPtr diagnostic_ids_; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_DRIVER_CLANG_RUNNER_H_ diff --git a/toolchain/driver/clang_runner_test.cpp b/toolchain/driver/clang_runner_test.cpp new file mode 100644 index 000000000000..b90dfa9683e5 --- /dev/null +++ b/toolchain/driver/clang_runner_test.cpp @@ -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 +#include + +#include +#include +#include + +#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 +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 diff --git a/toolchain/driver/driver.cpp b/toolchain/driver/driver.cpp index 9e594ae4aec3..6e33369eddb8 100644 --- a/toolchain/driver/driver.cpp +++ b/toolchain/driver/driver.cpp @@ -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 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 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 args, Options& options) @@ -411,7 +476,9 @@ auto Driver::RunCommand(llvm::ArrayRef 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::Make(*module_, options_.target, driver_->error_stream_); + std::optional 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 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( - 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( - 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& 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 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 diff --git a/toolchain/driver/driver.h b/toolchain/driver/driver.h index 253320321961..36ff889143d1 100644 --- a/toolchain/driver/driver.h +++ b/toolchain/driver/driver.h @@ -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_;