Переглянути джерело

Add subcommands and busybox entry points for LLVM tools (#5049)

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.

Depends on #5048 -- only the last commit should be reviewed here.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
Chandler Carruth 1 рік тому
батько
коміт
1459332031

+ 3 - 1
scripts/fix_cc_deps.py

@@ -68,7 +68,9 @@ EXTERNAL_REPOS: dict[str, ExternalRepo] = {
 }
 
 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)$"
 )
 
 

+ 32 - 0
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_MAIN_TOOLS", "generate_llvm_tools_def")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -120,6 +121,37 @@ cc_test(
     ],
 )
 
+generate_llvm_tools_def(
+    name = "llvm_tools_def",
+    out = "llvm_tools.def",
+)
+
+config_setting(
+    name = "is_macos",
+    constraint_values = ["@platforms//os:macos"],
+)
+
+cc_library(
+    name = "llvm_tools",
+    srcs = ["llvm_tools.cpp"],
+    hdrs = ["llvm_tools.h"],
+    linkopts = select({
+        # TODO: This should be moved upstream to LLVM's tool libraries that
+        # require it.
+        ":is_macos": [
+            "-framework",
+            "CoreFoundation",
+        ],
+        "//conditions:default": [],
+    }),
+    deps = [
+        ":llvm_tools_def",
+        "//common:command_line",
+        "//common:enum_base",
+        "@llvm-project//llvm:Support",
+    ] + [info.lib for info in LLVM_MAIN_TOOLS.values()],
+)
+
 cc_library(
     name = "shared_value_stores",
     hdrs = ["shared_value_stores.h"],

+ 151 - 0
toolchain/base/llvm_tools.bzl

@@ -0,0 +1,151 @@
+# 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")
+
+# The main LLVM command line tools, including their "primary" name, binary name,
+# and the library dependency required to use them.
+LLVM_MAIN_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"),
+}
+
+# A collection of additional alias names that should be available for the main
+# tools. The key is the main tool with the support for these names, followed by
+# a list of the aliased names.
+#
+# Note that we don't track separate binary names for the alias names as those
+# are always formed by prepending `llvm-` for the aliases.
+LLVM_TOOL_ALIASES = {
+    "ar": ["ranlib", "lib", "dlltool"],
+    "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. It does not use
+// `#include` guards, and instead is designed to be `#include`ed after the
+// x-macro is defined in order for its inclusion to expand to the desired
+// output. Macro definitions are cleaned up at the end of this file.
+//
+// Each X-macro takes four arguments:
+// - `Id` is the identifier-shaped PascalCased tool name.
+// - `Name` is a string literal of the tool name.
+// - `BinName` is a string literal of the binary name of the tool when installed
+//    as a stand-alone command line tool.
+// - `MainFn` is the function symbol name used to run the tool as-if its `main`.
+//
+// There are three X-macros available:
+// - `CARBON_LLVM_TOOL` is available for every tool.
+//   - `CARBON_LLVM_MAIN_TOOL` is available for each tool with a distinct
+//     `MainFn` symbol name.
+//   - `CARBON_LLVM_ALIAS_TOOL` is available for each tool that is an alias of
+//     some other tool. It's `MainFn` will be the alias-target symbol name.
+//
+// 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_MAIN_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_MAIN_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.
+
+    This first generates the `.def` file into the `out` filename, and then
+    synthesizes a `cc_library` rule exporting that file in its `textual_hdrs`.
+
+    The `cc_library` rule name is the provided `name` and should be depended on
+    by code that includes the generated file. The `kwargs` are expanded into the
+    `cc_library` in case other attributes need to be configured there.
+
+    The two-step process is necessary to avoid trying to compile or otherwise
+    process the generated file as something other than a textual header.
+    """
+    generate_llvm_tools_def_rule(name = out)
+    cc_library(
+        name = name,
+        textual_hdrs = [out],
+        **kwargs
+    )

+ 41 - 0
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

+ 75 - 0
toolchain/base/llvm_tools.h

@@ -0,0 +1,75 @@
+// 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 <cstdint>
+
+#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"
+};
+
+// An enum-like class for each of the LLVM tools.
+//
+// This can be used like an enum to track a specific one of the LLVM tools. It
+// also has a collection of methods to access various aspects of the tools
+// themselves, including the symbol used to invoke the given tool.
+//
+// The instances of this class are generated from `llvm_tools.bzl`, see that
+// file for more details.
+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<LLVMTool> 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> LLVMTool::Tools = ToolsStorage;
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_BASE_LLVM_TOOLS_H_

+ 39 - 0
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"],

+ 3 - 0
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();
 }

+ 20 - 5
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;
 

+ 38 - 0
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 <algorithm>
+#include <memory>
+#include <numeric>
+#include <optional>
+
+#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<llvm::StringRef> 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<char> cstr_arg_storage;
+  llvm::SmallVector<const char*, 64> 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<char**>(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

+ 26 - 0
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<llvm::StringRef> args) -> bool;
+};
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_DRIVER_LLVM_RUNNER_H_

+ 96 - 0
toolchain/driver/llvm_runner_test.cpp

@@ -0,0 +1,96 @@
+// 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#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::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 the LLVM tool 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

+ 73 - 0
toolchain/driver/llvm_subcommand.cpp

@@ -0,0 +1,73 @@
+// 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) {
+    // TODO: The subcommand info for each tool is weirdly stored in the
+    // `LLVMTool` class instead of here where it makes more logical sense.
+    // Either we should figure out how to move it to here or we should more
+    // fully document the oddity of having it in the `LLVMTool` class.
+    //
+    // TODO: Currently, the command line subsystem's help isn't as user friendly
+    // for the generated subcommands below this as it could be. Because each of
+    // these has a completely generic and stamped out info, the `help` output is
+    // very repetitive and doesn't actually contribute much information.
+    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 arbitrary LLVM tools and libraries when fuzzing.
+  if (!DisableFuzzingExternalLibraries(driver_env, "llvm")) {
+    return {.success = false};
+  }
+
+  return {.success = runner.Run(options_.subcommand_tool, options_.args)};
+}
+
+}  // namespace Carbon

+ 59 - 0
toolchain/driver/llvm_subcommand.h

@@ -0,0 +1,59 @@
+// 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`.
+  //
+  // When this top-level subcommand is selected (potentially through a nested
+  // sub-subcommand), the `selected_subcommand` will be set to point to
+  // `subcommand` to reflect that.
+  auto Build(CommandLine::CommandBuilder& b, DriverSubcommand* subcommand,
+             DriverSubcommand** selected_subcommand) -> void;
+
+  LLVMTool subcommand_tool;
+  llvm::SmallVector<llvm::StringRef> args;
+};
+
+// Implement the LLVM subcommand of the driver.
+//
+// This provides access to the full collection of 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_

+ 15 - 0
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:

+ 13 - 1
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_MAIN_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_MAIN_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/"),
     ],

+ 9 - 0
toolchain/install/busybox_main.cpp

@@ -54,6 +54,15 @@ static auto Main(int argc, char** argv) -> ErrorOr<int> {
             *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);
   }

+ 8 - 0
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

+ 4 - 0
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;