Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement -Zgcc-ld=lld stabilization MCP #96401

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 147 additions & 47 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ use rustc_fs_util::fix_windows_verbatim_for_gcc;
use rustc_hir::def_id::CrateNum;
use rustc_middle::middle::dependency_format::Linkage;
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip};
use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, SplitDwarfKind};
use rustc_session::config::{
LinkerFlavorCli, OutputFilenames, OutputType, PrintRequest, SplitDwarfKind,
};
use rustc_session::cstore::DllImport;
use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
use rustc_session::search_paths::PathKind;
use rustc_session::utils::NativeLibKind;
/// For all the linkers we support, and information they might
/// need out of the shared crate context before we get rid of it.
use rustc_session::{filesearch, Session};
use rustc_session::{config::InstrumentCoverage, filesearch, Session};
use rustc_span::symbol::Symbol;
use rustc_target::spec::crt_objects::{CrtObjects, CrtObjectsFallback};
use rustc_target::spec::{LinkOutputKind, LinkerFlavor, LldFlavor, SplitDebuginfo};
Expand Down Expand Up @@ -1136,7 +1138,7 @@ pub fn ignored_for_lto(sess: &Session, info: &CrateInfo, cnum: CrateNum) -> bool
&& (info.compiler_builtins == Some(cnum) || info.is_no_builtins.contains(&cnum))
}

// This functions tries to determine the appropriate linker (and corresponding LinkerFlavor) to use
/// This functions tries to determine the appropriate linker (and corresponding LinkerFlavor) to use
pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
fn infer_from(
sess: &Session,
Expand Down Expand Up @@ -1209,9 +1211,13 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
}
}

// linker and linker flavor specified via command line have precedence over what the target
// specification specifies
if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), sess.opts.cg.linker_flavor) {
// Lower the potential `-C linker-flavor` CLI flag to its principal linker-flavor
let linker_flavor =
sess.opts.cg.linker_flavor.as_ref().map(|surface_flavor| surface_flavor.to_flavor());

// The `-C linker` and `-C linker-flavor` CLI flags have higher priority than what the target
// specification declares.
if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), linker_flavor) {
return ret;
}

