diff --git a/toolchain/base/BUILD b/toolchain/base/BUILD index 73f63ba15d17..f2cee9e76fd2 100644 --- a/toolchain/base/BUILD +++ b/toolchain/base/BUILD @@ -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", ], ) diff --git a/toolchain/base/clang_invocation.cpp b/toolchain/base/clang_invocation.cpp index eefc3ad89d2f..f1a61057381b 100644 --- a/toolchain/base/clang_invocation.cpp +++ b/toolchain/base/clang_invocation.cpp @@ -7,14 +7,21 @@ #include #include +#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 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 subtarget_info( + target->createMCSubtargetInfo(triple, /*CPU=*/"", /*Features=*/"")); + if (subtarget_info) { + llvm::ArrayRef 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 fs, const InstallPaths& install_paths, llvm::StringRef target_str, llvm::ArrayRef extra_args) -> std::unique_ptr { - Diagnostics::ErrorTrackingConsumer error_tracker(consumer); - Diagnostics::NoLocEmitter emitter(&error_tracker); - - ClangDriverDiagnosticConsumer diagnostics_consumer(&emitter); - llvm::SmallVector 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 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 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; } diff --git a/toolchain/base/clang_invocation.h b/toolchain/base/clang_invocation.h index 258dde9c8f74..903e9a6cf6a5 100644 --- a/toolchain/base/clang_invocation.h +++ b/toolchain/base/clang_invocation.h @@ -5,6 +5,7 @@ #ifndef CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_ #define CARBON_TOOLCHAIN_BASE_CLANG_INVOCATION_H_ +#include #include #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 fs, const InstallPaths& install_paths, diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index b84b25d3167b..50d5230cd3af 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -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(*FrontendOpts); PPOpts = std::make_shared(*PPOpts); + TargetOpts = std::make_shared(*TargetOpts); } }; diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index 97559d31ae12..328a17af23d8 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -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", ], ) diff --git a/toolchain/driver/build_runtimes_subcommand.cpp b/toolchain/driver/build_runtimes_subcommand.cpp index a1f133ad4d1e..42668fced6cb 100644 --- a/toolchain/driver/build_runtimes_subcommand.cpp +++ b/toolchain/driver/build_runtimes_subcommand.cpp @@ -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 lib_unwind_builder( - &runner, driver_env.thread_pool, llvm::Triple(features.target), - &runtimes); + &runner, driver_env.thread_pool, &runtimes, features); ClangArchiveRuntimesBuilder 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); diff --git a/toolchain/driver/carbon_runtimes.cpp b/toolchain/driver/carbon_runtimes.cpp index f89cd7f36a9f..06fe3702fb17 100644 --- a/toolchain/driver/carbon_runtimes.cpp +++ b/toolchain/driver/carbon_runtimes.cpp @@ -102,11 +102,14 @@ auto CarbonPreludeBuilder::Build() && -> ErrorOr { 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_); diff --git a/toolchain/driver/clang_runner.cpp b/toolchain/driver/clang_runner.cpp index 03fc5e505970..b3cb155929b2 100644 --- a/toolchain/driver/clang_runner.cpp +++ b/toolchain/driver/clang_runner.cpp @@ -8,19 +8,19 @@ #include #include +#include #include #include #include #include #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 args) -> bool { }); } +auto ClangRunner::ComputeRuntimesFeatures(const CodegenOptions& codegen_options, + Diagnostics::Consumer& consumer) + -> ErrorOr { + llvm::SmallVector codegen_clang_args = + codegen_options.GetClangArgs(); + llvm::SmallVector extra_args(codegen_clang_args.begin(), + codegen_clang_args.end()); + std::unique_ptr 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::make_move_iterator(target_opts.Features.begin()), + std::make_move_iterator(target_opts.Features.end())), + }; +} + auto ClangRunner::RunWithPrebuiltRuntimes(llvm::ArrayRef args, Runtimes& prebuilt_runtimes, bool enable_leaking) @@ -179,7 +204,8 @@ auto ClangRunner::RunWithPrebuiltRuntimes(llvm::ArrayRef args, auto ClangRunner::Run(llvm::ArrayRef args, Runtimes::Cache& runtimes_cache, llvm::ThreadPoolInterface& runtimes_build_thread_pool, - bool enable_leaking) -> ErrorOr { + bool enable_leaking, Runtimes::Cache::Features features) + -> ErrorOr { 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 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 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 lib_unwind_builder( - this, &runtimes_build_thread_pool, llvm::Triple(features.target), - &runtimes); + this, &runtimes_build_thread_pool, &runtimes, features); ClangArchiveRuntimesBuilder 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, diff --git a/toolchain/driver/clang_runner.h b/toolchain/driver/clang_runner.h index 309c4d2e6307..f66f3d76e4b1 100644 --- a/toolchain/driver/clang_runner.h +++ b/toolchain/driver/clang_runner.h @@ -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 args, Runtimes::Cache& runtimes_cache, llvm::ThreadPoolInterface& runtimes_build_thread_pool, - bool enable_leaking = false) -> ErrorOr; + bool enable_leaking = false, Runtimes::Cache::Features features = {}) + -> ErrorOr; // Run Clang with the provided arguments and prebuilt runtimes. // @@ -104,6 +107,13 @@ class ClangRunner : ToolRunnerBase { auto RunWithNoRuntimes(llvm::ArrayRef args, bool enable_leaking = false) -> ErrorOr; + // 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; + private: friend class ClangRuntimesBuilderBase; diff --git a/toolchain/driver/clang_runner_test.cpp b/toolchain/driver/clang_runner_test.cpp index 456d0969fdc2..9c47e693b7a9 100644 --- a/toolchain/driver/clang_runner_test.cpp +++ b/toolchain/driver/clang_runner_test.cpp @@ -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 diff --git a/toolchain/driver/clang_runtimes.cpp b/toolchain/driver/clang_runtimes.cpp index 37255aa02baf..7b5966cba9b6 100644 --- a/toolchain/driver/clang_runtimes.cpp +++ b/toolchain/driver/clang_runtimes.cpp @@ -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 requires IsClangArchiveRuntimes ClangArchiveRuntimesBuilder::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(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", diff --git a/toolchain/driver/clang_runtimes.h b/toolchain/driver/clang_runtimes.h index 8d8f53ebd9cc..5c20db30cbc4 100644 --- a/toolchain/driver/clang_runtimes.h +++ b/toolchain/driver/clang_runtimes.h @@ -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 target_args_; ErrorOr 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; diff --git a/toolchain/driver/clang_runtimes_test.cpp b/toolchain/driver/clang_runtimes_test.cpp index e34f2e4d059a..077069febef6 100644 --- a/toolchain/driver/clang_runtimes_test.cpp +++ b/toolchain/driver/clang_runtimes_test.cpp @@ -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); diff --git a/toolchain/driver/codegen_options.cpp b/toolchain/driver/codegen_options.cpp index fdae3a6c4537..c0bcb46b5206 100644 --- a/toolchain/driver/codegen_options.cpp +++ b/toolchain/driver/codegen_options.cpp @@ -4,6 +4,15 @@ #include "toolchain/driver/codegen_options.h" +#include + +#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& 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(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& 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 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 { + llvm::SmallVector args; + AppendClangArgs(args); + return args; } } // namespace Carbon diff --git a/toolchain/driver/codegen_options.h b/toolchain/driver/codegen_options.h index 503258ec3d97..50707ca43723 100644 --- a/toolchain/driver/codegen_options.h +++ b/toolchain/driver/codegen_options.h @@ -5,7 +5,11 @@ #ifndef CARBON_TOOLCHAIN_DRIVER_CODEGEN_OPTIONS_H_ #define CARBON_TOOLCHAIN_DRIVER_CODEGEN_OPTIONS_H_ +#include + #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& 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 host = llvm::sys::getDefaultTargetTriple(); llvm::StringRef target; + llvm::StringRef target_cpu; + llvm::StringRef target_cpu_tune; + llvm::StringRef target_cpu_features; }; } // namespace Carbon diff --git a/toolchain/driver/compile_driver.cpp b/toolchain/driver/compile_driver.cpp index a67c30b2e470..33f61319f07f 100644 --- a/toolchain/driver/compile_driver.cpp +++ b/toolchain/driver/compile_driver.cpp @@ -11,6 +11,7 @@ #include #include +#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( diff --git a/toolchain/driver/compile_options.cpp b/toolchain/driver/compile_options.cpp index ca4eef2a1a62..837452d2be9a 100644 --- a/toolchain/driver/compile_options.cpp +++ b/toolchain/driver/compile_options.cpp @@ -497,13 +497,19 @@ auto CompileOptions::BuildClangInvocation(DriverEnv& driver_env) -> ErrorOr> { // TODO: Move this into `BuildClangInvocation` when it can accept an // optimization level. - llvm::SmallVector 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 codegen_clang_args = + codegen_options->GetClangArgs(); + llvm::SmallVector 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); diff --git a/toolchain/driver/config_subcommand.cpp b/toolchain/driver/config_subcommand.cpp index 3e8fa2243ea8..76bceedc125f 100644 --- a/toolchain/driver/config_subcommand.cpp +++ b/toolchain/driver/config_subcommand.cpp @@ -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& data) -> bool { + llvm::SmallVector codegen_clang_args = + codegen_options.GetClangArgs(); + llvm::SmallVector 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; diff --git a/toolchain/driver/driver_test.cpp b/toolchain/driver/driver_test.cpp index 9e30f71370f5..f5aa63914a0c 100644 --- a/toolchain/driver/driver_test.cpp +++ b/toolchain/driver/driver_test.cpp @@ -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 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 diff --git a/toolchain/driver/link_driver.cpp b/toolchain/driver/link_driver.cpp index 3979aa2d2cdd..0b558d565cc8 100644 --- a/toolchain/driver/link_driver.cpp +++ b/toolchain/driver/link_driver.cpp @@ -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 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 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()) { diff --git a/toolchain/driver/runtimes_cache.cpp b/toolchain/driver/runtimes_cache.cpp index adbfc93cca50..1135d1d19133 100644 --- a/toolchain/driver/runtimes_cache.cpp +++ b/toolchain/driver/runtimes_cache.cpp @@ -251,12 +251,21 @@ auto Runtimes::Cache::Lookup(const Features& features) -> ErrorOr { // within the cache. llvm::SHA256 entry_hasher; + auto update_with_separator = [&](llvm::StringRef str) { + entry_hasher.update(str); + entry_hasher.update(llvm::ArrayRef{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 entry_digest = entry_hasher.final(); std::filesystem::path entry_path = diff --git a/toolchain/driver/runtimes_cache.h b/toolchain/driver/runtimes_cache.h index b2710b9ab81e..a4a6cbde2b59 100644 --- a/toolchain/driver/runtimes_cache.h +++ b/toolchain/driver/runtimes_cache.h @@ -7,6 +7,7 @@ #include #include +#include #include #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 (`+` / `-` from + // `clang::TargetOptions::Features`). + llvm::SmallVector target_features = {}; }; Cache() = default; diff --git a/toolchain/driver/runtimes_cache_test.cpp b/toolchain/driver/runtimes_cache_test.cpp index d14d02b7f44b..a5cbe3118c97 100644 --- a/toolchain/driver/runtimes_cache_test.cpp +++ b/toolchain/driver/runtimes_cache_test.cpp @@ -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"; diff --git a/toolchain/driver/testdata/compile/fail_target_cpu.carbon b/toolchain/driver/testdata/compile/fail_target_cpu.carbon new file mode 100644 index 000000000000..6d3ff9accde9 --- /dev/null +++ b/toolchain/driver/testdata/compile/fail_target_cpu.carbon @@ -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 diff --git a/toolchain/driver/testdata/compile/target_cpu.carbon b/toolchain/driver/testdata/compile/target_cpu.carbon new file mode 100644 index 000000000000..a569b7a0c62b --- /dev/null +++ b/toolchain/driver/testdata/compile/target_cpu.carbon @@ -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: