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>
This commit is contained in:
Chandler Carruth
2025-03-04 18:00:06 +00:00
committed by GitHub
co-authored by Geoff Romer
parent c2281d1250
commit 1459332031
18 changed files with 705 additions and 7 deletions
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;