Files
carbon-lang/toolchain/base/clang_invocation.cpp
Chandler Carruth 0546b48a7d 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
2026-10-21 00:43:54 +00:00

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