mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 12:38:30 +00:00
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
226 lines
9.0 KiB
C++
226 lines
9.0 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include "toolchain/base/clang_invocation.h"
|
|
|
|
#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 {
|
|
|
|
// The fake file name to use for the synthesized includes file.
|
|
static constexpr const char IncludesFileName[] = "<carbon Cpp imports>";
|
|
|
|
auto ClangDriverDiagnosticConsumer::HandleDiagnostic(
|
|
clang::DiagnosticsEngine::Level diag_level, const clang::Diagnostic& info)
|
|
-> void {
|
|
DiagnosticConsumer::HandleDiagnostic(diag_level, info);
|
|
|
|
llvm::SmallString<256> message;
|
|
info.FormatDiagnostic(message);
|
|
|
|
switch (diag_level) {
|
|
case clang::DiagnosticsEngine::Ignored:
|
|
case clang::DiagnosticsEngine::Note:
|
|
case clang::DiagnosticsEngine::Remark: {
|
|
// TODO: Emit notes and remarks.
|
|
break;
|
|
}
|
|
case clang::DiagnosticsEngine::Warning:
|
|
case clang::DiagnosticsEngine::Error:
|
|
case clang::DiagnosticsEngine::Fatal: {
|
|
CARBON_DIAGNOSTIC(CppInteropDriverWarning, Warning, "{0}", std::string);
|
|
CARBON_DIAGNOSTIC(CppInteropDriverError, Error, "{0}", std::string);
|
|
emitter_->Emit(diag_level == clang::DiagnosticsEngine::Warning
|
|
? CppInteropDriverWarning
|
|
: CppInteropDriverError,
|
|
message.str().str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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> {
|
|
llvm::SmallVector<std::string> args;
|
|
args.push_back("--start-no-unused-arguments");
|
|
AppendDefaultClangArgs(install_paths, target_str, args);
|
|
args.push_back("--end-no-unused-arguments");
|
|
args.append({
|
|
llvm::formatv("--target={0}", target_str).str(),
|
|
|
|
// Add our include file name as the input file, and force it to be
|
|
// interpreted as C++.
|
|
"-x",
|
|
"c++",
|
|
IncludesFileName,
|
|
});
|
|
|
|
// The clang driver inconveniently wants an array of `const char*`, so convert
|
|
// the arguments.
|
|
llvm::BumpPtrAllocator alloc;
|
|
llvm::SmallVector<const char*> cstr_args = BuildCStrArgs(
|
|
install_paths.clang_path().native(), args, extra_args, alloc);
|
|
|
|
// 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,
|
|
&diagnostics_consumer,
|
|
/*ShouldOwnClient=*/false));
|
|
|
|
// Ask the driver to process the arguments and build a corresponding clang
|
|
// frontend 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 (!invocation || driver_diags->hasErrorOccurred() ||
|
|
!ValidateTargetOptions(*driver_diags, invocation->getTargetOpts())) {
|
|
return nullptr;
|
|
}
|
|
|
|
// 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;
|
|
|
|
return invocation;
|
|
}
|
|
|
|
auto AppendDefaultClangArgs(const InstallPaths& install_paths,
|
|
llvm::StringRef target_str,
|
|
llvm::SmallVectorImpl<std::string>& args) -> void {
|
|
args.append({
|
|
// Enable PIE by default, but allow it to be overridden by Clang
|
|
// arguments. Clang's default is configurable, but we'd like our
|
|
// defaults to be more stable.
|
|
// TODO: Decide if we want this.
|
|
"-fPIE",
|
|
|
|
// Enable function and data sections by default, and don't waste object
|
|
// file size on unique section names. Allow these to be overridden by
|
|
// Clang arguments.
|
|
"-ffunction-sections",
|
|
"-fdata-sections",
|
|
"-fno-unique-section-names",
|
|
|
|
// Override runtime library defaults.
|
|
//
|
|
// TODO: We should consider if there is a reasonable way to build Clang
|
|
// with its configuration macros set to establish these defaults rather
|
|
// than doing it with runtime flags.
|
|
"-rtlib=compiler-rt",
|
|
"-unwindlib=libunwind",
|
|
"-stdlib=libc++",
|
|
|
|
// Override the default linker to use.
|
|
"-fuse-ld=lld",
|
|
});
|
|
|
|
// Add target-specific flags.
|
|
llvm::Triple triple(target_str);
|
|
switch (triple.getOS()) {
|
|
case llvm::Triple::Darwin:
|
|
case llvm::Triple::MacOSX:
|
|
// On macOS we need to set the sysroot to a viable SDK. Currently, this
|
|
// hard codes the path to be the unversioned symlink. The prefix is also
|
|
// hard coded in Homebrew and so this seems likely to work reasonably
|
|
// well. Homebrew and I suspect the Xcode Clang both have this hard coded
|
|
// at build time, so this seems reasonably safe but we can revisit if/when
|
|
// needed.
|
|
args.push_back(
|
|
"--sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk");
|
|
|
|
// We also need to insist on a modern linker, otherwise the driver tries
|
|
// too old and deprecated flags. The specific number here comes from an
|
|
// inspection of the Clang driver source code to understand where features
|
|
// were enabled, and this appears to be the latest version to control
|
|
// driver behavior.
|
|
//
|
|
// TODO: We should replace this with use of `lld` eventually.
|
|
args.push_back("-mlinker-version=705");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Append our exact header search paths for the various parts of the C++
|
|
// standard library headers as we don't build a single unified tree.
|
|
for (const std::filesystem::path& runtime_path :
|
|
{install_paths.libunwind_path(), install_paths.libcxx_path(),
|
|
install_paths.libcxxabi_path()}) {
|
|
args.push_back(
|
|
llvm::formatv("-stdlib++-isystem{0}", runtime_path / "include").str());
|
|
}
|
|
}
|
|
|
|
} // namespace Carbon
|