Expand Down Expand Up @@ -1533,7 +1539,7 @@ fn detect_self_contained_mingw(sess: &Session) -> bool {
/// Whether we link to our own CRT objects instead of relying on gcc to pull them.
/// We only provide such support for a very limited number of targets.
fn crt_objects_fallback(sess: &Session, crate_type: CrateType) -> bool {
if let Some(self_contained) = sess.opts.cg.link_self_contained {
if let Some(self_contained) = sess.opts.cg.link_self_contained.crt.is_explicitly_set() {
return self_contained;
}

Expand Down Expand Up @@ -1917,7 +1923,7 @@ fn add_order_independent_options(
out_filename: &Path,
tmpdir: &Path,
) {
add_gcc_ld_path(cmd, sess, flavor);
handle_cli_linker_flavors(cmd, sess, flavor, crt_objects_fallback);

add_apple_sdk(cmd, sess, flavor);

Expand Down Expand Up @@ -2569,46 +2575,140 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, String> {
}
}

fn add_gcc_ld_path(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
if let Some(ld_impl) = sess.opts.debugging_opts.gcc_ld {
if let LinkerFlavor::Gcc = flavor {
match ld_impl {
LdImpl::Lld => {
if sess.target.lld_flavor == LldFlavor::Ld64 {
let tools_path = sess.get_tools_search_paths(false);
let ld64_exe = tools_path
.into_iter()
.map(|p| p.join("gcc-ld"))
.map(|p| {
p.join(if sess.host.is_like_windows { "ld64.exe" } else { "ld64" })
})
.find(|p| p.exists())
.unwrap_or_else(|| sess.fatal("rust-lld (as ld64) not found"));
cmd.cmd().arg({
let mut arg = OsString::from("-fuse-ld=");
arg.push(ld64_exe);
arg
});
} else {
let tools_path = sess.get_tools_search_paths(false);
let lld_path = tools_path
.into_iter()
.map(|p| p.join("gcc-ld"))
.find(|p| {
p.join(if sess.host.is_like_windows { "ld.exe" } else { "ld" })
.exists()
})
.unwrap_or_else(|| sess.fatal("rust-lld (as ld) not found"));
cmd.cmd().arg({
let mut arg = OsString::from("-B");
arg.push(lld_path);
arg
});
}
}
/// This takes care of the various possible enrichments to the linking that can be requested on the
/// CLI (and emitting errors and warnings when applicable):
/// - shortcuts to `-fuse-ld` with the `gcc` flavor
/// - the unstable `-Zgcc-ld=lld` flag to use `rust-lld`, stabilized as the following item
/// - the combination of these two: opting into using `lld` and the self-contained linker, to use
/// the `rustup` distributed `rust-lld`
fn handle_cli_linker_flavors(
cmd: &mut dyn Linker,
sess: &Session,
flavor: LinkerFlavor,
crt_objects_fallback: bool,
) {
let unstable_gcc_lld = sess.opts.debugging_opts.gcc_ld == Some(LdImpl::Lld);
if unstable_gcc_lld {
// Sanity check: ensure `gcc` is the currently selected flavor.
if LinkerFlavor::Gcc != flavor {
sess.fatal("`-Zgcc-ld` is used even though the linker flavor is not `gcc`");
}
}

let cg = &sess.opts.cg;
let unstable_self_contained_linker = cg.link_self_contained.linker.is_on();

// Sanity check for distro-builds: they don't distribute `rust-lld`, and the `-C
// link-self-contained=linker` flag cannot be used there: the `rust.lld` flag has to be enabled
// in `config.toml` so that `#[cfg(rust_lld_enabled)] applies.
if unstable_self_contained_linker && !cfg!(rust_lld_enabled) {
sess.fatal(
"Using `-Clink-self-contained=linker` requires the \
compiler to be built with the `rust.lld` flag enabled",
);
}

// The `-C linker-flavor` CLI flag can optionally enrich linker-flavors. Check whether that's
// applicable, and emit errors if sanity checks fail. There's currently only one enrichment:
// adding an argument to the `cc` invocation to use the `use_ld` given linker.
let use_ld = match &cg.linker_flavor {
Some(LinkerFlavorCli::Gcc { use_ld }) => {
// Ensure `gcc` is the currently selected flavor. Error out cleanly, as `-Zgcc-ld` does
// if that happens, but this should be unreachable.
if LinkerFlavor::Gcc != flavor {
sess.fatal(
"`-Clinker-flavor=gcc:*` flag is used even though the \
linker flavor is not `gcc`",
);
}

use_ld
}

// Note: exhaustive match arm here, to avoid fallthroughs if new linker-flavor enrichments
// are added in the future.
Some(LinkerFlavorCli::WellKnown(_)) | None => {
if unstable_gcc_lld {
"lld"
} else {
// We're not in a situation needing enrichments.
return;
}
}
};

// From now, we handle the `gcc` linker-flavor enrichment.
let mut cc_arg = OsString::new();

// Except for `lld`, the given linker executable will be passed straight to `-fuse-ld`.
if use_ld == "lld" {
// Start by checking if we're in the context of a known issue that users might hit when
// using `lld`:
//
// 1. when requesting self-contained CRT linking (or on a target that does it
// automatically), and coverage/profile generation: point at #79555 "Coverage is not
// generated when using rust-lld as linker"
let instrument_coverage = cg.instrument_coverage.is_some()
&& cg.instrument_coverage != Some(InstrumentCoverage::Off);
let generate_profile = cg.profile_generate.enabled();
if crt_objects_fallback && (instrument_coverage || generate_profile) {
sess.warn(
"Using `lld`, self-contained linking, and coverage or profile generation has known \
issues. See issue #79555 for more details, at \
https://github.com/rust-lang/rust/issues/79555",
);
}

// 2. Maybe point at https://github.com/flamegraph-rs/flamegraph/pull/157 or the
// corresponding rust/LLVM issue when/if it's tracked, depending on whether we use the
// workaround argument `--no-rosegment` by default when invoking `lld`.
//
// 3. If in the future, other linker flavors and targets are eligible to a `rust-lld`
// enrichment, maybe also point at target-specific issues like:
// - MSVC + ThinLTO blocker https://github.com/rust-lang/rust/issues/81408
// - the "lld on MSVC" tracking issue https://github.com/rust-lang/rust/issues/71520
// containing a list of blocking issues

// Now, handle `rust-lld`. If both the `-Clink-self-contained=linker` and
// `-Clinker-flavor=gcc:lld` flags were provided, we use `rust-lld`, the rustup-distributed
// version of `lld` (when applicable`, i.e. not in distro-builds) by:
// - checking the `lld-wrapper`s exist in the sysroot
// - adding their folder as a search path, or requesting to use a wrapper directly
if unstable_self_contained_linker || unstable_gcc_lld {
// A `gcc-ld` folder (containing the `lld-wrapper`s that will run `rust-lld`) is present in
// the sysroot's target-specific tool binaries folder.
let tools_path = sess.get_tools_search_paths(false);
let mut possible_gcc_ld_paths = tools_path.into_iter().map(|p| p.join("gcc-ld"));

// Set-up the correct flag and argument to find the wrapper:
// - a path to the `ld64` wrapper needs to be passed with `-fuse-ld` on Apple targets
// - otherwise, a `-B` search path to the `gcc-ld` folder is enough
let (cc_flag, lld_wrapper_path) = if sess.target.lld_flavor == LldFlavor::Ld64 {
let ld64_exe = if sess.host.is_like_windows { "ld64.exe" } else { "ld64" };
let ld64_path = possible_gcc_ld_paths
.map(|p| p.join(ld64_exe))
.find(|p| p.exists())
.unwrap_or_else(|| sess.fatal("rust-lld (as ld64) not found"));
("-fuse-ld=", ld64_path)
} else {
let ld_exe = if sess.host.is_like_windows { "ld.exe" } else { "ld" };
let ld_path = possible_gcc_ld_paths
.find(|p| p.join(ld_exe).exists())
.unwrap_or_else(|| sess.fatal("rust-lld (as ld) not found"));
("-B", ld_path)
};
cc_arg.push(cc_flag);
cc_arg.push(lld_wrapper_path);
} else {
sess.fatal("option `-Z gcc-ld` is used even though linker flavor is not gcc");
// We were asked to use `lld` but not `rust-lld`.
cc_arg.push("-fuse-ld=lld");
}
}
} else {
// Otherwise, we were just asked to use a linker executable, and it's expected that `cc`
// will find it on the $PATH.
cc_arg.push("-fuse-ld=");
cc_arg.push(use_ld);
};

cmd.cmd().arg(cc_arg);
}
9 changes: 6 additions & 3 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::interface::parse_cfgspecs;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig};
use rustc_session::config::InstrumentCoverage;
use rustc_session::config::LinkSelfContained;
use rustc_session::config::Strip;
use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
use rustc_session::config::{
Expand All @@ -12,7 +13,9 @@ use rustc_session::config::{
BranchProtection, Externs, OomStrategy, OutputType, OutputTypes, PAuthKey, PacRet,
SymbolManglingVersion, WasiExecModel,
};
use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath};
use rustc_session::config::{
CFGuard, ExternEntry, LinkerFlavorCli, LinkerPluginLto, LtoCli, SwitchWithOptPath,
};
use rustc_session::lint::Level;
use rustc_session::search_paths::SearchPath;
use rustc_session::utils::{CanonicalizedPath, NativeLib, NativeLibKind};
Expand Down Expand Up @@ -549,9 +552,9 @@ fn test_codegen_options_tracking_hash() {
untracked!(incremental, Some(String::from("abc")));
// `link_arg` is omitted because it just forwards to `link_args`.
untracked!(link_args, vec![String::from("abc"), String::from("def")]);
untracked!(link_self_contained, Some(true));
untracked!(link_self_contained, LinkSelfContained::on());
untracked!(linker, Some(PathBuf::from("linker")));
untracked!(linker_flavor, Some(LinkerFlavor::Gcc));
untracked!(linker_flavor, Some(LinkerFlavorCli::WellKnown(LinkerFlavor::Gcc)));
untracked!(no_stack_check, true);
untracked!(remark, Passes::Some(vec![String::from("pass1"), String::from("pass2")]));
untracked!(rpath, true);
Expand Down
Loading