From 27c99315de2c4958d7f605fd1d045de20823e3f0 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Wed, 19 Feb 2025 23:04:52 +0000 Subject: [PATCH] Add subcommands and busybox entry points for LLVM tools This adds support for most of the remaining LLVM command line tools using a generic, generated wrapper. The subcommand interface for these is (much) less interesting than our other subcommands, but it gives us a uniform and consistent layer. Note that I structured these as nested sub-sub-commands below an `llvm` subcommand because of an expectation that we will want to add more, and ones that don't use this generic layer. Some concrete future work: - Add the `opt` and `llc` tools as subcommands for easier debugging and experimentation with LLVM IR output from Carbon's toolchain. - Potentially sink `lld` below the `llvm` layer given that it has significantly less user visibility than commands like `clang`. Unfortunately, the current driver subcommand APIs make nested subcommands awkward. I've added a somewhat rough hack here to let the LLVM tools go in, but there are some TODOs that I want to address in a follow-up that works to adjust the structure of this code to be more conducive to nesting like this. --- scripts/fix_cc_deps.py | 4 +- toolchain/base/BUILD | 18 +++ toolchain/base/llvm_tools.bzl | 116 ++++++++++++++++++ toolchain/base/llvm_tools.cpp | 41 +++++++ toolchain/base/llvm_tools.h | 67 ++++++++++ toolchain/driver/BUILD | 39 ++++++ toolchain/driver/driver.cpp | 3 + toolchain/driver/driver_subcommand.h | 25 +++- toolchain/driver/llvm_runner.cpp | 38 ++++++ toolchain/driver/llvm_runner.h | 26 ++++ toolchain/driver/llvm_runner_test.cpp | 97 +++++++++++++++ toolchain/driver/llvm_subcommand.cpp | 65 ++++++++++ toolchain/driver/llvm_subcommand.h | 57 +++++++++ .../driver/testdata/fail_llvm_fuzzing.carbon | 15 +++ toolchain/install/BUILD | 14 ++- toolchain/install/busybox_main.cpp | 9 ++ toolchain/install/install_paths.cpp | 8 ++ toolchain/install/install_paths.h | 4 + 18 files changed, 639 insertions(+), 7 deletions(-) create mode 100644 toolchain/base/llvm_tools.bzl create mode 100644 toolchain/base/llvm_tools.cpp create mode 100644 toolchain/base/llvm_tools.h create mode 100644 toolchain/driver/llvm_runner.cpp create mode 100644 toolchain/driver/llvm_runner.h create mode 100644 toolchain/driver/llvm_runner_test.cpp create mode 100644 toolchain/driver/llvm_subcommand.cpp create mode 100644 toolchain/driver/llvm_subcommand.h create mode 100644 toolchain/driver/testdata/fail_llvm_fuzzing.carbon diff --git a/scripts/fix_cc_deps.py b/scripts/fix_cc_deps.py index fd4d8bba73b69..d8a75fd50bd4b 100755 --- a/scripts/fix_cc_deps.py +++ b/scripts/fix_cc_deps.py @@ -68,7 +68,9 @@ class RuleChoice(NamedTuple): } IGNORE_SOURCE_FILE_REGEX = re.compile( - r"^(third_party/clangd.*|common/version.*\.cpp|.*_autogen_manifest\.cpp)$" + r"^(third_party/clangd.*|common/version.*\.cpp" + r"|.*_autogen_manifest\.cpp" + r"|toolchain/base/llvm_tools.def)$" ) diff --git a/toolchain/base/BUILD b/toolchain/base/BUILD index 0a345a6185491..29f9612abcc12 100644 --- a/toolchain/base/BUILD +++ b/toolchain/base/BUILD @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("llvm_tools.bzl", "LLVM_TOOLS", "generate_llvm_tools_def") package(default_visibility = ["//visibility:public"]) @@ -120,6 +121,23 @@ cc_test( ], ) +generate_llvm_tools_def( + name = "llvm_tools_def", + out = "llvm_tools.def", +) + +cc_library( + name = "llvm_tools", + srcs = ["llvm_tools.cpp"], + hdrs = ["llvm_tools.h"], + deps = [ + ":llvm_tools_def", + "//common:command_line", + "//common:enum_base", + "@llvm-project//llvm:Support", + ] + [info.lib for info in LLVM_TOOLS.values()], +) + cc_library( name = "shared_value_stores", hdrs = ["shared_value_stores.h"], diff --git a/toolchain/base/llvm_tools.bzl b/toolchain/base/llvm_tools.bzl new file mode 100644 index 0000000000000..796e582ab2d2e --- /dev/null +++ b/toolchain/base/llvm_tools.bzl @@ -0,0 +1,116 @@ +# 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 + +"""Provides variables and rules to automate working with LLVM's CLI tools.""" + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +LLVM_TOOLS = { + "ar": struct(bin_name = "llvm-ar", lib = "@llvm-project//llvm:llvm-ar-lib"), + "cgdata": struct(bin_name = "llvm-cgdata", lib = "@llvm-project//llvm:llvm-cgdata-lib"), + "cxxfilt": struct(bin_name = "llvm-cxxfilt", lib = "@llvm-project//llvm:llvm-cxxfilt-lib"), + "debuginfod-find": struct(bin_name = "llvm-debuginfod-find", lib = "@llvm-project//llvm:llvm-debuginfod-find-lib"), + "dsymutil": struct(bin_name = "dsymutil", lib = "@llvm-project//llvm:dsymutil-lib"), + "dwp": struct(bin_name = "llvm-dwp", lib = "@llvm-project//llvm:llvm-dwp-lib"), + "gsymutil": struct(bin_name = "llvm-gsymutil", lib = "@llvm-project//llvm:llvm-gsymutil-lib"), + "ifs": struct(bin_name = "llvm-ifs", lib = "@llvm-project//llvm:llvm-ifs-lib"), + "libtool-darwin": struct(bin_name = "llvm-libtool-darwin", lib = "@llvm-project//llvm:llvm-libtool-darwin-lib"), + "lipo": struct(bin_name = "llvm-lipo", lib = "@llvm-project//llvm:llvm-lipo-lib"), + "ml": struct(bin_name = "llvm-ml", lib = "@llvm-project//llvm:llvm-ml-lib"), + "mt": struct(bin_name = "llvm-mt", lib = "@llvm-project//llvm:llvm-mt-lib"), + "nm": struct(bin_name = "llvm-nm", lib = "@llvm-project//llvm:llvm-nm-lib"), + "objcopy": struct(bin_name = "llvm-objcopy", lib = "@llvm-project//llvm:llvm-objcopy-lib"), + "objdump": struct(bin_name = "llvm-objdump", lib = "@llvm-project//llvm:llvm-objdump-lib"), + "profdata": struct(bin_name = "llvm-profdata", lib = "@llvm-project//llvm:llvm-profdata-lib"), + "rc": struct(bin_name = "llvm-rc", lib = "@llvm-project//llvm:llvm-rc-lib"), + "readobj": struct(bin_name = "llvm-readobj", lib = "@llvm-project//llvm:llvm-readobj-lib"), + "sancov": struct(bin_name = "sancov", lib = "@llvm-project//llvm:sancov-lib"), + "size": struct(bin_name = "llvm-size", lib = "@llvm-project//llvm:llvm-size-lib"), + "symbolizer": struct(bin_name = "llvm-symbolizer", lib = "@llvm-project//llvm:llvm-symbolizer-lib"), +} + +LLVM_TOOL_ALIASES = { + "ar": ["ranlib", "lib", "dlltool"], + #"cxxfilt": ["c++filt"], + "objcopy": ["bitcode-strip", "install-name-tool", "strip"], + "objdump": ["otool"], + "rc": ["windres"], + "readobj": ["readelf"], + "symbolizer": ["addr2line"], +} + +_DEF_FILE_TEMPLATE = """ +// 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 +// +// This is a generated X-macro header for defining LLVM tools. +// +// See toolchain/driver/llvm_tools.bzl for more details. + +#ifndef CARBON_LLVM_TOOL +#define CARBON_LLVM_TOOL(Id, Name, BinName, MainFn) +#endif + +#ifndef CARBON_LLVM_MAIN_TOOL +#define CARBON_LLVM_MAIN_TOOL(Id, Name, BinName, MainFn) \\ + CARBON_LLVM_TOOL(Id, Name, BinName, MainFn) +#endif + +#ifndef CARBON_LLVM_ALIAS_TOOL +#define CARBON_LLVM_ALIAS_TOOL(Id, Name, BinName, MainFn) \\ + CARBON_LLVM_TOOL(Id, Name, BinName, MainFn) +#endif + +{} + +#undef CARBON_LLVM_TOOL +#undef CARBON_LLVM_MAIN_TOOL +#undef CARBON_LLVM_ALIAS_TOOL +""" + +_DEF_MACRO_TEMPLATE = """ +CARBON_LLVM_{kind}TOOL({id}, "{name}", "{bin_name}", {main_fn}) +""".strip() + +def _build_def_macro(kind, name, bin_name, main_info): + id = "".join([w.title() for w in name.split("-")]) + main_fn = main_info.bin_name.replace("-", "_") + "_main" + return _DEF_MACRO_TEMPLATE.format( + kind = kind, + id = id, + name = name, + bin_name = bin_name, + main_fn = main_fn, + ) + +def _generate_llvm_tools_def_rule(ctx): + def_lines = [] + + for name, tool_info in LLVM_TOOLS.items(): + def_lines.append(_build_def_macro("MAIN_", name, tool_info.bin_name, tool_info)) + + for target, aliases in LLVM_TOOL_ALIASES.items(): + tool_info = LLVM_TOOLS[target] + for alias in aliases: + bin_name = "llvm-" + alias + def_lines.append(_build_def_macro("ALIAS_", alias, bin_name, tool_info)) + + def_file = ctx.actions.declare_file(ctx.label.name) + ctx.actions.write(def_file, _DEF_FILE_TEMPLATE.format("\n".join(def_lines))) + return [DefaultInfo(files = depset([def_file]))] + +generate_llvm_tools_def_rule = rule( + implementation = _generate_llvm_tools_def_rule, + attrs = {}, +) + +def generate_llvm_tools_def(name, out, **kwargs): + """Generates the LLVM tools `.def` file.""" + generate_llvm_tools_def_rule(name = out) + cc_library( + name = name, + textual_hdrs = [out], + **kwargs + ) diff --git a/toolchain/base/llvm_tools.cpp b/toolchain/base/llvm_tools.cpp new file mode 100644 index 0000000000000..13088fa600d02 --- /dev/null +++ b/toolchain/base/llvm_tools.cpp @@ -0,0 +1,41 @@ +// 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/base/llvm_tools.h" + +#include "common/command_line.h" + +// NOLINTBEGIN(readability-identifier-naming): External library name. +#define CARBON_LLVM_MAIN_TOOL(Identifier, Name, BinName, MainFn) \ + extern auto MainFn(int argc, char** argv, const llvm::ToolContext& context) \ + -> int; +#include "toolchain/base/llvm_tools.def" +// NOLINTEND(readability-identifier-naming): External library name. + +namespace Carbon { + +CARBON_DEFINE_ENUM_CLASS_NAMES(LLVMTool) = { +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) Name, +#include "toolchain/base/llvm_tools.def" +}; + +constexpr llvm::StringLiteral LLVMTool::BinNames[] = { +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) BinName, +#include "toolchain/base/llvm_tools.def" +}; + +constexpr LLVMTool::MainFnT* LLVMTool::MainFns[] = { +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) &::MainFn, +#include "toolchain/base/llvm_tools.def" +}; + +constexpr CommandLine::CommandInfo LLVMTool::SubcommandInfos[] = { +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) \ + {.name = Name, \ + .help = "Runs the LLVM " Name \ + " command line tool with the provided arguments."}, +#include "toolchain/base/llvm_tools.def" +}; + +} // namespace Carbon diff --git a/toolchain/base/llvm_tools.h b/toolchain/base/llvm_tools.h new file mode 100644 index 0000000000000..3eec764b4823b --- /dev/null +++ b/toolchain/base/llvm_tools.h @@ -0,0 +1,67 @@ +// 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_BASE_LLVM_TOOLS_H_ +#define CARBON_TOOLCHAIN_BASE_LLVM_TOOLS_H_ + +#include + +#include "common/command_line.h" +#include "common/enum_base.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/LLVMDriver.h" + +namespace Carbon { + +CARBON_DEFINE_RAW_ENUM_CLASS(LLVMTool, uint8_t) { +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) \ + CARBON_RAW_ENUM_ENUMERATOR(Identifier) +#include "toolchain/base/llvm_tools.def" +}; + +class LLVMTool : public CARBON_ENUM_BASE(LLVMTool) { + public: +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) \ + CARBON_ENUM_CONSTANT_DECL(Identifier) +#include "toolchain/base/llvm_tools.def" + + static const llvm::ArrayRef Tools; + + using MainFnT = auto(int argc, char** argv, const llvm::ToolContext& context) + -> int; + + using EnumBase::EnumBase; + + auto bin_name() const -> llvm::StringLiteral { return BinNames[AsInt()]; } + + auto main_fn() const -> MainFnT* { return MainFns[AsInt()]; } + + auto subcommand_info() const -> const CommandLine::CommandInfo& { + return SubcommandInfos[AsInt()]; + } + + private: + static const LLVMTool ToolsStorage[]; + + static const llvm::StringLiteral BinNames[]; + static MainFnT* const MainFns[]; + + static const CommandLine::CommandInfo SubcommandInfos[]; +}; + +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) \ + CARBON_ENUM_CONSTANT_DEFINITION(LLVMTool, Identifier) +#include "toolchain/base/llvm_tools.def" + +constexpr LLVMTool LLVMTool::ToolsStorage[] = { +#define CARBON_LLVM_TOOL(Identifier, Name, BinName, MainFn) \ + LLVMTool::Identifier, +#include "toolchain/base/llvm_tools.def" +}; +constexpr llvm::ArrayRef LLVMTool::Tools = ToolsStorage; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_BASE_LLVM_TOOLS_H_ diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index 375312692146b..a8bdda427112b 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -104,6 +104,8 @@ cc_library( "link_subcommand.h", "lld_subcommand.cpp", "lld_subcommand.h", + "llvm_subcommand.cpp", + "llvm_subcommand.h", ], hdrs = [ "driver.h", @@ -116,12 +118,14 @@ cc_library( deps = [ ":clang_runner", ":lld_runner", + ":llvm_runner", "//common:command_line", "//common:error", "//common:ostream", "//common:raw_string_ostream", "//common:version", "//common:vlog", + "//toolchain/base:llvm_tools", "//toolchain/base:pretty_stack_trace_function", "//toolchain/base:shared_value_stores", "//toolchain/base:timings", @@ -223,6 +227,41 @@ cc_test( ], ) +cc_library( + name = "llvm_runner", + srcs = ["llvm_runner.cpp"], + hdrs = ["llvm_runner.h"], + deps = [ + ":tool_runner_base", + "//common:ostream", + "//common:vlog", + "//toolchain/base:llvm_tools", + "//toolchain/install:install_paths", + "@llvm-project//lld:Common", + "@llvm-project//lld:ELF", + "@llvm-project//lld:MachO", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "llvm_runner_test", + size = "small", + srcs = ["llvm_runner_test.cpp"], + env = cc_env(), + deps = [ + ":llvm_runner", + "//common:all_llvm_targets", + "//common:ostream", + "//common:raw_string_ostream", + "//testing/base:capture_std_streams", + "//testing/base:global_exe_path", + "//testing/base:gtest_main", + "@googletest//:gtest", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "tool_runner_base", srcs = ["tool_runner_base.cpp"], diff --git a/toolchain/driver/driver.cpp b/toolchain/driver/driver.cpp index 850c029c09bca..278cbc0a23a48 100644 --- a/toolchain/driver/driver.cpp +++ b/toolchain/driver/driver.cpp @@ -16,6 +16,7 @@ #include "toolchain/driver/language_server_subcommand.h" #include "toolchain/driver/link_subcommand.h" #include "toolchain/driver/lld_subcommand.h" +#include "toolchain/driver/llvm_subcommand.h" namespace Carbon { @@ -35,6 +36,7 @@ struct Options { LanguageServerSubcommand language_server; LinkSubcommand link; LldSubcommand lld; + LLVMSubcommand llvm; // On success, this is set to the subcommand to run. DriverSubcommand* selected_subcommand = nullptr; @@ -92,6 +94,7 @@ applies to each message that forms a diagnostic, not just the primary message. language_server.AddTo(b, &selected_subcommand); link.AddTo(b, &selected_subcommand); lld.AddTo(b, &selected_subcommand); + llvm.AddTo(b, &selected_subcommand); b.RequiresSubcommand(); } diff --git a/toolchain/driver/driver_subcommand.h b/toolchain/driver/driver_subcommand.h index e9d6ff2df3a8f..dff386fa620a6 100644 --- a/toolchain/driver/driver_subcommand.h +++ b/toolchain/driver/driver_subcommand.h @@ -32,16 +32,31 @@ class DriverSubcommand { // the subcommand is in use. auto AddTo(CommandLine::CommandBuilder& b, DriverSubcommand** selected_subcommand) -> void { - b.AddSubcommand(info_, [this, selected_subcommand]( - CommandLine::CommandBuilder& sub_b) { - BuildOptions(sub_b); - sub_b.Do([this, selected_subcommand] { *selected_subcommand = this; }); - }); + b.AddSubcommand( + info_, [this, selected_subcommand](CommandLine::CommandBuilder& sub_b) { + BuildOptionsAndSetAction(sub_b, selected_subcommand); + }); } // Adds command line options. virtual auto BuildOptions(CommandLine::CommandBuilder& b) -> void = 0; + // Adds command line options and registers the `Run` method to be called. + // + // For more complex subcommands that need to control the action this method + // can be overridden and bypass the simple API above. + // + // TODO: This isn't the most elegant way to manage this. There is probably a + // better factoring / organization of the driver subcommand infrastructure + // that bakes in the needed flexibility here, but that was deferred for a + // future refactoring. + virtual auto BuildOptionsAndSetAction(CommandLine::CommandBuilder& b, + DriverSubcommand** selected_subcommand) + -> void { + BuildOptions(b); + b.Do([this, selected_subcommand] { *selected_subcommand = this; }); + } + // Runs the command. virtual auto Run(DriverEnv& driver_env) -> DriverResult = 0; diff --git a/toolchain/driver/llvm_runner.cpp b/toolchain/driver/llvm_runner.cpp new file mode 100644 index 0000000000000..0f9c290b500ee --- /dev/null +++ b/toolchain/driver/llvm_runner.cpp @@ -0,0 +1,38 @@ +// 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/llvm_runner.h" + +#include +#include +#include +#include + +#include "common/vlog.h" +#include "lld/Common/Driver.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" + +namespace Carbon { + +auto LLVMRunner::Run(LLVMTool tool, llvm::ArrayRef args) + -> bool { + std::string path = installation_->llvm_tool_path(tool); + + // Allocate one chunk of storage for the actual C-strings and a vector of + // pointers into the storage. + llvm::OwningArrayRef cstr_arg_storage; + llvm::SmallVector cstr_args = BuildCStrArgs( + tool.name(), path, /*verbose_flag=*/std::nullopt, args, cstr_arg_storage); + + CARBON_VLOG("Running LLVM's {0} tool...\n", tool.name()); + int exit_code = tool.main_fn()( + cstr_args.size(), const_cast(cstr_args.data()), + {.Path = path.c_str(), .PrependArg = nullptr, .NeedsPrependArg = false}); + + // TODO: Should this be forwarding the full exit code? + return exit_code == 0; +} + +} // namespace Carbon diff --git a/toolchain/driver/llvm_runner.h b/toolchain/driver/llvm_runner.h new file mode 100644 index 0000000000000..01db06015d430 --- /dev/null +++ b/toolchain/driver/llvm_runner.h @@ -0,0 +1,26 @@ +// 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_LLVM_RUNNER_H_ +#define CARBON_TOOLCHAIN_DRIVER_LLVM_RUNNER_H_ + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "toolchain/base/llvm_tools.h" +#include "toolchain/driver/tool_runner_base.h" + +namespace Carbon { + +// Runs any of the LLVM tools in a manner similar to invoking it with the +// provided arguments. +class LLVMRunner : ToolRunnerBase { + public: + using ToolRunnerBase::ToolRunnerBase; + + auto Run(LLVMTool tool, llvm::ArrayRef args) -> bool; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_DRIVER_LLVM_RUNNER_H_ diff --git a/toolchain/driver/llvm_runner_test.cpp b/toolchain/driver/llvm_runner_test.cpp new file mode 100644 index 0000000000000..75d90d71686ae --- /dev/null +++ b/toolchain/driver/llvm_runner_test.cpp @@ -0,0 +1,97 @@ +// 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/llvm_runner.h" + +#include +#include + +#include "common/ostream.h" +#include "common/raw_string_ostream.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/FormatVariadic.h" +#include "testing/base/capture_std_streams.h" +#include "testing/base/global_exe_path.h" + +namespace Carbon { +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; +using ::testing::StrEq; + +TEST(LLVMRunnerTest, Version) { + RawStringOstream test_os; + const auto install_paths = + InstallPaths::MakeForBazelRunfiles(Testing::GetExePath()); + LLVMRunner runner(&install_paths, &test_os); + + std::string out; + std::string err; + + for (LLVMTool tool : LLVMTool::Tools) { + std::string test_flag = "--version"; + std::string expected_out = "LLVM version"; + + // Handle any special requirements for specific tools. + switch (tool) { + case LLVMTool::Addr2Line: + case LLVMTool::BitcodeStrip: + case LLVMTool::Cgdata: + case LLVMTool::DebuginfodFind: + case LLVMTool::Dwp: + case LLVMTool::Gsymutil: + case LLVMTool::Ifs: + case LLVMTool::InstallNameTool: + case LLVMTool::Lipo: + case LLVMTool::Objcopy: + case LLVMTool::Profdata: + case LLVMTool::Sancov: + case LLVMTool::Strip: + case LLVMTool::Symbolizer: + case LLVMTool::Windres: + // TODO: These tools are not well behaved when invoked as a library, + // typically directly calling `exit` on some or all code paths. We + // should see if they can be fixed upstream and re-enable testing. + continue; + + case LLVMTool::Dlltool: + case LLVMTool::Rc: + // No good flags to generically test these tools. + continue; + + case LLVMTool::Lib: + test_flag = "/help"; + expected_out = "LLVM Lib"; + break; + + case LLVMTool::Ml: + test_flag = "/help"; + expected_out = "LLVM MASM Assembler"; + break; + + case LLVMTool::Mt: + test_flag = "/help"; + expected_out = "Manifest Tool"; + break; + + default: + break; + } + + EXPECT_TRUE(Testing::CallWithCapturedOutput( + out, err, [&] { return runner.Run(tool, {test_flag}); })); + + // The arguments to LLD should be part of the verbose log. + EXPECT_THAT(test_os.TakeStr(), HasSubstr(test_flag)); + + // Nothing should print to stderr here. + EXPECT_THAT(err, StrEq("")); + + EXPECT_THAT(out, HasSubstr(expected_out)); + } +} + +} // namespace +} // namespace Carbon diff --git a/toolchain/driver/llvm_subcommand.cpp b/toolchain/driver/llvm_subcommand.cpp new file mode 100644 index 0000000000000..e8aef461a55f6 --- /dev/null +++ b/toolchain/driver/llvm_subcommand.cpp @@ -0,0 +1,65 @@ +// 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/llvm_subcommand.h" + +#include "common/command_line.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" +#include "toolchain/base/llvm_tools.h" +#include "toolchain/driver/llvm_runner.h" + +namespace Carbon { + +static constexpr CommandLine::CommandInfo SubcommandInfo = { + .name = "llvm", + .help = R"""( +Runs LLVM's command line tools with the provided arguments. + +This subcommand provides access to a collection of LLVM's command line tools via +further subcommands. For each of these tools, their command line can be provided +using positional arguments. +)""", +}; + +auto LLVMOptions::Build(CommandLine::CommandBuilder& b, + DriverSubcommand* subcommand, + DriverSubcommand** selected_subcommand) -> void { + // Add further subcommands for each LLVM tool. + for (LLVMTool tool : LLVMTool::Tools) { + b.AddSubcommand(tool.subcommand_info(), [&](auto& sub_b) { + sub_b.AddStringPositionalArg( + { + .name = "ARG", + .help = R"""( +Arguments passed to the LLVM tool. +)""", + }, + [&](auto& arg_b) { arg_b.Append(&args); }); + sub_b.Do([=, this] { + subcommand_tool = tool; + *selected_subcommand = subcommand; + }); + }); + } + + // The `llvm` subcommand can't be used directly. + b.RequiresSubcommand(); +} + +LLVMSubcommand::LLVMSubcommand() : DriverSubcommand(SubcommandInfo) {} + +auto LLVMSubcommand::Run(DriverEnv& driver_env) -> DriverResult { + LLVMRunner runner(driver_env.installation, driver_env.vlog_stream); + + // Don't run LLD when fuzzing, as we're not currently in a good position to + // debug and fix fuzzer-found bugs within LLD. + if (!DisableFuzzingExternalLibraries(driver_env, "llvm")) { + return {.success = false}; + } + + return {.success = runner.Run(options_.subcommand_tool, options_.args)}; +} + +} // namespace Carbon diff --git a/toolchain/driver/llvm_subcommand.h b/toolchain/driver/llvm_subcommand.h new file mode 100644 index 0000000000000..c28965b067c06 --- /dev/null +++ b/toolchain/driver/llvm_subcommand.h @@ -0,0 +1,57 @@ +// 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_LLVM_SUBCOMMAND_H_ +#define CARBON_TOOLCHAIN_DRIVER_LLVM_SUBCOMMAND_H_ + +#include "common/command_line.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "toolchain/base/llvm_tools.h" +#include "toolchain/driver/driver_env.h" +#include "toolchain/driver/driver_subcommand.h" + +namespace Carbon { + +// Options for the LLVM subcommand. +// +// See the implementation of `Build` for documentation on members. +struct LLVMOptions { + // Build the LLVM subcommand options using `b`. Whatever eventual nested + // subcommand action is selected, it needs to additionally run `select_fn`. + auto Build(CommandLine::CommandBuilder& b, DriverSubcommand* subcommand, + DriverSubcommand** selected_subcommand) -> void; + + LLVMTool subcommand_tool; + llvm::SmallVector args; +}; + +// Implement the LLVM subcommand of the driver. +// +// This provides access to the full collection of internal LLVM command line +// tools. +class LLVMSubcommand : public DriverSubcommand { + public: + explicit LLVMSubcommand(); + + // The LLVM subcommand uses a custom subcommand structure, so `BuildOptions` + // is a no-op and we override the more complex layer. + auto BuildOptions(CommandLine::CommandBuilder& /*b*/) -> void override { + CARBON_FATAL("Unused."); + } + auto BuildOptionsAndSetAction(CommandLine::CommandBuilder& b, + DriverSubcommand** selected_subcommand) + -> void override { + options_.Build(b, this, selected_subcommand); + } + + auto Run(DriverEnv& driver_env) -> DriverResult override; + + private: + LLVMOptions options_; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_DRIVER_LLVM_SUBCOMMAND_H_ diff --git a/toolchain/driver/testdata/fail_llvm_fuzzing.carbon b/toolchain/driver/testdata/fail_llvm_fuzzing.carbon new file mode 100644 index 0000000000000..feec345f1139a --- /dev/null +++ b/toolchain/driver/testdata/fail_llvm_fuzzing.carbon @@ -0,0 +1,15 @@ +// 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 +// +// ARGS: --include-diagnostic-kind --fuzzing llvm ar -- --version +// +// SET-CAPTURE-CONSOLE-OUTPUT +// clang-format off +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/fail_llvm_fuzzing.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/fail_llvm_fuzzing.carbon +// CHECK:STDERR: error: preventing fuzzing of `llvm` subcommand due to external library [ToolFuzzingDisallowed] +// CHECK:STDERR: diff --git a/toolchain/install/BUILD b/toolchain/install/BUILD index 61c967455abe7..07c64f9b307af 100644 --- a/toolchain/install/BUILD +++ b/toolchain/install/BUILD @@ -9,6 +9,7 @@ load( load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") load("//bazel/cc_toolchains:defs.bzl", "cc_env") load("//bazel/manifest:defs.bzl", "manifest") +load("//toolchain/base:llvm_tools.bzl", "LLVM_TOOLS", "LLVM_TOOL_ALIASES") load("install_filegroups.bzl", "install_filegroup", "install_symlink", "install_target", "make_install_filegroups") load("pkg_helpers.bzl", "pkg_naming_variables", "pkg_tar_and_test") @@ -30,6 +31,7 @@ cc_library( deps = [ "//common:check", "//common:error", + "//toolchain/base:llvm_tools", "@bazel_tools//tools/cpp/runfiles", "@llvm-project//llvm:Support", ], @@ -111,6 +113,7 @@ cc_binary( "//common:exe_path", "//common:init_llvm", "//common:version_stamp", + "//toolchain/base:llvm_tools_def", "//toolchain/driver", "@llvm-project//llvm:Support", ], @@ -123,6 +126,15 @@ lld_aliases = [ "ld64.lld", ] +llvm_binaries = lld_aliases + [ + tool.bin_name + for tool in LLVM_TOOLS.values() +] + [ + "llvm-" + alias + for (_, aliases) in LLVM_TOOL_ALIASES.items() + for alias in aliases +] + filegroup( name = "clang_headers", srcs = ["@llvm-project//clang:builtin_headers_gen"], @@ -165,7 +177,7 @@ install_dirs = { name, "../../carbon-busybox", is_driver = True, - ) for name in lld_aliases], + ) for name in llvm_binaries], "lib/carbon/llvm/lib/clang/" + LLVM_VERSION_MAJOR: [ install_filegroup("include", ":clang_headers", "staging/include/"), ], diff --git a/toolchain/install/busybox_main.cpp b/toolchain/install/busybox_main.cpp index bfc93971e1c75..5c723471b5b90 100644 --- a/toolchain/install/busybox_main.cpp +++ b/toolchain/install/busybox_main.cpp @@ -54,6 +54,15 @@ static auto Main(int argc, char** argv) -> ErrorOr { *busybox_info.mode) .Case("ld.lld", {"lld", "--platform=gnu", "--"}) .Case("ld64.lld", {"lld", "--platform=darwin", "--"}) + + // We also support a number of LLVM tools with a trivial translation + // to subcommands. If any of these end up needing more advanced + // translation, that can be factored into the `.def` file to provide custom + // expansion here. +#define CARBON_LLVM_TOOL(Id, Name, BinName, MainFn) \ + .Case(BinName, {"llvm", Name, "--"}) +#include "toolchain/base/llvm_tools.def" + .Default({*busybox_info.mode, "--"}); args.append(subcommand_args); } diff --git a/toolchain/install/install_paths.cpp b/toolchain/install/install_paths.cpp index cf7d6ccf5f9fc..ba24331caa54a 100644 --- a/toolchain/install/install_paths.cpp +++ b/toolchain/install/install_paths.cpp @@ -195,4 +195,12 @@ auto InstallPaths::ld64_lld_path() const -> std::string { return path.str().str(); } +auto InstallPaths::llvm_tool_path(LLVMTool tool) const -> std::string { + llvm::SmallString<256> path(prefix_); + // TODO: Adjust this to work equally well on Windows. + llvm::sys::path::append(path, llvm::sys::path::Style::posix, + "lib/carbon/llvm/bin", tool.bin_name()); + return path.str().str(); +} + } // namespace Carbon diff --git a/toolchain/install/install_paths.h b/toolchain/install/install_paths.h index b28d677cffeac..fa3bc9e24f3cd 100644 --- a/toolchain/install/install_paths.h +++ b/toolchain/install/install_paths.h @@ -9,6 +9,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" +#include "toolchain/base/llvm_tools.h" namespace Carbon { @@ -92,6 +93,9 @@ class InstallPaths { auto ld_lld_path() const -> std::string; auto ld64_lld_path() const -> std::string; + // The path to any of the LLVM tools. + auto llvm_tool_path(LLVMTool tool) const -> std::string; + private: friend class InstallPathsTestPeer;