Support --target-cpu, --target-cpu-tune, and --target-cpu-features (#7903)

Add support to the driver for CLI flags that select which CPU to target,
which CPU to tune performance for, and which CPU features to enable.

- `--target-cpu` selects the architecture of CPU to target, similar to
`-march` on x86. This combines `-march` with `armv*` strings and `-mcpu`
on Arm for specific CPU names. Similarly for RISC-V (if/when we support
and test it), etc. Notably, `--target-cpu=native` targets the current
native CPU.
- `--target-cpu-tune` maps to `-mtune`. This is expected to be rarely if
ever desirable, and I'm open to removing it, but if folks are relying on
this for performance I didn't want to hide the option.
- `--target-cpu-features` allows a "standard" (for LLVM) comma-separated
list of LLVM subtarget features, including `+feature` and `-feature`
syntax.

These flags, when provided, establish the default baseline for CPU
targeting and control how on-demand runtimes are built. They can be
changed or overridden by additional Clang flags when using
`--clang-arg`.

A critical part of this is building runtimes on demand using these
features. For that, we need to do some amount of canonicalization
(re-using Clang's canonicalization to ensure consistent behavior), and
extend the runtimes cache features to recognize these customizations.
Note that only the top-level Carbon flags impact runtimes, any
particular Clang arguments are only used by the specific compiles that
receive those arguments.

There is some nascent RISC-V support here, but it is incomplete and only
minimally tested. Still, it seemed better to record the parts we could
test than remove them.

Assisted-by: Antigravity with Gemini
This commit is contained in:
Chandler Carruth authored and GitHub committed 2026-10-21 00:43:54 +00:00
1 parent 4441c53404
commit 0546b48a7d
25 files changed
+1084 -92

No files matched your search

+2
View File
@@ -72,7 +72,9 @@ cc_library(
"@llvm-project//clang:basic",
"@llvm-project//clang:driver",
"@llvm-project//clang:frontend",
"@llvm-project//llvm:MC",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:TargetParser",
],
)
+57 -15
View File
@@ -7,14 +7,21 @@
#include <filesystem>
#include <string>
#include "clang/Basic/DiagnosticDriver.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Driver/CreateInvocationFromArgs.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/Utils.h"
#include "common/string_helpers.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TargetParser/SubtargetFeature.h"
#include "llvm/TargetParser/Triple.h"
namespace Carbon {
@@ -50,17 +57,51 @@ auto ClangDriverDiagnosticConsumer::HandleDiagnostic(
}
}
// Validates `target_opts` (such as `-march`, `-mcpu`, `-mtune`, and
// `-target-feature`) and canonicalizes `target_opts.Features` via
// `clang::TargetInfo::CreateTargetInfo`. Returns `false` if any target option
// or feature is invalid.
static auto ValidateTargetOptions(clang::DiagnosticsEngine& driver_diags,
clang::TargetOptions& target_opts) -> bool {
llvm::IntrusiveRefCntPtr<clang::TargetInfo> target_info(
clang::TargetInfo::CreateTargetInfo(driver_diags, target_opts));
// `TargetInfo::CreateTargetInfo` expands Clang-level feature aliases into
// `TargetOptions::Features`, but passes unknown `+feature`/`-feature`
// entries through to the backend where `MCSubtargetInfo` only prints a raw
// stderr warning. Validate that every canonicalized backend feature is
// recognized by the target's `MCSubtargetInfo` when the target is registered.
std::string target_error;
llvm::Triple triple(target_opts.Triple);
if (const llvm::Target* target =
llvm::TargetRegistry::lookupTarget(triple, target_error)) {
std::unique_ptr<llvm::MCSubtargetInfo> subtarget_info(
target->createMCSubtargetInfo(triple, /*CPU=*/"", /*Features=*/""));
if (subtarget_info) {
llvm::ArrayRef<llvm::SubtargetFeatureKV> valid_features =
subtarget_info->getAllProcessorFeatures();
const auto& features =
target_info ? target_opts.Features : target_opts.FeaturesAsWritten;
for (llvm::StringRef feature : features) {
llvm::StringRef name = llvm::SubtargetFeatures::StripFlag(feature);
const auto* it = llvm::lower_bound(valid_features, name);
if (it == valid_features.end() || llvm::StringRef(it->key()) != name) {
driver_diags.Report(clang::diag::err_drv_unsupported_option_argument)
<< "-target-feature" << feature;
}
}
}
}
return target_info && !driver_diags.hasErrorOccurred();
}
auto BuildClangInvocation(Diagnostics::Consumer& consumer,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const InstallPaths& install_paths,
llvm::StringRef target_str,
llvm::ArrayRef<llvm::StringRef> extra_args)
-> std::unique_ptr<clang::CompilerInvocation> {
Diagnostics::ErrorTrackingConsumer error_tracker(consumer);
Diagnostics::NoLocEmitter emitter(&error_tracker);
ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
llvm::SmallVector<std::string> args;
args.push_back("--start-no-unused-arguments");
AppendDefaultClangArgs(install_paths, target_str, args);
@@ -83,6 +124,8 @@ auto BuildClangInvocation(Diagnostics::Consumer& consumer,
// Build a diagnostics engine. Note that we don't have any diagnostic options
// yet; they're produced by running the driver.
Diagnostics::NoLocEmitter emitter(&consumer);
ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter);
clang::DiagnosticOptions driver_diag_opts;
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> driver_diags(
clang::CompilerInstance::createDiagnostics(*fs, driver_diag_opts,
@@ -91,23 +134,22 @@ auto BuildClangInvocation(Diagnostics::Consumer& consumer,
// Ask the driver to process the arguments and build a corresponding clang
// frontend invocation.
auto invocation =
std::unique_ptr<clang::CompilerInvocation> invocation =
clang::createInvocation(cstr_args, {.Diags = driver_diags, .VFS = fs});
// If Clang produced an error, throw away its invocation.
if (error_tracker.seen_error()) {
if (!invocation || driver_diags->hasErrorOccurred() ||
!ValidateTargetOptions(*driver_diags, invocation->getTargetOpts())) {
return nullptr;
}
if (invocation) {
// Track submodule visibility in the preprocessor and Sema.
invocation->getLangOpts().Modules = true;
invocation->getLangOpts().ModulesLocalVisibility = true;
// Track submodule visibility in the preprocessor and Sema.
invocation->getLangOpts().Modules = true;
invocation->getLangOpts().ModulesLocalVisibility = true;
// Do not emit Clang's name and version as the creator of the output file.
invocation->getCodeGenOpts().EmitVersionIdentMetadata = false;
invocation->getCodeGenOpts().DiscardValueNames = false;
}
// Do not emit Clang's name and version as the creator of the output file.
invocation->getCodeGenOpts().EmitVersionIdentMetadata = false;
invocation->getCodeGenOpts().DiscardValueNames = false;
return invocation;
}
+5 -2
View File
@@ -5,6 +5,7 @@
#ifndef CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_
#define CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_
#include <memory>
#include <string>
#include "clang/Basic/Diagnostic.h"
@@ -37,8 +38,10 @@ class ClangDriverDiagnosticConsumer : public clang::DiagnosticConsumer {
};
// Builds and returns a clang `CompilerInvocation` to use when building code for
// interop, from a list of clang driver arguments. Emits diagnostics to
// `consumer` if the arguments are invalid.
// interop, from a list of clang driver arguments. Validates the target options
// and canonicalizes `TargetOptions::Features` via
// `clang::TargetInfo::CreateTargetInfo`. Emits diagnostics to `consumer` if the
// arguments or target options are invalid.
auto BuildClangInvocation(Diagnostics::Consumer& consumer,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
const InstallPaths& install_paths,
+2
View File
@@ -14,6 +14,7 @@
#include "clang/Basic/DiagnosticParse.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/Module.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/CodeGen/ModuleBuilder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
@@ -103,6 +104,7 @@ class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
// Make a deep copy of options that we modify.
FrontendOpts = std::make_shared<clang::FrontendOptions>(*FrontendOpts);
PPOpts = std::make_shared<clang::PreprocessorOptions>(*PPOpts);
TargetOpts = std::make_shared<clang::TargetOptions>(*TargetOpts);
}
};
+8
View File
@@ -26,6 +26,7 @@ cc_library(
"clang_runtimes.h",
],
deps = [
":codegen_options",
":llvm_runner",
":runtimes_cache",
":tool_runner_base",
@@ -41,6 +42,7 @@ cc_library(
"//toolchain/base:install_paths",
"//toolchain/base:kind_switch",
"//toolchain/base:runtimes_build_info",
"//toolchain/diagnostics:emitter",
"@llvm-project//clang:basic",
"@llvm-project//clang:clang-driver",
"@llvm-project//clang:codegen",
@@ -62,6 +64,7 @@ cc_test(
data = ["//toolchain/install:install_data"],
deps = [
":clang_runner",
":codegen_options",
":llvm_runner",
":runtimes_cache",
"//common:all_llvm_targets",
@@ -74,6 +77,7 @@ cc_test(
"//testing/base:global_exe_path",
"//testing/base:gtest_main",
"//toolchain/base:install_paths",
"//toolchain/diagnostics:null_diagnostics",
"@googletest//:gtest",
"@llvm-project//llvm:Object",
"@llvm-project//llvm:Support",
@@ -91,6 +95,7 @@ cc_test(
],
deps = [
":clang_runner",
":codegen_options",
":llvm_runner",
":runtimes_cache",
"//common:all_llvm_targets",
@@ -103,6 +108,7 @@ cc_test(
"//testing/base:gtest_main",
"//toolchain/base:install_paths",
"//toolchain/base:llvm_tools",
"//toolchain/diagnostics:null_diagnostics",
"@bazel_tools//tools/cpp/runfiles",
"@googletest//:gtest",
"@llvm-project//llvm:Object",
@@ -203,6 +209,7 @@ cc_library(
"//toolchain/parse:tree",
"//toolchain/sem_ir:typed_insts",
"//toolchain/source:source_buffer",
"@llvm-project//clang:basic",
"@llvm-project//llvm:Core",
"@llvm-project//llvm:MC",
"@llvm-project//llvm:Passes",
@@ -485,6 +492,7 @@ cc_test(
"@googletest//:gtest",
"@llvm-project//llvm:Object",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:TargetParser",
],
)
@@ -82,8 +82,9 @@ auto BuildRuntimesSubcommand::RunInternal(DriverEnv& driver_env)
ClangRunner runner(driver_env.installation, driver_env.fs,
driver_env.vlog_stream);
Runtimes::Cache::Features features = {
.target = options_.codegen_options.target.str()};
CARBON_ASSIGN_OR_RETURN(Runtimes::Cache::Features features,
runner.ComputeRuntimesFeatures(
options_.codegen_options, *driver_env.consumer));
bool is_cache = options_.directory.empty();
std::filesystem::path output_path = options_.directory.str();
@@ -102,14 +103,11 @@ auto BuildRuntimesSubcommand::RunInternal(DriverEnv& driver_env)
}
ClangResourceDirBuilder resource_dir_builder(&runner, driver_env.thread_pool,
llvm::Triple(features.target),
&runtimes);
&runtimes, features);
ClangArchiveRuntimesBuilder<Runtimes::LibUnwind> lib_unwind_builder(
&runner, driver_env.thread_pool, llvm::Triple(features.target),
&runtimes);
&runner, driver_env.thread_pool, &runtimes, features);
ClangArchiveRuntimesBuilder<Runtimes::Libcxx> libcxx_builder(
&runner, driver_env.thread_pool, llvm::Triple(features.target),
&runtimes);
&runner, driver_env.thread_pool, &runtimes, features);
CarbonPreludeBuilder prelude_builder(&driver_env, &runtimes,
&options_.codegen_options);
+6 -3
View File
@@ -102,11 +102,14 @@ auto CarbonPreludeBuilder::Build() && -> ErrorOr<std::filesystem::path> {
absolute_output_path.parent_path());
return absolute_output_path.string();
});
CARBON_CHECK(init_result,
"Failed to initialize compiler driver for Carbon prelude.");
if (!init_result) {
return Error("Failed to initialize compiler driver for Carbon prelude.");
}
auto compile_result = compile_driver_.Compile(*driver_env_);
CARBON_CHECK(compile_result.success, "Failed to compile Carbon prelude.");
if (!compile_result.success) {
return Error("Failed to compile Carbon prelude.");
}
result_ = (*std::move(runtimes_builder_)).Commit();
return std::move(result_);
+36 -10
View File
@@ -8,19 +8,19 @@
#include <unistd.h>
#include <filesystem>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticDriver.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/CodeGen/ObjectFilePCHContainerWriter.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/TextDiagnosticBuffer.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
@@ -151,6 +151,31 @@ static auto IsNonLinkCommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
});
}
auto ClangRunner::ComputeRuntimesFeatures(const CodegenOptions& codegen_options,
Diagnostics::Consumer& consumer)
-> ErrorOr<Runtimes::Cache::Features> {
llvm::SmallVector<std::string> codegen_clang_args =
codegen_options.GetClangArgs();
llvm::SmallVector<llvm::StringRef> extra_args(codegen_clang_args.begin(),
codegen_clang_args.end());
std::unique_ptr<clang::CompilerInvocation> invocation = BuildClangInvocation(
consumer, fs_, *installation_, codegen_options.target, extra_args);
if (!invocation) {
return Error(
llvm::formatv("Unable to setup target `{0}`", codegen_options.target)
.str());
}
clang::TargetOptions& target_opts = invocation->getTargetOpts();
return Runtimes::Cache::Features{
.target = codegen_options.target.str(),
.cpu = std::move(target_opts.CPU),
.tune_cpu = std::move(target_opts.TuneCPU),
.target_features = llvm::SmallVector<std::string>(
std::make_move_iterator(target_opts.Features.begin()),
std::make_move_iterator(target_opts.Features.end())),
};
}
auto ClangRunner::RunWithPrebuiltRuntimes(llvm::ArrayRef<llvm::StringRef> args,
Runtimes& prebuilt_runtimes,
bool enable_leaking)
@@ -179,7 +204,8 @@ auto ClangRunner::RunWithPrebuiltRuntimes(llvm::ArrayRef<llvm::StringRef> args,
auto ClangRunner::Run(llvm::ArrayRef<llvm::StringRef> args,
Runtimes::Cache& runtimes_cache,
llvm::ThreadPoolInterface& runtimes_build_thread_pool,
bool enable_leaking) -> ErrorOr<bool> {
bool enable_leaking, Runtimes::Cache::Features features)
-> ErrorOr<bool> {
std::string target = ComputeClangTarget(args);
// Check the args to see if we have a known target-independent command. If so,
@@ -198,7 +224,9 @@ auto ClangRunner::Run(llvm::ArrayRef<llvm::StringRef> args,
enable_leaking);
}
Runtimes::Cache::Features features = {.target = target};
if (features.target.empty()) {
features.target = target;
}
CARBON_ASSIGN_OR_RETURN(Runtimes runtimes, runtimes_cache.Lookup(features));
// We need to build the Clang resource directory for these runtimes. This
@@ -206,14 +234,12 @@ auto ClangRunner::Run(llvm::ArrayRef<llvm::StringRef> args,
// the build. The temporary directory should only be used during the build,
// not once we are running Clang with the built runtime.
CARBON_VLOG("Building target resource dir...\n");
ClangResourceDirBuilder builder(this, &runtimes_build_thread_pool,
llvm::Triple(features.target), &runtimes);
ClangResourceDirBuilder builder(this, &runtimes_build_thread_pool, &runtimes,
features);
ClangArchiveRuntimesBuilder<Runtimes::LibUnwind> lib_unwind_builder(
this, &runtimes_build_thread_pool, llvm::Triple(features.target),
&runtimes);
this, &runtimes_build_thread_pool, &runtimes, features);
ClangArchiveRuntimesBuilder<Runtimes::Libcxx> libcxx_builder(
this, &runtimes_build_thread_pool, llvm::Triple(features.target),
&runtimes);
this, &runtimes_build_thread_pool, &runtimes, features);
CARBON_ASSIGN_OR_RETURN(std::filesystem::path resource_dir_path,
std::move(builder).Wait());
CARBON_ASSIGN_OR_RETURN(std::filesystem::path libunwind_path,
+11 -1
View File
@@ -19,6 +19,8 @@
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/TargetParser/Triple.h"
#include "toolchain/base/install_paths.h"
#include "toolchain/diagnostics/emitter.h"
#include "toolchain/driver/codegen_options.h"
#include "toolchain/driver/runtimes_cache.h"
#include "toolchain/driver/tool_runner_base.h"
@@ -83,7 +85,8 @@ class ClangRunner : ToolRunnerBase {
auto Run(llvm::ArrayRef<llvm::StringRef> args,
Runtimes::Cache& runtimes_cache,
llvm::ThreadPoolInterface& runtimes_build_thread_pool,
bool enable_leaking = false) -> ErrorOr<bool>;
bool enable_leaking = false, Runtimes::Cache::Features features = {})
-> ErrorOr<bool>;
// Run Clang with the provided arguments and prebuilt runtimes.
//
@@ -104,6 +107,13 @@ class ClangRunner : ToolRunnerBase {
auto RunWithNoRuntimes(llvm::ArrayRef<llvm::StringRef> args,
bool enable_leaking = false) -> ErrorOr<bool>;
// Computes the canonicalized `Runtimes::Cache::Features` for
// `codegen_options`, emitting any diagnostics for invalid target flags to
// `consumer`.
auto ComputeRuntimesFeatures(const CodegenOptions& codegen_options,
Diagnostics::Consumer& consumer)
-> ErrorOr<Runtimes::Cache::Features>;
private:
friend class ClangRuntimesBuilderBase;
+156
View File
@@ -24,13 +24,20 @@
#include "testing/base/file_helpers.h"
#include "testing/base/global_exe_path.h"
#include "toolchain/base/install_paths.h"
#include "toolchain/diagnostics/null_diagnostics.h"
#include "toolchain/driver/codegen_options.h"
#include "toolchain/driver/runtimes_cache.h"
namespace Carbon {
namespace {
using ::testing::_;
using ::testing::Contains;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using Testing::IsError;
using Testing::IsSuccess;
using ::testing::Not;
using ::testing::StrEq;
class ClangRunnerTest : public ::testing::Test {
@@ -278,5 +285,154 @@ _Z4testv:
EXPECT_THAT(err, StrEq(""));
}
TEST_F(ClangRunnerTest, ComputeRuntimesFeatures) {
ClangRunner runner(&install_paths_, vfs_);
auto compute = [&](const CodegenOptions& options) {
return runner.ComputeRuntimesFeatures(options, Diagnostics::NullConsumer());
};
// x86_64: default, microarchitecture level, specific CPU, and tune CPU.
auto x86_default = compute({.target = "x86_64-unknown-linux-gnu"});
ASSERT_THAT(x86_default, IsSuccess(_));
EXPECT_THAT(x86_default->cpu, StrEq("x86-64"));
EXPECT_THAT(x86_default->tune_cpu, StrEq("generic"));
auto x86_v3 = compute(
{.target = "x86_64-unknown-linux-gnu", .target_cpu = "x86-64-v3"});
ASSERT_THAT(x86_v3, IsSuccess(_));
EXPECT_THAT(x86_v3->cpu, StrEq("x86-64-v3"));
EXPECT_THAT(x86_v3->tune_cpu, IsEmpty());
EXPECT_THAT(x86_v3->target_features, Contains("+avx2"));
EXPECT_THAT(x86_v3->target_features, Contains("+bmi2"));
auto x86_v3_tuned = compute({.target = "x86_64-unknown-linux-gnu",
.target_cpu = "x86-64-v3",
.target_cpu_tune = "znver4"});
ASSERT_THAT(x86_v3_tuned, IsSuccess(_));
EXPECT_THAT(x86_v3_tuned->cpu, StrEq("x86-64-v3"));
EXPECT_THAT(x86_v3_tuned->tune_cpu, StrEq("znver4"));
auto x86_znver4 =
compute({.target = "x86_64-unknown-linux-gnu", .target_cpu = "znver4"});
ASSERT_THAT(x86_znver4, IsSuccess(_));
EXPECT_THAT(x86_znver4->cpu, StrEq("znver4"));
EXPECT_THAT(x86_znver4->tune_cpu, IsEmpty());
EXPECT_THAT(x86_znver4->target_features, Contains("+avx512f"));
// AArch64: architecture string (-march), CPU name (-mcpu), and tune (-mtune).
auto aarch64_v9 = compute({.target = "aarch64-unknown-linux-gnu",
.target_cpu = "armv9-a",
.target_cpu_tune = "neoverse-v2"});
ASSERT_THAT(aarch64_v9, IsSuccess(_));
EXPECT_THAT(aarch64_v9->tune_cpu, StrEq("neoverse-v2"));
EXPECT_THAT(aarch64_v9->target_features, Contains("+v9a"));
EXPECT_THAT(aarch64_v9->target_features, Contains("+sve2"));
auto aarch64_neoverse = compute(
{.target = "aarch64-unknown-linux-gnu", .target_cpu = "neoverse-v2"});
ASSERT_THAT(aarch64_neoverse, IsSuccess(_));
EXPECT_THAT(aarch64_neoverse->cpu, StrEq("neoverse-v2"));
EXPECT_THAT(aarch64_neoverse->tune_cpu, IsEmpty());
EXPECT_THAT(aarch64_neoverse->target_features, Contains("+v9a"));
// RISC-V: ISA string (-march), profile (-march), CPU name (-mcpu), and tune
// (-mtune).
auto riscv_gc = compute({.target = "riscv64-unknown-linux-gnu",
.target_cpu = "rv64gc",
.target_cpu_tune = "spacemit-x60"});
ASSERT_THAT(riscv_gc, IsSuccess(_));
EXPECT_THAT(riscv_gc->tune_cpu, StrEq("spacemit-x60"));
EXPECT_THAT(riscv_gc->target_features, Contains("+m"));
EXPECT_THAT(riscv_gc->target_features, Contains("+a"));
EXPECT_THAT(riscv_gc->target_features, Contains("+f"));
EXPECT_THAT(riscv_gc->target_features, Contains("+d"));
EXPECT_THAT(riscv_gc->target_features, Contains("+c"));
auto riscv_profile = compute(
{.target = "riscv64-unknown-linux-gnu", .target_cpu = "rva22u64"});
ASSERT_THAT(riscv_profile, IsSuccess(_));
EXPECT_THAT(riscv_profile->target_features, Contains("+zba"));
EXPECT_THAT(riscv_profile->target_features, Contains("+zbb"));
EXPECT_THAT(riscv_profile->target_features, Contains("+zbs"));
auto riscv_cpu = compute(
{.target = "riscv64-unknown-linux-gnu", .target_cpu = "spacemit-x60"});
ASSERT_THAT(riscv_cpu, IsSuccess(_));
EXPECT_THAT(riscv_cpu->cpu, StrEq("spacemit-x60"));
EXPECT_THAT(riscv_cpu->tune_cpu, IsEmpty());
EXPECT_THAT(riscv_cpu->target_features, Contains("+v"));
// Host `native` resolution should canonicalize to a concrete CPU name for
// both target CPU and tune CPU.
std::string host_target = llvm::sys::getDefaultTargetTriple();
llvm::Triple host_triple(host_target);
if (host_triple.isX86() || host_triple.isAArch64()) {
std::string host_cpu = llvm::sys::getHostCPUName().str();
auto native_features = compute({.target = host_target,
.target_cpu = "native",
.target_cpu_tune = "native"});
ASSERT_THAT(native_features, IsSuccess(_));
EXPECT_THAT(native_features->cpu, StrEq(host_cpu));
EXPECT_THAT(native_features->tune_cpu, StrEq(host_cpu));
EXPECT_THAT(native_features->target_features, Not(IsEmpty()));
}
// `--target-cpu-features` across x86_64, AArch64, and RISC-V, including `+`,
// `-`, and bare feature names.
{
auto features = compute({.target = "x86_64-unknown-linux-gnu",
.target_cpu = "x86-64-v3",
.target_cpu_features = "-avx2,avx512f"});
ASSERT_THAT(features, IsSuccess(_));
EXPECT_THAT(features->cpu, StrEq("x86-64-v3"));
// Enabling `avx512f` after `-avx2` works in order and re-enables `avx2` as
// an implied prerequisite.
EXPECT_THAT(features->target_features, Contains("+avx512f"));
EXPECT_THAT(features->target_features, Contains("+avx2"));
}
{
auto features = compute({.target = "x86_64-unknown-linux-gnu",
.target_cpu = "x86-64-v3",
.target_cpu_features = "avx512f,-avx512f,-fma"});
ASSERT_THAT(features, IsSuccess(_));
EXPECT_THAT(features->target_features, Not(Contains("+avx512f")));
EXPECT_THAT(features->target_features, Not(Contains("+fma")));
EXPECT_THAT(features->target_features, Contains("+avx2"));
}
{
auto features = compute({.target = "aarch64-unknown-linux-gnu",
.target_cpu = "armv8-a",
.target_cpu_features = "sve,+lse,-crc"});
ASSERT_THAT(features, IsSuccess(_));
EXPECT_THAT(features->target_features, Contains("+sve"));
EXPECT_THAT(features->target_features, Contains("+lse"));
EXPECT_THAT(features->target_features, Not(Contains("+crc")));
}
{
auto features = compute({.target = "riscv64-unknown-linux-gnu",
.target_cpu = "rv64gc",
.target_cpu_features = "+zba,zbb,-m"});
ASSERT_THAT(features, IsSuccess(_));
EXPECT_THAT(features->target_features, Contains("+zba"));
EXPECT_THAT(features->target_features, Contains("+zbb"));
EXPECT_THAT(features->target_features, Not(Contains("+m")));
}
// Invalid target CPU, tune CPU, or target feature flags should return an
// error.
EXPECT_THAT(compute({.target = "x86_64-unknown-linux-gnu",
.target_cpu = "invalid-cpu-name"}),
IsError(_));
EXPECT_THAT(compute({.target = "x86_64-unknown-linux-gnu",
.target_cpu_tune = "invalid-cpu-name"}),
IsError(_));
EXPECT_THAT(compute({.target = "x86_64-unknown-linux-gnu",
.target_cpu_features = "+not-a-feature"}),
IsError(_));
EXPECT_THAT(compute({.target = "riscv64-unknown-linux-gnu",
.target_cpu_features = "+f,+zfinx"}),
IsError(_));
}
} // namespace
} // namespace Carbon
+45 -9
View File
@@ -44,6 +44,36 @@
namespace Carbon {
ClangRuntimesBuilderBase::ClangRuntimesBuilderBase(
ClangRunner* clang, llvm::ThreadPoolInterface* threads,
const Runtimes::Cache::Features& features)
: clang_(clang),
vlog_stream_(clang_->vlog_stream_),
tasks_(*threads),
target_triple_(features.target),
target_flag_(llvm::formatv("--target={0}", target_triple_.str())),
result_(Error("Did not finish building the runtime!")) {
if (!features.cpu.empty() || !features.tune_cpu.empty() ||
!features.target_features.empty()) {
target_args_.push_back("--start-no-unused-arguments");
if (!features.cpu.empty()) {
target_args_.append({"-Xclang", "-target-cpu", "-Xclang", features.cpu,
"-Xclangas", "-target-cpu", "-Xclangas",
features.cpu});
}
if (!features.tune_cpu.empty()) {
target_args_.append(
{"-Xclang", "-tune-cpu", "-Xclang", features.tune_cpu});
}
for (const std::string& feature : features.target_features) {
target_args_.append({"-Xclang", "-target-feature", "-Xclang", feature,
"-Xclangas", "-target-feature", "-Xclangas",
feature});
}
target_args_.push_back("--end-no-unused-arguments");
}
}
auto ClangRuntimesBuilderBase::ArchiveBuilder::Setup(Latch::Handle latch_handle)
-> void {
// `NewArchiveMember` isn't default constructable unfortunately, so we have to
@@ -189,9 +219,12 @@ auto ClangRuntimesBuilderBase::ArchiveBuilder::CompileMember(
}
// Collect the additional required flags and dynamic flags for this builder.
args.push_back("-c");
args.push_back(builder_->target_flag_);
for (const std::string& target_arg : builder_->target_args_) {
args.push_back(target_arg);
}
args.append({
"-c",
builder_->target_flag_,
"-o",
obj_path.native(),
src_path.native(),
@@ -233,9 +266,9 @@ auto ClangRuntimesBuilderBase::ArchiveBuilder::CompileMember(
template <Runtimes::Component Component>
requires IsClangArchiveRuntimes<Component>
ClangArchiveRuntimesBuilder<Component>::ClangArchiveRuntimesBuilder(
ClangRunner* clang, llvm::ThreadPoolInterface* threads,
llvm::Triple target_triple, Runtimes* runtimes)
: ClangRuntimesBuilderBase(clang, threads, std::move(target_triple)) {
ClangRunner* clang, llvm::ThreadPoolInterface* threads, Runtimes* runtimes,
const Runtimes::Cache::Features& features)
: ClangRuntimesBuilderBase(clang, threads, features) {
// Ensure we're on a platform where we _can_ build a working runtime.
if (target_triple_.isOSWindows()) {
result_ =
@@ -391,9 +424,9 @@ auto ClangResourceDirBuilder::GetDarwinOsSuffix(llvm::Triple target_triple)
}
ClangResourceDirBuilder::ClangResourceDirBuilder(
ClangRunner* clang, llvm::ThreadPoolInterface* threads,
llvm::Triple target_triple, Runtimes* runtimes)
: ClangRuntimesBuilderBase(clang, threads, std::move(target_triple)),
ClangRunner* clang, llvm::ThreadPoolInterface* threads, Runtimes* runtimes,
const Runtimes::Cache::Features& features)
: ClangRuntimesBuilderBase(clang, threads, features),
crt_begin_result_(Error("Never built CRT begin file!")),
crt_end_result_(Error("Never built CRT end file!")) {
// Ensure we're on a platform where we _can_ build a working runtime.
@@ -418,7 +451,7 @@ ClangResourceDirBuilder::ClangResourceDirBuilder(
runtimes_builder_ = std::get<Runtimes::Builder>(std::move(build_dir));
lib_path_ = std::filesystem::path("lib");
std::filesystem::path builtins_name = "libclang_rt.builtins.a";
if (target_triple.isOSDarwin()) {
if (target_triple_.isOSDarwin()) {
// Darwin targets don't use the full triple, and don't include the
// architecture in the resource directory naming structure.
//
@@ -554,6 +587,9 @@ auto ClangResourceDirBuilder::BuildCrtFile(llvm::StringRef src_file)
"-w",
target_flag_,
};
for (const std::string& target_arg : target_args_) {
copts.push_back(target_arg);
}
llvm::append_range(copts, RuntimesBuildInfo::CrtCopts);
copts.append({
"-c",
+6 -9
View File
@@ -54,13 +54,7 @@ class ClangRuntimesBuilderBase {
// Both the `clang` runner and the `threads` need to outlive this object.
ClangRuntimesBuilderBase(ClangRunner* clang,
llvm::ThreadPoolInterface* threads,
llvm::Triple target_triple)
: clang_(clang),
vlog_stream_(clang_->vlog_stream_),
tasks_(*threads),
target_triple_(std::move(target_triple)),
target_flag_(llvm::formatv("--target={0}", target_triple_.str())),
result_(Error("Did not finish building the runtime!")) {}
const Runtimes::Cache::Features& features);
auto installation() -> const InstallPaths& { return *clang_->installation_; }
@@ -78,6 +72,7 @@ class ClangRuntimesBuilderBase {
llvm::Triple target_triple_;
std::string target_flag_;
llvm::SmallVector<std::string> target_args_;
ErrorOr<std::filesystem::path> result_;
@@ -226,7 +221,8 @@ class ClangArchiveRuntimesBuilder : public ClangRuntimesBuilderBase {
// they were already available, the call to `Wait` will not block.
ClangArchiveRuntimesBuilder(ClangRunner* clang,
llvm::ThreadPoolInterface* threads,
llvm::Triple target_triple, Runtimes* runtimes);
Runtimes* runtimes,
const Runtimes::Cache::Features& features);
private:
// Helpers to compute the list of source files and compile flags for a
@@ -280,7 +276,8 @@ class ClangResourceDirBuilder : public ClangRuntimesBuilderBase {
// they were already available, the call to `Wait` will not block.
ClangResourceDirBuilder(ClangRunner* clang,
llvm::ThreadPoolInterface* threads,
llvm::Triple target_triple, Runtimes* runtimes);
Runtimes* runtimes,
const Runtimes::Cache::Features& features);
private:
friend class ClangResourceDirBuilderTestPeer;
+22 -7
View File
@@ -27,7 +27,9 @@
#include "testing/base/global_exe_path.h"
#include "toolchain/base/install_paths.h"
#include "toolchain/base/llvm_tools.h"
#include "toolchain/diagnostics/null_diagnostics.h"
#include "toolchain/driver/clang_runner.h"
#include "toolchain/driver/codegen_options.h"
#include "toolchain/driver/llvm_runner.h"
#include "toolchain/driver/runtimes_cache.h"
#include "tools/cpp/runfiles/runfiles.h"
@@ -270,8 +272,9 @@ class ClangRuntimesTest : public ::testing::Test {
Runtimes::Cache runtimes_cache_ =
*Runtimes::Cache::MakeSystem(install_paths_);
Runtimes::Cache::Features features = {.target = target_};
Runtimes runtimes_ = *runtimes_cache_.Lookup(features);
Runtimes::Cache::Features features_ = *runner_.ComputeRuntimesFeatures(
{.target = target_}, Diagnostics::NullConsumer());
Runtimes runtimes_ = *runtimes_cache_.Lookup(features_);
// Note that for debugging it may be useful to replace this with a
// single-threaded thread pool. However the test will be _much_ slower.
@@ -279,16 +282,28 @@ class ClangRuntimesTest : public ::testing::Test {
};
TEST_F(ClangRuntimesTest, ResourceDir) {
ClangResourceDirBuilder resource_dir_builder(&runner_, &threads_,
target_triple_, &runtimes_);
ClangResourceDirBuilder resource_dir_builder(&runner_, &threads_, &runtimes_,
features_);
auto build_result = std::move(resource_dir_builder).Wait();
ASSERT_TRUE(build_result.ok()) << build_result.error();
TestResourceDir(std::move(*build_result));
if (target_triple_.isX86()) {
auto v3_features = *runner_.ComputeRuntimesFeatures(
{.target = target_, .target_cpu = "x86-64-v3"},
Diagnostics::NullConsumer());
auto v3_runtimes = *runtimes_cache_.Lookup(v3_features);
ClangResourceDirBuilder v3_builder(&runner_, &threads_, &v3_runtimes,
v3_features);
auto v3_result = std::move(v3_builder).Wait();
ASSERT_TRUE(v3_result.ok()) << v3_result.error();
TestResourceDir(std::move(*v3_result));
}
}
TEST_F(ClangRuntimesTest, Libunwind) {
LibunwindBuilder libunwind_builder(&runner_, &threads_, target_triple_,
&runtimes_);
LibunwindBuilder libunwind_builder(&runner_, &threads_, &runtimes_,
features_);
auto build_result = std::move(libunwind_builder).Wait();
ASSERT_TRUE(build_result.ok()) << build_result.error();
std::filesystem::path runtimes_path = std::move(*build_result);
@@ -303,7 +318,7 @@ TEST_F(ClangRuntimesTest, Libunwind) {
// sustainable way. Given that, we disable this test by default but include it
// for debugging purposes.
TEST_F(ClangRuntimesTest, DISABLED_Libcxx) {
LibcxxBuilder libcxx_builder(&runner_, &threads_, target_triple_, &runtimes_);
LibcxxBuilder libcxx_builder(&runner_, &threads_, &runtimes_, features_);
auto build_result = std::move(libcxx_builder).Wait();
ASSERT_TRUE(build_result.ok()) << build_result.error();
std::filesystem::path runtimes_path = std::move(*build_result);
+128
View File
@@ -4,6 +4,15 @@
#include "toolchain/driver/codegen_options.h"
#include <string>
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TargetParser/AArch64TargetParser.h"
#include "llvm/TargetParser/RISCVISAInfo.h"
#include "llvm/TargetParser/RISCVTargetParser.h"
#include "llvm/TargetParser/Triple.h"
namespace Carbon {
auto CodegenOptions::Build(CommandLine::CommandBuilder& b) -> void {
@@ -23,6 +32,125 @@ https://clang.llvm.org/docs/CrossCompilation.html#target-triple
arg_b.Default(host);
arg_b.Set(&target);
});
b.AddStringOption(
{
.name = "target-cpu",
.value_name = "CPU",
.help = R"""(
Select a target CPU or architecture level.
Accepts CPU names (such as `znver4`, `apple-m1`, `spacemit-x60`, or `native`) as
well as architecture strings or profiles (such as `x86-64-v3`, `armv9-a`,
`rv64gc`, or `rva22u64`). This corresponds to Clang's `-march` and `-mcpu`
flags, selecting the appropriate underlying flag for the target architecture.
)""",
},
[&](auto& arg_b) { arg_b.Set(&target_cpu); });
b.AddStringOption(
{
.name = "target-cpu-tune",
.value_name = "CPU",
.help = R"""(
Select a target CPU to tune code generation for without enabling new
instruction set extensions.
Accepts CPU names (such as `znver4`, `apple-m1`, `spacemit-x60`, `generic`, or
`native`). This corresponds to Clang's `-mtune` flag.
)""",
},
[&](auto& arg_b) { arg_b.Set(&target_cpu_tune); });
b.AddStringOption(
{
.name = "target-cpu-features",
.value_name = "FEATURES",
.help = R"""(
Select target CPU features to enable or disable.
Accepts a comma-separated list of LLVM target feature names, optionally prefixed
with `+` to enable or `-` to disable (such as `+avx2,-fma` or `sve,crc`). Bare
feature names without a prefix are enabled.
)""",
},
[&](auto& arg_b) { arg_b.Set(&target_cpu_features); });
}
static auto AppendTargetCpuClangArgs(llvm::StringRef target,
llvm::StringRef target_cpu,
llvm::SmallVectorImpl<std::string>& args)
-> void {
if (target_cpu.empty()) {
return;
}
llvm::Triple triple(target);
if (triple.isX86()) {
// On x86, Clang uses `-march=` for both microarchitecture levels (such as
// `x86-64-v3`) and specific CPUs (such as `znver4` or `native`), and does
// not support `-mcpu=`.
args.push_back(llvm::formatv("-march={0}", target_cpu).str());
return;
}
if (triple.isAArch64()) {
auto [base, _] = target_cpu.split('+');
bool is_arch =
llvm::AArch64::parseArch(base) != nullptr || base.starts_with("armv");
args.push_back(
llvm::formatv(is_arch ? "-march={0}" : "-mcpu={0}", target_cpu).str());
return;
}
if (triple.isRISCV()) {
auto isa_info = llvm::RISCVISAInfo::parseArchString(
target_cpu, /*EnableExperimentalExtension=*/true);
bool is_arch = static_cast<bool>(isa_info);
if (!isa_info) {
llvm::consumeError(isa_info.takeError());
is_arch =
!llvm::RISCV::parseCPU(target_cpu, triple.isRISCV64()) &&
(target_cpu.starts_with("rv32") || target_cpu.starts_with("rv64") ||
target_cpu.starts_with("rva") || target_cpu.starts_with("rvb") ||
target_cpu.starts_with("rvi") || target_cpu.starts_with("rvm"));
}
args.push_back(
llvm::formatv(is_arch ? "-march={0}" : "-mcpu={0}", target_cpu).str());
return;
}
args.push_back(llvm::formatv("-mcpu={0}", target_cpu).str());
}
auto CodegenOptions::AppendClangArgs(
llvm::SmallVectorImpl<std::string>& args) const -> void {
AppendTargetCpuClangArgs(target, target_cpu, args);
if (!target_cpu_tune.empty()) {
args.push_back(llvm::formatv("-mtune={0}", target_cpu_tune).str());
}
if (!target_cpu_features.empty()) {
llvm::SmallVector<llvm::StringRef> features;
target_cpu_features.split(features, ',', /*MaxSplit=*/-1,
/*KeepEmpty=*/false);
for (llvm::StringRef feature : features) {
args.push_back("-Xclang");
args.push_back("-target-feature");
args.push_back("-Xclang");
if (feature.size() > 1 &&
(feature.starts_with('+') || feature.starts_with('-'))) {
args.push_back(feature.str());
} else {
args.push_back(llvm::formatv("+{0}", feature).str());
}
}
}
}
auto CodegenOptions::GetClangArgs() const -> llvm::SmallVector<std::string> {
llvm::SmallVector<std::string> args;
AppendClangArgs(args);
return args;
}
} // namespace Carbon
+15
View File
@@ -5,7 +5,11 @@
#ifndef CARBON_TOOLCHAIN_DRIVER_CODEGEN_OPTIONS_H_
#define CARBON_TOOLCHAIN_DRIVER_CODEGEN_OPTIONS_H_
#include <string>
#include "common/command_line.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/TargetParser/Host.h"
@@ -17,8 +21,19 @@ namespace Carbon {
struct CodegenOptions {
auto Build(CommandLine::CommandBuilder& b) -> void;
// Appends the Clang driver flags corresponding to `target_cpu`,
// `target_cpu_tune`, and `target_cpu_features`.
auto AppendClangArgs(llvm::SmallVectorImpl<std::string>& args) const -> void;
// Returns the Clang driver flags corresponding to `target_cpu`,
// `target_cpu_tune`, and `target_cpu_features`.
auto GetClangArgs() const -> llvm::SmallVector<std::string>;
std::string host = llvm::sys::getDefaultTargetTriple();
llvm::StringRef target;
llvm::StringRef target_cpu;
llvm::StringRef target_cpu_tune;
llvm::StringRef target_cpu_features;
};
} // namespace Carbon
+19 -4
View File
@@ -11,6 +11,7 @@
#include <system_error>
#include <utility>
#include "clang/Basic/TargetOptions.h"
#include "common/error.h"
#include "common/ostream.h"
#include "common/pretty_stack_trace_function.h"
@@ -18,6 +19,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
@@ -211,9 +213,21 @@ auto CompilationUnit::MakeTargetMachine(
llvm::Triple target_triple(options_->codegen_options->target);
module_->setTargetTriple(target_triple);
// TODO: Provide flags to control these.
constexpr llvm::StringLiteral CPU = "generic";
constexpr llvm::StringLiteral Features = "";
const auto& clang_target_opts = clang_invocation.getTargetOpts();
std::string features_str = llvm::join(clang_target_opts.Features, ",");
// LLVM's TargetMachine does not have a module-level TuneCPU setting; instead,
// target backends read the `"tune-cpu"` function attribute. Ignore Clang's
// implicit `"generic"` default on x86 unless `--target-cpu-tune` was set.
if (!clang_target_opts.TuneCPU.empty() &&
(!options_->codegen_options->target_cpu_tune.empty() ||
clang_target_opts.TuneCPU != "generic")) {
for (llvm::Function& fn : *module_) {
if (!fn.isDeclaration() && !fn.hasFnAttribute("tune-cpu")) {
fn.addFnAttr("tune-cpu", clang_target_opts.TuneCPU);
}
}
}
const auto& codegen_opts = clang_invocation.getCodeGenOpts();
@@ -226,7 +240,8 @@ auto CompilationUnit::MakeTargetMachine(
target_opts.DataSections = codegen_opts.DataSections;
target_opts.UniqueSectionNames = codegen_opts.UniqueSectionNames;
target_machine_.reset(target_->createTargetMachine(
target_triple, CPU, Features, target_opts, llvm::Reloc::PIC_));
target_triple, clang_target_opts.CPU, features_str, target_opts,
llvm::Reloc::PIC_));
}
auto CompilationUnit::RunOptimize(
+13 -7
View File
@@ -497,13 +497,19 @@ auto CompileOptions::BuildClangInvocation(DriverEnv& driver_env)
-> ErrorOr<std::shared_ptr<clang::CompilerInvocation>> {
// TODO: Move this into `BuildClangInvocation` when it can accept an
// optimization level.
llvm::SmallVector<llvm::StringRef> all_clang_args = {
// Propagate our optimization level to Clang as a default. This can be
// overridden by Clang arguments, but doing so will only have an effect
// if those arguments affect Clang's IR, not its pass pipeline.
CompileOptions::GetClangOptimizationFlag(opt_level),
};
all_clang_args.append(clang_args);
llvm::SmallVector<std::string> codegen_clang_args =
codegen_options->GetClangArgs();
llvm::SmallVector<llvm::StringRef> all_clang_args;
all_clang_args.reserve(1 + clang_args.size() + codegen_clang_args.size());
// Propagate our optimization level and codegen options to Clang as defaults.
// These can be overridden by Clang arguments, though optimization flags will
// only have an effect if those arguments affect Clang's IR, not its pass
// pipeline.
all_clang_args.push_back(CompileOptions::GetClangOptimizationFlag(opt_level));
for (llvm::StringRef arg : codegen_clang_args) {
all_clang_args.push_back(arg);
}
all_clang_args.append(clang_args.begin(), clang_args.end());
auto clang_invocation = Carbon::BuildClangInvocation(
*driver_env.consumer, driver_env.fs, *driver_env.installation,
codegen_options->target, all_clang_args);
+15 -7
View File
@@ -75,14 +75,22 @@ struct ConfigDataEntry {
// If there are any errors setting up Clang, this will diagnose them using
// `driver_env.consumer` and return `false`. If successful, returns `true`.
static auto ComputeClangConfig(DriverEnv& driver_env,
llvm::StringRef target_str,
const CodegenOptions& codegen_options,
llvm::SmallVectorImpl<ConfigDataEntry>& data)
-> bool {
llvm::SmallVector<std::string> codegen_clang_args =
codegen_options.GetClangArgs();
llvm::SmallVector<llvm::StringRef> extra_args(codegen_clang_args.begin(),
codegen_clang_args.end());
// Build a library invocation of Clang in order to query its header search
// paths.
std::shared_ptr clang_invocation =
BuildClangInvocation(*driver_env.consumer, driver_env.fs,
*driver_env.installation, target_str, {});
std::shared_ptr clang_invocation = BuildClangInvocation(
*driver_env.consumer, driver_env.fs, *driver_env.installation,
codegen_options.target, extra_args);
if (!clang_invocation) {
return false;
}
clang_invocation->getFrontendOpts().DisableFree = false;
// Setup up a driver-style diagnostic engine for the compiler invocation and
@@ -106,7 +114,8 @@ static auto ComputeClangConfig(DriverEnv& driver_env,
CARBON_DIAGNOSTIC(ConfigFailedToSetupTarget, Error,
"unable to setup the requested target `{0}`",
std::string);
driver_env.emitter.Emit(ConfigFailedToSetupTarget, target_str.str());
driver_env.emitter.Emit(ConfigFailedToSetupTarget,
codegen_options.target.str());
return false;
}
@@ -215,8 +224,7 @@ auto ConfigSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
// Compute and print Clang's config entries if we can. This will have been
// diagnosed while computing, so just track if we hit errors.
result &=
ComputeClangConfig(driver_env, options_.codegen_options.target, data);
result &= ComputeClangConfig(driver_env, options_.codegen_options, data);
llvm::sort(data, [](const ConfigDataEntry& lhs, const ConfigDataEntry& rhs) {
return lhs.key < rhs.key;
+221
View File
@@ -22,6 +22,8 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/Triple.h"
#include "testing/base/capture_std_streams.h"
#include "testing/base/file_helpers.h"
#include "testing/base/global_exe_path.h"
@@ -369,5 +371,224 @@ fn Run() {
EXPECT_TRUE(result->getBinary()->isObject());
}
TEST_F(DriverTest, TargetCpu) {
MakeTestFile("fn Run() {}", "test.carbon");
struct TargetCpuCase {
llvm::StringRef target;
llvm::StringRef cpu = "";
llvm::StringRef tune_cpu = "";
llvm::StringRef features = "";
};
TargetCpuCase compile_cases[] = {
{.target = "x86_64-unknown-linux-gnu", .cpu = "x86-64-v3"},
{.target = "x86_64-unknown-linux-gnu",
.cpu = "x86-64-v3",
.tune_cpu = "znver4"},
{.target = "x86_64-unknown-linux-gnu", .cpu = "znver4"},
{.target = "x86_64-unknown-linux-gnu", .tune_cpu = "znver4"},
{.target = "x86_64-unknown-linux-gnu",
.cpu = "x86-64-v2",
.tune_cpu = "znver4",
.features = "+avx2,-fma,bmi2"},
{.target = "aarch64-unknown-linux-gnu", .cpu = "armv9-a"},
{.target = "aarch64-unknown-linux-gnu",
.cpu = "armv9-a",
.tune_cpu = "neoverse-v2"},
{.target = "aarch64-unknown-linux-gnu", .cpu = "neoverse-v2"},
{.target = "aarch64-unknown-linux-gnu", .tune_cpu = "neoverse-v2"},
{.target = "aarch64-unknown-linux-gnu",
.cpu = "armv8.2-a",
.features = "sve,-crc"},
};
for (const auto& tc : compile_cases) {
SCOPED_TRACE(llvm::formatv("target={0}, cpu={1}, tune={2}, features={3}",
tc.target, tc.cpu, tc.tune_cpu, tc.features)
.str());
std::string target_arg = llvm::formatv("--target={0}", tc.target).str();
std::string cpu_arg = llvm::formatv("--target-cpu={0}", tc.cpu).str();
std::string tune_arg =
llvm::formatv("--target-cpu-tune={0}", tc.tune_cpu).str();
std::string features_arg =
llvm::formatv("--target-cpu-features={0}", tc.features).str();
llvm::SmallVector<llvm::StringRef> args = {"compile", "--no-prelude-import",
"--output=-", target_arg};
if (!tc.cpu.empty()) {
args.push_back(cpu_arg);
}
if (!tc.tune_cpu.empty()) {
args.push_back(tune_arg);
}
if (!tc.features.empty()) {
args.push_back(features_arg);
}
args.push_back("test.carbon");
EXPECT_TRUE(driver_.RunCommand(args).success)
<< test_error_stream_.TakeStr();
EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
EXPECT_THAT(test_output_stream_.TakeStr(), HasSubstr("main:"));
}
// Test `native` on the host target.
std::string host_target = llvm::sys::getDefaultTargetTriple();
llvm::Triple host_triple(host_target);
if (host_triple.isX86() || host_triple.isAArch64()) {
EXPECT_TRUE(driver_
.RunCommand({"compile", "--no-prelude-import", "--output=-",
"--target-cpu=native",
"--target-cpu-tune=native", "test.carbon"})
.success)
<< test_error_stream_.TakeStr();
EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
EXPECT_THAT(test_output_stream_.TakeStr(), HasSubstr("main:"));
}
// Invalid target CPU, tune CPU, or target features should fail cleanly with a
// diagnostic across `compile`, `link`, and `build-runtimes`.
for (llvm::StringRef target :
{"x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"}) {
std::string target_arg = llvm::formatv("--target={0}", target).str();
EXPECT_FALSE(
driver_
.RunCommand({"compile", "--no-prelude-import", "--output=-",
target_arg, "--target-cpu=not-a-valid-cpu",
"test.carbon"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("not-a-valid-cpu"));
test_output_stream_.TakeStr();
EXPECT_FALSE(
driver_
.RunCommand({"compile", "--no-prelude-import", "--output=-",
target_arg, "--target-cpu-tune=not-a-valid-tune-cpu",
"test.carbon"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(),
HasSubstr("not-a-valid-tune-cpu"));
test_output_stream_.TakeStr();
EXPECT_FALSE(
driver_
.RunCommand({"compile", "--no-prelude-import", "--output=-",
target_arg, "--target-cpu-features=+not-a-feature",
"test.carbon"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("+not-a-feature"));
test_output_stream_.TakeStr();
EXPECT_FALSE(
driver_
.RunCommand({"--no-build-runtimes", "link", "--output=out",
target_arg, "--target-cpu=not-a-valid-cpu", "test.o"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("not-a-valid-cpu"));
test_output_stream_.TakeStr();
EXPECT_FALSE(
driver_
.RunCommand({"--no-build-runtimes", "link", "--output=out",
target_arg, "--target-cpu-features=+not-a-feature",
"test.o"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("+not-a-feature"));
test_output_stream_.TakeStr();
EXPECT_FALSE(driver_
.RunCommand({"build-runtimes", target_arg,
"--target-cpu=not-a-valid-cpu"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("not-a-valid-cpu"));
test_output_stream_.TakeStr();
EXPECT_FALSE(driver_
.RunCommand({"build-runtimes", target_arg,
"--target-cpu-features=+not-a-feature"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("+not-a-feature"));
test_output_stream_.TakeStr();
}
// Test RISC-V `--target-cpu`, `--target-cpu-tune`, and
// `--target-cpu-features` mapping and Clang target validation via `config`
// (since the RISC-V LLVM codegen backend is not linked by default).
auto cleanup = ScopedTempWorkingDir();
MakeTestFile("fn Run() {}", "host_test.carbon");
if (host_triple.isX86() || host_triple.isAArch64()) {
llvm::StringRef host_cpu = host_triple.isX86() ? "x86-64-v3" : "armv8.2-a";
llvm::StringRef host_tune = host_triple.isX86() ? "znver4" : "neoverse-v2";
llvm::StringRef host_feature = host_triple.isX86() ? "+bmi2" : "+lse";
std::string cpu_arg = llvm::formatv("--target-cpu={0}", host_cpu).str();
std::string tune_arg =
llvm::formatv("--target-cpu-tune={0}", host_tune).str();
std::string feature_arg =
llvm::formatv("--target-cpu-features={0}", host_feature).str();
ASSERT_TRUE(driver_
.RunCommand({"compile", "--no-prelude-import", cpu_arg,
tune_arg, feature_arg, "host_test.carbon"})
.success)
<< test_error_stream_.TakeStr();
EXPECT_TRUE(driver_
.RunCommand({"--no-build-runtimes", "link",
"--output=host_test_linked", cpu_arg, tune_arg,
feature_arg, "host_test.o", "--", "-lc"})
.success)
<< test_error_stream_.TakeStr();
EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
// Also verify `build` with `--target-cpu` and a compile-only `--clang-arg`
// (such as `-fsanitize=address`, which would fail linking if forwarded to
// the link step without sanitizer runtimes).
EXPECT_TRUE(
driver_
.RunCommand({"--no-build-runtimes", "build", "--no-use-temp-dir",
"--no-prelude-import",
"--clang-arg=-fsanitize=address", cpu_arg, tune_arg,
feature_arg, "host_test.carbon", "--", "--", "-lc"})
.success)
<< test_error_stream_.TakeStr();
EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
}
for (llvm::StringRef riscv_cpu : {"rv64gc", "rva22u64", "spacemit-x60"}) {
SCOPED_TRACE(riscv_cpu);
std::string cpu_arg = llvm::formatv("--target-cpu={0}", riscv_cpu).str();
EXPECT_TRUE(driver_
.RunCommand({"config", "--json",
"--target=riscv64-unknown-linux-gnu", cpu_arg,
"--target-cpu-tune=spacemit-x60",
"--target-cpu-features=+zba,-m"})
.success)
<< test_error_stream_.TakeStr();
EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
test_output_stream_.TakeStr();
}
EXPECT_FALSE(
driver_
.RunCommand({"config", "--json", "--target=riscv64-unknown-linux-gnu",
"--target-cpu=not-a-valid-cpu"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("not-a-valid-cpu"));
test_output_stream_.TakeStr();
EXPECT_FALSE(
driver_
.RunCommand({"config", "--json", "--target=riscv64-unknown-linux-gnu",
"--target-cpu-tune=not-a-valid-tune-cpu"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("not-a-valid-tune-cpu"));
test_output_stream_.TakeStr();
EXPECT_FALSE(
driver_
.RunCommand({"config", "--json", "--target=riscv64-unknown-linux-gnu",
"--target-cpu-features=+f,+zfinx"})
.success);
EXPECT_THAT(test_error_stream_.TakeStr(),
HasSubstr("invalid feature combination"));
test_output_stream_.TakeStr();
}
} // namespace
} // namespace Carbon
+22 -7
View File
@@ -67,10 +67,19 @@ auto LinkDriver::Link(DriverEnv& driver_env) -> DriverResult {
// We link using a C++ mode of the driver.
clang_args.push_back("--driver-mode=g++");
// Pass the target down to Clang to pick up the correct defaults.
// Pass the target and codegen flags down to Clang as defaults.
std::string target_arg =
llvm::formatv("--target={0}", options_->codegen_options->target).str();
clang_args.push_back(target_arg);
llvm::SmallVector<std::string> codegen_clang_args =
options_->codegen_options->GetClangArgs();
if (!codegen_clang_args.empty()) {
clang_args.push_back("--start-no-unused-arguments");
for (llvm::StringRef arg : codegen_clang_args) {
clang_args.push_back(arg);
}
clang_args.push_back("--end-no-unused-arguments");
}
if (!options_->output_filename.empty()) {
clang_args.push_back("-o");
@@ -92,6 +101,15 @@ auto LinkDriver::Link(DriverEnv& driver_env) -> DriverResult {
return {.success = false};
}
ClangRunner runner(driver_env.installation, driver_env.fs,
driver_env.vlog_stream);
auto features = runner.ComputeRuntimesFeatures(*options_->codegen_options,
*driver_env.consumer);
if (!features.ok()) {
return {.success = false};
}
// Find or build the Carbon Core runtimes for linking the prelude into the
// binary.
bool include_prelude = false;
@@ -111,9 +129,7 @@ auto LinkDriver::Link(DriverEnv& driver_env) -> DriverResult {
runtimes_path = driver_env.prebuilt_runtimes->base_path();
include_prelude = true;
} else if (driver_env.build_runtimes_on_demand) {
Runtimes::Cache::Features features = {
.target = options_->codegen_options->target.str()};
auto runtimes_or_error = driver_env.runtimes_cache.Lookup(features);
auto runtimes_or_error = driver_env.runtimes_cache.Lookup(*features);
CARBON_CHECK(runtimes_or_error.ok(), "Runtimes cache lookup failed: {}",
runtimes_or_error.error().message());
auto runtimes = std::move(*runtimes_or_error);
@@ -171,8 +187,6 @@ auto LinkDriver::Link(DriverEnv& driver_env) -> DriverResult {
clang_args.append(options_->object_filenames.begin(),
options_->object_filenames.end());
ClangRunner runner(driver_env.installation, driver_env.fs,
driver_env.vlog_stream);
ErrorOr<bool> run_result =
driver_env.prebuilt_runtimes
? runner.RunWithPrebuiltRuntimes(clang_args,
@@ -180,7 +194,8 @@ auto LinkDriver::Link(DriverEnv& driver_env) -> DriverResult {
driver_env.enable_leaking)
: driver_env.build_runtimes_on_demand
? runner.Run(clang_args, driver_env.runtimes_cache,
*driver_env.thread_pool, driver_env.enable_leaking)
*driver_env.thread_pool, driver_env.enable_leaking,
*std::move(features))
: runner.RunWithNoRuntimes(clang_args, driver_env.enable_leaking);
if (!run_result.ok()) {
+11 -2
View File
@@ -251,12 +251,21 @@ auto Runtimes::Cache::Lookup(const Features& features) -> ErrorOr<Runtimes> {
// within the cache.
llvm::SHA256 entry_hasher;
auto update_with_separator = [&](llvm::StringRef str) {
entry_hasher.update(str);
entry_hasher.update(llvm::ArrayRef<uint8_t>{0});
};
// First incorporate our cache key that comes from the installation's digest.
// This ensures we don't share a cache entry with any other Carbon
// installations using different inputs.
entry_hasher.update(cache_key_);
update_with_separator(cache_key_);
// Then incorporate the specific features that are enabled in this entry.
entry_hasher.update(features.target);
update_with_separator(features.target);
update_with_separator(features.cpu);
update_with_separator(features.tune_cpu);
for (llvm::StringRef feature : features.target_features) {
update_with_separator(feature);
}
std::array<uint8_t, 32> entry_digest = entry_hasher.final();
std::filesystem::path entry_path =
+14 -1
View File
@@ -7,6 +7,7 @@
#include <chrono>
#include <filesystem>
#include <string>
#include <utility>
#include "common/check.h"
@@ -14,6 +15,7 @@
#include "common/filesystem.h"
#include "common/ostream.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "toolchain/base/install_paths.h"
@@ -234,9 +236,20 @@ class Runtimes::Cache {
// The features of a cached runtimes directory.
//
// TODO: Add support for more build flags that we want to enable when building
// runtimes such as sanitizers and CPU-specific optimizations.
// runtimes such as sanitizers.
struct Features {
// The target triple to build runtimes for.
std::string target;
// The canonical target CPU name (from `clang::TargetOptions::CPU`).
std::string cpu = {};
// The canonical tune CPU name (from `clang::TargetOptions::TuneCPU`).
std::string tune_cpu = {};
// The canonical sorted target features (`+<feature>` / `-<feature>` from
// `clang::TargetOptions::Features`).
llvm::SmallVector<std::string> target_features = {};
};
Cache() = default;
+45
View File
@@ -547,6 +547,51 @@ TEST_F(RuntimesCacheTest, Lookup) {
Eq(runtimes.base_dir().Stat()->unix_inode()));
}
TEST_F(RuntimesCacheTest, LookupTargetCpuFeatures) {
Runtimes::Cache::Features base_features = {
.target = "x86_64-unknown-linux-gnu",
.cpu = "x86-64-v3",
.tune_cpu = "znver4",
.target_features = {"+avx2", "+bmi2"},
};
auto base_runtimes = *cache_.Lookup(base_features);
// Looking up identical features should return the same cache directory.
auto same_runtimes = *cache_.Lookup(base_features);
EXPECT_THAT(same_runtimes.base_path(), Eq(base_runtimes.base_path()));
// Changing `cpu` should produce a distinct cache directory.
auto diff_cpu = base_features;
diff_cpu.cpu = "x86-64-v4";
EXPECT_THAT(cache_.Lookup(diff_cpu)->base_path(),
Ne(base_runtimes.base_path()));
// Changing `tune_cpu` should produce a distinct cache directory.
auto diff_tune = base_features;
diff_tune.tune_cpu = "skylake";
EXPECT_THAT(cache_.Lookup(diff_tune)->base_path(),
Ne(base_runtimes.base_path()));
// Changing `target_features` should produce a distinct cache directory.
auto diff_features = base_features;
diff_features.target_features = {"+avx2"};
EXPECT_THAT(cache_.Lookup(diff_features)->base_path(),
Ne(base_runtimes.base_path()));
// Shifting characters across field boundaries should not collide.
auto boundary1 = *cache_.Lookup({
.target = "x86_64-unknown-linux-gnu",
.cpu = "ab",
.tune_cpu = "c",
});
auto boundary2 = *cache_.Lookup({
.target = "x86_64-unknown-linux-gnu",
.cpu = "a",
.tune_cpu = "bc",
});
EXPECT_THAT(boundary1.base_path(), Ne(boundary2.base_path()));
}
TEST_F(RuntimesCacheTest, LookupFailsIfCannotCreateDir) {
// Create a read-only directory with the cache in it to cause failures.
std::filesystem::path ro_cache_path = tmp_dir_.path() / "ro_cache";
@@ -0,0 +1,17 @@
// 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 compile --target=x86_64-unknown-linux-gnu --target-cpu=not-a-valid-cpu --target-cpu-features=+not-a-feature foo.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/fail_target_cpu.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_target_cpu.carbon
// CHECK:STDERR: error: unknown target CPU 'not-a-valid-cpu' [CppInteropDriverError]
// CHECK:STDERR:
// CHECK:STDERR: error: unsupported argument '+not-a-feature' to option '-target-feature' [CppInteropDriverError]
// CHECK:STDERR:
// --- foo.carbon
+202
View File
@@ -0,0 +1,202 @@
// 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: compile --optimize=speed foo.carbon --target=x86_64-unknown-linux-gnu --target-cpu=x86-64-v2 --target-cpu-tune=znver4 --target-cpu-features=+avx2,-popcnt --clang-arg=-mlzcnt --phase=optimize --dump-llvm-ir --exclude-dump-file-prefix=%{core}
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/target_cpu.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/target_cpu.carbon
// --- foo.carbon
import Cpp inline '''
#ifndef __AVX2__
#error "__AVX2__ should be defined via --target-cpu-features=+avx2"
#endif
#ifdef __POPCNT__
#error "__POPCNT__ should be disabled via --target-cpu-features=-popcnt"
#endif
#ifndef __LAHF_SAHF__
#error "__LAHF_SAHF__ should be defined via --target-cpu=x86-64-v2"
#endif
#ifndef __LZCNT__
#error "__LZCNT__ should be defined via --clang-arg=-mlzcnt"
#endif
''';
fn VectorizedWithAvx2(a: array(i32, 65536)*) {
// With --target-cpu-features=+avx2, this loop should vectorize using
// 256-bit (<8 x i32>) vectors instead of baseline 128-bit (<4 x i32>).
var n: i32 = 0;
while (n < 65536) {
(*a)[n] *= 2;
++n;
}
}
// CHECK:STDOUT: ; ---
// CHECK:STDOUT: ; ModuleID = 'foo.carbon'
// CHECK:STDOUT: source_filename = "foo.carbon"
// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nofree norecurse nosync nounwind memory(argmem: readwrite)
// CHECK:STDOUT: define void @_CVectorizedWithAvx2.Main(ptr nofree captures(none) %a) local_unnamed_addr #0 !dbg !8 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: br label %vector.body, !dbg !13
// CHECK:STDOUT:
// CHECK:STDOUT: vector.body: ; preds = %vector.body, %entry
// CHECK:STDOUT: %index = phi i64 [ 0, %entry ], [ %index.next.3, %vector.body ], !dbg !80
// CHECK:STDOUT: %0 = getelementptr inbounds nuw [4 x i8], ptr %a, i64 %index, !dbg !18
// CHECK:STDOUT: %1 = getelementptr inbounds nuw i8, ptr %0, i64 32, !dbg !18
// CHECK:STDOUT: %2 = getelementptr inbounds nuw i8, ptr %0, i64 64, !dbg !18
// CHECK:STDOUT: %3 = getelementptr inbounds nuw i8, ptr %0, i64 96, !dbg !18
// CHECK:STDOUT: %wide.load = load <8 x i32>, ptr %0, align 4, !dbg !18
// CHECK:STDOUT: %wide.load5 = load <8 x i32>, ptr %1, align 4, !dbg !18
// CHECK:STDOUT: %wide.load6 = load <8 x i32>, ptr %2, align 4, !dbg !18
// CHECK:STDOUT: %wide.load7 = load <8 x i32>, ptr %3, align 4, !dbg !18
// CHECK:STDOUT: %4 = shl <8 x i32> %wide.load, splat (i32 1), !dbg !18
// CHECK:STDOUT: %5 = shl <8 x i32> %wide.load5, splat (i32 1), !dbg !18
// CHECK:STDOUT: %6 = shl <8 x i32> %wide.load6, splat (i32 1), !dbg !18
// CHECK:STDOUT: %7 = shl <8 x i32> %wide.load7, splat (i32 1), !dbg !18
// CHECK:STDOUT: store <8 x i32> %4, ptr %0, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %5, ptr %1, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %6, ptr %2, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %7, ptr %3, align 4, !dbg !18
// CHECK:STDOUT: %8 = getelementptr inbounds nuw [4 x i8], ptr %a, i64 %index, !dbg !18
// CHECK:STDOUT: %9 = getelementptr inbounds nuw i8, ptr %8, i64 128, !dbg !18
// CHECK:STDOUT: %10 = getelementptr inbounds nuw i8, ptr %8, i64 160, !dbg !18
// CHECK:STDOUT: %11 = getelementptr inbounds nuw i8, ptr %8, i64 192, !dbg !18
// CHECK:STDOUT: %12 = getelementptr inbounds nuw i8, ptr %8, i64 224, !dbg !18
// CHECK:STDOUT: %wide.load.1 = load <8 x i32>, ptr %9, align 4, !dbg !18
// CHECK:STDOUT: %wide.load5.1 = load <8 x i32>, ptr %10, align 4, !dbg !18
// CHECK:STDOUT: %wide.load6.1 = load <8 x i32>, ptr %11, align 4, !dbg !18
// CHECK:STDOUT: %wide.load7.1 = load <8 x i32>, ptr %12, align 4, !dbg !18
// CHECK:STDOUT: %13 = shl <8 x i32> %wide.load.1, splat (i32 1), !dbg !18
// CHECK:STDOUT: %14 = shl <8 x i32> %wide.load5.1, splat (i32 1), !dbg !18
// CHECK:STDOUT: %15 = shl <8 x i32> %wide.load6.1, splat (i32 1), !dbg !18
// CHECK:STDOUT: %16 = shl <8 x i32> %wide.load7.1, splat (i32 1), !dbg !18
// CHECK:STDOUT: store <8 x i32> %13, ptr %9, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %14, ptr %10, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %15, ptr %11, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %16, ptr %12, align 4, !dbg !18
// CHECK:STDOUT: %17 = getelementptr inbounds nuw [4 x i8], ptr %a, i64 %index, !dbg !18
// CHECK:STDOUT: %18 = getelementptr inbounds nuw i8, ptr %17, i64 256, !dbg !18
// CHECK:STDOUT: %19 = getelementptr inbounds nuw i8, ptr %17, i64 288, !dbg !18
// CHECK:STDOUT: %20 = getelementptr inbounds nuw i8, ptr %17, i64 320, !dbg !18
// CHECK:STDOUT: %21 = getelementptr inbounds nuw i8, ptr %17, i64 352, !dbg !18
// CHECK:STDOUT: %wide.load.2 = load <8 x i32>, ptr %18, align 4, !dbg !18
// CHECK:STDOUT: %wide.load5.2 = load <8 x i32>, ptr %19, align 4, !dbg !18
// CHECK:STDOUT: %wide.load6.2 = load <8 x i32>, ptr %20, align 4, !dbg !18
// CHECK:STDOUT: %wide.load7.2 = load <8 x i32>, ptr %21, align 4, !dbg !18
// CHECK:STDOUT: %22 = shl <8 x i32> %wide.load.2, splat (i32 1), !dbg !18
// CHECK:STDOUT: %23 = shl <8 x i32> %wide.load5.2, splat (i32 1), !dbg !18
// CHECK:STDOUT: %24 = shl <8 x i32> %wide.load6.2, splat (i32 1), !dbg !18
// CHECK:STDOUT: %25 = shl <8 x i32> %wide.load7.2, splat (i32 1), !dbg !18
// CHECK:STDOUT: store <8 x i32> %22, ptr %18, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %23, ptr %19, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %24, ptr %20, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %25, ptr %21, align 4, !dbg !18
// CHECK:STDOUT: %26 = getelementptr inbounds nuw [4 x i8], ptr %a, i64 %index, !dbg !18
// CHECK:STDOUT: %27 = getelementptr inbounds nuw i8, ptr %26, i64 384, !dbg !18
// CHECK:STDOUT: %28 = getelementptr inbounds nuw i8, ptr %26, i64 416, !dbg !18
// CHECK:STDOUT: %29 = getelementptr inbounds nuw i8, ptr %26, i64 448, !dbg !18
// CHECK:STDOUT: %30 = getelementptr inbounds nuw i8, ptr %26, i64 480, !dbg !18
// CHECK:STDOUT: %wide.load.3 = load <8 x i32>, ptr %27, align 4, !dbg !18
// CHECK:STDOUT: %wide.load5.3 = load <8 x i32>, ptr %28, align 4, !dbg !18
// CHECK:STDOUT: %wide.load6.3 = load <8 x i32>, ptr %29, align 4, !dbg !18
// CHECK:STDOUT: %wide.load7.3 = load <8 x i32>, ptr %30, align 4, !dbg !18
// CHECK:STDOUT: %31 = shl <8 x i32> %wide.load.3, splat (i32 1), !dbg !18
// CHECK:STDOUT: %32 = shl <8 x i32> %wide.load5.3, splat (i32 1), !dbg !18
// CHECK:STDOUT: %33 = shl <8 x i32> %wide.load6.3, splat (i32 1), !dbg !18
// CHECK:STDOUT: %34 = shl <8 x i32> %wide.load7.3, splat (i32 1), !dbg !18
// CHECK:STDOUT: store <8 x i32> %31, ptr %27, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %32, ptr %28, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %33, ptr %29, align 4, !dbg !18
// CHECK:STDOUT: store <8 x i32> %34, ptr %30, align 4, !dbg !18
// CHECK:STDOUT: %index.next.3 = add nuw nsw i64 %index, 128, !dbg !80
// CHECK:STDOUT: %35 = icmp eq i64 %index.next.3, 65536, !dbg !13
// CHECK:STDOUT: br i1 %35, label %while.done, label %vector.body, !dbg !13, !llvm.loop !88
// CHECK:STDOUT:
// CHECK:STDOUT: while.done: ; preds = %vector.body
// CHECK:STDOUT: ret void, !dbg !22
// CHECK:STDOUT:
// CHECK:STDOUT: ; uselistorder directives
// CHECK:STDOUT: uselistorder i64 %index, { 3, 0, 1, 2, 4 }
// CHECK:STDOUT: uselistorder ptr %0, { 3, 4, 0, 1, 2 }
// CHECK:STDOUT: uselistorder ptr %1, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %2, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %3, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %10, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %11, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %12, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %19, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %20, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %21, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %28, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %29, { 1, 0 }
// CHECK:STDOUT: uselistorder ptr %30, { 1, 0 }
// CHECK:STDOUT: uselistorder i64 %index.next.3, { 1, 0 }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nounwind
// CHECK:STDOUT: define weak_odr void @"_CSelfDestruct.72395be3dbbe8946:core.Destroy.Core"(ptr %self) local_unnamed_addr #1 !dbg !24 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret void, !dbg !26
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: attributes #0 = { nofree norecurse nosync nounwind memory(argmem: readwrite) "tune-cpu"="znver4" }
// CHECK:STDOUT: attributes #1 = { nounwind "tune-cpu"="znver4" }
// CHECK:STDOUT:
// CHECK:STDOUT: !llvm.dbg.cu = !{!1}
// CHECK:STDOUT: !llvm.module.flags = !{!62, !63, !64, !70, !71}
// CHECK:STDOUT: !llvm.errno.tbaa = !{!69}
// CHECK:STDOUT:
// CHECK:STDOUT: !0 = !DIFile(filename: "foo.carbon", directory: "")
// CHECK:STDOUT: !1 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !0, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !2 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
// CHECK:STDOUT: !3 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
// CHECK:STDOUT: !4 = !{null, !3}
// CHECK:STDOUT: !5 = !DISubroutineType(types: !4)
// CHECK:STDOUT: !6 = !{null, !2}
// CHECK:STDOUT: !7 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "VectorizedWithAvx2", linkageName: "_CVectorizedWithAvx2.Main", scope: null, file: !0, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !23)
// CHECK:STDOUT: !9 = !DILocalVariable(arg: 1, scope: !8, type: !3)
// CHECK:STDOUT: !13 = !DILocation(line: 21, column: 9, scope: !8)
// CHECK:STDOUT: !18 = !DILocation(line: 22, column: 5, scope: !8)
// CHECK:STDOUT: !22 = !DILocation(line: 17, column: 1, scope: !8)
// CHECK:STDOUT: !23 = !{!9}
// CHECK:STDOUT: !24 = distinct !DISubprogram(name: "SelfDestruct", linkageName: "_CSelfDestruct.72395be3dbbe8946:core.Destroy.Core", scope: null, file: !0, line: 20, type: !7, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !27)
// CHECK:STDOUT: !25 = !DILocalVariable(arg: 1, scope: !24, type: !2)
// CHECK:STDOUT: !26 = !DILocation(line: 20, column: 3, scope: !24)
// CHECK:STDOUT: !27 = !{!25}
// CHECK:STDOUT: !28 = !DIFile(filename: "{{.*}}/prelude/types/int.carbon", directory: "")
// CHECK:STDOUT: !29 = distinct !DISubprogram(name: "Op", linkageName: "_COp.Int.9452f4c51951679b.Core:Inc.Core.be1e879c1ad406d8", scope: null, file: !28, line: 396, type: !7, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !36)
// CHECK:STDOUT: !30 = !DILocalVariable(arg: 1, scope: !29, type: !2)
// CHECK:STDOUT: !33 = !{null, !2, !2}
// CHECK:STDOUT: !34 = !DISubroutineType(types: !33)
// CHECK:STDOUT: !36 = !{!30}
// CHECK:STDOUT: !37 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk:AddAssignWith.b59e374b0649c9ad.Core:Int.9452f4c51951679b.Core:AddAssignWith.ab03190a9e911cb4.Core.306167be98c24f26", scope: null, file: !28, line: 331, type: !34, spFlags: DISPFlagDefinition, unit: !1, retainedNodes: !44)
// CHECK:STDOUT: !38 = !DILocalVariable(arg: 1, scope: !37, type: !2)
// CHECK:STDOUT: !39 = !DILocalVariable(arg: 2, scope: !37, type: !2)
// CHECK:STDOUT: !44 = !{!38, !39}
// CHECK:STDOUT: !62 = !{i32 8, !"PIC Level", i32 2}
// CHECK:STDOUT: !63 = !{i32 7, !"PIE Level", i32 2}
// CHECK:STDOUT: !64 = !{i32 7, !"uwtable", i32 2}
// CHECK:STDOUT: !65 = !{!"Simple C++ TBAA"}
// CHECK:STDOUT: !66 = !{!"omnipotent char", !65, i64 0}
// CHECK:STDOUT: !67 = !{!"int", !66, i64 0}
// CHECK:STDOUT: !68 = !{!"__libc_errno", !67, i64 0}
// CHECK:STDOUT: !69 = !{!68, !67, i64 0}
// CHECK:STDOUT: !70 = !{i32 7, !"Dwarf Version", i32 5}
// CHECK:STDOUT: !71 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !78 = distinct !DILocation(line: 23, column: 5, scope: !8)
// CHECK:STDOUT: !79 = distinct !DILocation(line: 398, column: 5, scope: !29, inlinedAt: !78)
// CHECK:STDOUT: !80 = distinct !DILocation(line: 331, column: 3, scope: !37, inlinedAt: !79)
// CHECK:STDOUT: !85 = !{!"llvm.loop.isvectorized", i32 1}
// CHECK:STDOUT: !87 = !{!"llvm.loop.unroll.runtime.disable"}
// CHECK:STDOUT: !88 = distinct !{!88, !85, !87}
// CHECK:STDOUT: