diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 4d20ed841d47c..c9936beb99940 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -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}; @@ -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, @@ -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; } @@ -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; } @@ -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); @@ -2569,46 +2575,140 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result { } } -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); } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 1327bf6fcd427..66d665b8456ff 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -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::{ @@ -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}; @@ -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); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 925f6bfd93d3c..21ffe43e746e7 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -199,6 +199,198 @@ impl LinkerPluginLto { } } +/// The possible values `-C linker-flavor` can take: either one of the well-known linker flavors, or +/// an enrichment to one of them, for example representing additional arguments to its principal +/// linker flavor. +/// +/// This a surface enum for the CLI syntax, so that target specs don't have to deal with the +/// specifics of the CLI: they will always use the well-known principal linker flavor via the +/// `to_flavor` method, and never the enrichment variants. +/// +/// Currently used to represent finer-grained uses of `gcc`, to improve ease-of-use for wrapping a +/// given linker like `lld`, `gold` or `mold`. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum LinkerFlavorCli { + /// When the CLI option is one of the well-known linker-flavors that don't need special + /// handling. + WellKnown(LinkerFlavor), + + /// Enrichments to the `LinkerFlavor::Gcc` flavor, to specify the linker via `-fuse-ld` + Gcc { use_ld: String }, +} + +impl LinkerFlavorCli { + /// Returns the principal linker flavor that this CLI option represents. + pub fn to_flavor(&self) -> LinkerFlavor { + match *self { + LinkerFlavorCli::WellKnown(flavor) => flavor, + LinkerFlavorCli::Gcc { .. } => LinkerFlavor::Gcc, + } + } +} + +/// Parses a `-C linker-flavor` option +impl FromStr for LinkerFlavorCli { + type Err = (); + + fn from_str(s: &str) -> Result { + // If the value is one of the existing flavor mappings, return that. + if let Some(flavor) = LinkerFlavor::from_str(s) { + return Ok(LinkerFlavorCli::WellKnown(flavor)); + } + + // Otherwise, it should be the enrichments to the gcc/cc flavor: wrapping a given linker + // separated by a colon like `gcc:lld`. + let parts: Vec<_> = s.split("gcc:").collect(); + if parts.len() != 2 { + return Err(()); + } + + let wrapped_linker = parts[1]; + if !wrapped_linker.is_empty() { + Ok(LinkerFlavorCli::Gcc { use_ld: wrapped_linker.to_string() }) + } else { + Err(()) + } + } +} + +/// The different values `-C link-self-contained` can take. +/// +/// They are fine-grained, and control the behavior of: +/// - linking to our own CRT objects (the historical default) +/// - using a linker we distribute +/// +/// Since this flag historically targeted the CRT use-case, the defaults haven't changed, but there +/// are new values so that users can choose that behavior in combination with the linker use-case. +/// +/// For example: +/// - the absence of the `-C link-self-contained` was the historical default, and therefore this +/// default value still targets `auto` (`LinkSelfContainedCrt::Auto`). +/// - `-C link-self-contained=linker` turns on the linker, while keeping the default CRT behavior. +/// - explicitly turning on the CRT linking can be done as the historical `-C link-self-contained=y`, +/// or the fine-grained `-C link-self-contained=crt` +/// - turning both on can be done with `-C link-self-contained=all` +/// +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct LinkSelfContained { + pub crt: LinkSelfContainedCrt, + pub linker: LinkSelfContainedLinker, +} + +impl LinkSelfContained { + /// Explicitly turns off the self-contained linker facet, becoming an opt-out that is the + /// historically stable `-C link-self-contained=y` behavior. + fn crt_only() -> Self { + LinkSelfContained { crt: LinkSelfContainedCrt::On, linker: LinkSelfContainedLinker::Off } + } + + /// Keeps stable behavior only turning the self-contained CRT linking on. + pub fn on() -> Self { + LinkSelfContained::crt_only() + } + + fn off() -> Self { + LinkSelfContained { crt: LinkSelfContainedCrt::Off, linker: LinkSelfContainedLinker::Off } + } + + /// Explicitly turns off the self-contained linker facet, becoming an opt-out that is the + /// historically stable default, target-defined, behavior. + /// + /// The current default. + fn auto() -> Self { + LinkSelfContained { crt: LinkSelfContainedCrt::Auto, linker: LinkSelfContainedLinker::Off } + } + + fn linker() -> Self { + LinkSelfContained { crt: LinkSelfContainedCrt::Auto, linker: LinkSelfContainedLinker::On } + } + + fn all() -> Self { + LinkSelfContained { crt: LinkSelfContainedCrt::On, linker: LinkSelfContainedLinker::On } + } +} + +impl Default for LinkSelfContained { + fn default() -> Self { + LinkSelfContained::auto() + } +} + +impl FromStr for LinkSelfContained { + type Err = (); + + fn from_str(s: &str) -> Result { + // TMP: do we also need "linker_only", crt:off and linker:on ? Except in rare targets, this + // use-case should be taken care of by "linker" for crt:auto linker:on. + + Ok(match s { + // Historical value parsing: a bool option + "no" | "n" | "off" => LinkSelfContained::off(), + "yes" | "y" | "on" => LinkSelfContained::on(), + + // New fine-grained facets + "crt" => { + // This is the same as `-C link-self-contained=y`, but allows to possibly change + // that while keeping the current only turning on self-contained CRT linking. Also + // makes an explicit value to opt into only a single facet. + LinkSelfContained::crt_only() + } + "auto" => LinkSelfContained::auto(), + "linker" => LinkSelfContained::linker(), + + // Composite of both + "all" => LinkSelfContained::all(), + _ => return Err(()), + }) + } +} + +/// Historically, `-C link-self-contained` was mainly about linking to our own CRT objects. This has +/// been extracted to a few dedicated values, while keeping the existing stable values as-is: the +/// default value, or opt-in for the whole flag are still these CRT-related values. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum LinkSelfContainedCrt { + /// The historical default value, when `-C link-self-contained` was not specified.The + /// self-contained linking behavior is target-defined in this case. + Auto, + + /// The historical value for `-C link-self-contained=y`. + On, + + /// When self-contained linking is explicitly turned off, with `-C link-self-contained=n` + Off, +} + +impl LinkSelfContainedCrt { + pub fn is_explicitly_set(&self) -> Option { + match self { + LinkSelfContainedCrt::On => Some(true), + LinkSelfContainedCrt::Off => Some(false), + LinkSelfContainedCrt::Auto => None, + } + } +} + +/// The different linker-related options for `-C link-self-contained`, to choose between +/// using system-installed linkers, or one present in the rust distribution. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum LinkSelfContainedLinker { + /// Whenever `-C link-self-contained=linker` is present. This opts in to using + /// a linker we distribute (e.g. `rust-lld`). + On, + + /// The default case: when `-C link-self-contained=linker` is absent, and when self-contained + /// linking is explicitly turned off, with `-C link-self-contained=n`. + Off, +} + +impl LinkSelfContainedLinker { + pub fn is_on(&self) -> bool { + *self == LinkSelfContainedLinker::On + } +} + /// The different settings that can be enabled via the `-Z location-detail` flag. #[derive(Clone, PartialEq, Hash, Debug)] pub struct LocationDetail { @@ -2378,7 +2570,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { } } - if cg.linker_flavor == Some(LinkerFlavor::L4Bender) + if cg.linker_flavor == Some(LinkerFlavorCli::WellKnown(LinkerFlavor::L4Bender)) && !nightly_options::is_unstable_enabled(matches) { early_error( @@ -2388,6 +2580,48 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { ); } + if let Some(LinkerFlavorCli::Gcc { use_ld }) = &cg.linker_flavor { + // For testing purposes, until we have more feedback about these options: ensure `-Z + // unstable-options` enabled when using the `gcc` linker flavor enrichments. + if !debugging_opts.unstable_options { + early_error( + error_format, + "the `gcc:*` linker flavor is unstable, the `-Z unstable-options` \ + flag must also be passed to use it", + ); + } + + // Until the unstable flag is removed, ensure `-Zgcc-ld=lld` and `-Clinker-flavor=gcc:lld` + // have a matching linker choice. + if use_ld != "lld" && debugging_opts.gcc_ld == Some(LdImpl::Lld) { + early_error( + error_format, + "`-Zgcc-ld=lld` and `-Clinker-flavor` differ in their \ + linker choice. The latter should be `-Clinker-flavor=gcc:lld`", + ); + } + } + + // For testing purposes, until we have more feedback about these options: ensure `-Z + // unstable-options` is enabled when using the unstable `-C link-self-contained` options. + if !debugging_opts.unstable_options { + let uses_unstable_self_contained_option = + matches.opt_strs("C").iter().any(|option| match option.as_str() { + "link-self-contained=crt" + | "link-self-contained=auto" + | "link-self-contained=linker" + | "link-self-contained=all" => true, + _ => false, + }); + if uses_unstable_self_contained_option { + early_error( + error_format, + "only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, \ + the `-Z unstable-options` flag must also be passed to use the unstable values", + ); + } + } + let prints = collect_print_requests(&mut cg, &mut debugging_opts, matches, error_format); let cg = cg; diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index 054b18b6b633a..a6200909e8246 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -34,6 +34,9 @@ pub mod output; pub use getopts; +#[cfg(test)] +mod tests; + /// Requirements for a `StableHashingContext` to be used in this crate. /// This is a hack to allow using the `HashStable_Generic` derive macro /// instead of implementing everything in `rustc_middle`. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 96f50e57ac4d0..42cb1089be0e5 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -5,7 +5,7 @@ use crate::lint; use crate::search_paths::SearchPath; use crate::utils::NativeLib; use rustc_errors::LanguageIdentifier; -use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet}; +use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, SanitizerSet}; use rustc_target::spec::{ RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, }; @@ -385,7 +385,9 @@ mod desc { "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`"; pub const parse_cfprotection: &str = "`none`|`no`|`n` (default), `branch`, `return`, or `full`|`yes`|`y` (equivalent to `branch` and `return`)"; pub const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`"; - pub const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavor::one_of(); + pub const parse_linker_flavor: &str = "one of: `em`, `gcc`, `l4-bender`, `ld`, `msvc`, \ + `ptx-linker`, `bpf-linker`, `wasm-ld`, `ld64.lld`, `ld.lld`, `lld-link`, \ + or a `gcc:`-prefixed linker to use with the gcc flavor, like `gcc:lld` or `gcc:gold,`"; pub const parse_optimization_fuel: &str = "crate=integer"; pub const parse_mir_spanview: &str = "`statement` (default), `terminator`, or `block`"; pub const parse_instrument_coverage: &str = @@ -414,6 +416,10 @@ mod desc { pub const parse_split_dwarf_kind: &str = "one of supported split dwarf modes (`split` or `single`)"; pub const parse_gcc_ld: &str = "one of: no value, `lld`"; + // FIXME: add link to reference or other documentation, where the `-Clink-self-contained` + // options will be explained in more detail. + pub const parse_link_self_contained: &str = + "one of: `y`, `yes`, `on`, `n`, `no`, `off`, `auto`, `crt`, `linker`, `all`"; pub const parse_stack_protector: &str = "one of (`none` (default), `basic`, `strong`, or `all`)"; pub const parse_branch_protection: &str = @@ -752,8 +758,8 @@ mod parse { true } - crate fn parse_linker_flavor(slot: &mut Option, v: Option<&str>) -> bool { - match v.and_then(LinkerFlavor::from_str) { + crate fn parse_linker_flavor(slot: &mut Option, v: Option<&str>) -> bool { + match v.and_then(|s| LinkerFlavorCli::from_str(s).ok()) { Some(lf) => *slot = Some(lf), _ => return false, } @@ -1006,6 +1012,20 @@ mod parse { true } + /// Parses `-C link-self-contained`: it used to be a boolean without a static default, but now + /// also accepts some strings, in addition to the regular boolean values. + crate fn parse_link_self_contained(slot: &mut LinkSelfContained, v: Option<&str>) -> bool { + // Whenever `-C link-self-contained` is passed without a value, it's an opt-in + // just like `parse_opt_bool`. + let s = v.unwrap_or("y"); + + match LinkSelfContained::from_str(s).ok() { + Some(value) => *slot = value, + None => return false, + } + true + } + crate fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool { match v.and_then(|s| StackProtector::from_str(s).ok()) { Some(ssp) => *slot = ssp, @@ -1092,12 +1112,12 @@ options! { "extra arguments to append to the linker invocation (space separated)"), link_dead_code: Option = (None, parse_opt_bool, [TRACKED], "keep dead code at link time (useful for code coverage) (default: no)"), - link_self_contained: Option = (None, parse_opt_bool, [UNTRACKED], + link_self_contained: LinkSelfContained = (LinkSelfContained::default(), parse_link_self_contained, [UNTRACKED], "control whether to link Rust provided C objects/libraries or rely on C toolchain installed in the system"), linker: Option = (None, parse_opt_pathbuf, [UNTRACKED], "system linker to link outputs with"), - linker_flavor: Option = (None, parse_linker_flavor, [UNTRACKED], + linker_flavor: Option = (None, parse_linker_flavor, [UNTRACKED], "linker flavor"), linker_plugin_lto: LinkerPluginLto = (LinkerPluginLto::Disabled, parse_linker_plugin_lto, [TRACKED], @@ -1575,7 +1595,7 @@ pub enum WasiExecModel { Reactor, } -#[derive(Clone, Copy, Hash)] +#[derive(Clone, Copy, Hash, PartialEq)] pub enum LdImpl { Lld, } diff --git a/compiler/rustc_session/src/tests.rs b/compiler/rustc_session/src/tests.rs new file mode 100644 index 0000000000000..385560b3f328b --- /dev/null +++ b/compiler/rustc_session/src/tests.rs @@ -0,0 +1,123 @@ +use std::str::FromStr; + +use crate::config::*; +use rustc_target::spec::{LinkerFlavor, LldFlavor}; + +/// When adding support for `-C link-self-contained=linker`, we want to ensure the existing +/// values are still supported as-is: they are option values that can be used on stable. +#[test] +pub fn parse_stable_self_contained() { + // The existing default is when the argument is not used, the behavior depends on the + // target. + assert_eq!( + LinkSelfContained::default(), + LinkSelfContained { crt: LinkSelfContainedCrt::Auto, linker: LinkSelfContainedLinker::Off } + ); + + // Turning the flag `on` should currently only enable the default on stable. + assert_eq!( + LinkSelfContained::from_str("on"), + Ok(LinkSelfContained { + crt: LinkSelfContainedCrt::On, + linker: LinkSelfContainedLinker::Off + }) + ); + + // Turning the flag `off` applies to both facets. + assert_eq!( + LinkSelfContained::from_str("off"), + Ok(LinkSelfContained { + crt: LinkSelfContainedCrt::Off, + linker: LinkSelfContainedLinker::Off + }) + ); + + assert_eq!( + LinkSelfContained::from_str("crt"), + Ok(LinkSelfContained { + crt: LinkSelfContainedCrt::On, + linker: LinkSelfContainedLinker::Off + }) + ); +} + +#[test] +pub fn parse_self_contained_with_linker() { + // Turning the linker on doesn't change the CRT behavior + assert_eq!( + LinkSelfContained::from_str("linker"), + Ok(LinkSelfContained { + crt: LinkSelfContainedCrt::Auto, + linker: LinkSelfContainedLinker::On + }) + ); + + assert_eq!( + LinkSelfContained::from_str("all"), + Ok(LinkSelfContained { + crt: LinkSelfContainedCrt::On, + linker: LinkSelfContainedLinker::On + }) + ); + + // If `linker` is turned on by default someday, we need to be able to go back to the current + // default. + assert_eq!( + LinkSelfContained::from_str("auto"), + Ok(LinkSelfContained { + crt: LinkSelfContainedCrt::Auto, + linker: LinkSelfContainedLinker::Off + }) + ); +} + +/// When adding enrichments to `-C linker-flavor`, we want to ensure the existing `rustc_target` +/// `LinkerFlavor`s are still supported as-is: they are option values that can be used on +/// stable. +#[test] +pub fn parse_well_known_linker_flavor() { + // All `LinkerFlavor`s are wrapped as a whole, so there's no particular need to be + // exhaustive here. + assert_eq!(LinkerFlavorCli::from_str("gcc"), Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Gcc))); + assert_eq!( + LinkerFlavorCli::from_str("msvc"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Msvc)) + ); + assert_eq!( + LinkerFlavorCli::from_str("bpf-linker"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::BpfLinker)) + ); + assert_eq!( + LinkerFlavorCli::from_str("lld-link"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Lld(LldFlavor::Link))) + ); + assert_eq!( + LinkerFlavorCli::from_str("ld64.lld"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Lld(LldFlavor::Ld64))) + ); + + // While other invalid values for well-known flavors are already errors + assert_eq!(LinkerFlavorCli::from_str("unknown_linker"), Err(())); +} + +/// Enrichments can currently allow for the `gcc` flavor to specify for a given linker to be +/// used, much like you'd use `-fuse-ld` as a link arg. When using `-C +/// linker-flavor=gcc:$linker`, the `$linker` will be passed directly to `cc`. +#[test] +pub fn parse_gcc_enrichment_linker_flavor() { + assert_eq!( + LinkerFlavorCli::from_str("gcc:lld"), + Ok(LinkerFlavorCli::Gcc { use_ld: "lld".to_string() }) + ); + assert_eq!( + LinkerFlavorCli::from_str("gcc:gold"), + Ok(LinkerFlavorCli::Gcc { use_ld: "gold".to_string() }) + ); + + // No linker actually mentioned + assert_eq!(LinkerFlavorCli::from_str("gcc:"), Err(())); + + // Only one `gcc:` separator allowed + assert_eq!(LinkerFlavorCli::from_str("gcc:gcc:"), Err(())); + assert_eq!(LinkerFlavorCli::from_str("gcc:gcc:linker"), Err(())); +} diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index bd5b712c143c5..3c2f351159e2d 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -158,6 +158,10 @@ macro_rules! flavor_mappings { ) } +// Note: until string concatenation can be done in const contexts, remember to update the +// `parse_linker_flavor` error message in rustc_session/src/options.rs whenever adding a new allowed +// value here, since it also mentions enriched CLI-allowed values like `gcc:lld` that are then +// lowered to these target specs `LinkerFlavor`s. flavor_mappings! { ((LinkerFlavor::Em), "em"), ((LinkerFlavor::Gcc), "gcc"), diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index a212c659b5c2b..0f5c171aac96d 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -694,6 +694,9 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS cargo.rustflag("--cfg=parallel_compiler"); cargo.rustdocflag("--cfg=parallel_compiler"); } + if builder.config.lld_enabled { + cargo.rustflag("--cfg=rust_lld_enabled"); + } if builder.config.rust_verify_llvm_ir { cargo.env("RUSTC_VERIFY_LLVM_IR", "1"); } diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 59102ad9f50b8..7ff99614b295b 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -194,6 +194,7 @@ const EXTRA_CHECK_CFGS: &[(Option, &'static str, Option<&[&'static str]>)] (None, "bootstrap", None), (Some(Mode::Rustc), "parallel_compiler", None), (Some(Mode::ToolRustc), "parallel_compiler", None), + (Some(Mode::Rustc), "rust_lld_enabled", None), (Some(Mode::Std), "stdarch_intel_sde", None), (Some(Mode::Std), "no_fp_fmt_parse", None), (Some(Mode::Std), "no_global_oom_handling", None), diff --git a/src/test/run-make/gcc-linker-flavor/Makefile b/src/test/run-make/gcc-linker-flavor/Makefile new file mode 100644 index 0000000000000..09b8b28e8263e --- /dev/null +++ b/src/test/run-make/gcc-linker-flavor/Makefile @@ -0,0 +1,9 @@ +# only-linux + +# Ensure that the enriched `gcc` linker-flavor passes the requested linker to `cc` + +-include ../../run-make-fulldeps/tools.mk +RUSTC_FLAGS = -C linker-flavor=gcc:my_wrapped_linker -Z unstable-options --print link-args + +all: + $(RUSTC) $(RUSTC_FLAGS) empty.rs | $(CGREP) "fuse-ld=my_wrapped_linker" diff --git a/src/test/run-make/gcc-linker-flavor/empty.rs b/src/test/run-make/gcc-linker-flavor/empty.rs new file mode 100644 index 0000000000000..d227f726a44b2 --- /dev/null +++ b/src/test/run-make/gcc-linker-flavor/empty.rs @@ -0,0 +1,4 @@ +// Test ensuring that the enriched `gcc` linker flavor requesting an arbitrary linker (`-C +// linker-flavor=gcc:$linker`) is passed to `cc` as `-fuse-ld` + +fn main() {} diff --git a/src/test/run-make/rust-lld/Makefile b/src/test/run-make/rust-lld/Makefile new file mode 100644 index 0000000000000..7e5c76e080985 --- /dev/null +++ b/src/test/run-make/rust-lld/Makefile @@ -0,0 +1,15 @@ +# only-linux +# only-x86_64 +# needs-rust-lld + +# Ensure that the stable version of `-Zgcc-ld=lld` works: +# - `-C link-self-contained=linker` +# - `-C linker-flavor=gcc:lld` + +-include ../../run-make-fulldeps/tools.mk +RUSTC_FLAGS = -C linker-flavor=gcc:lld -C link-self-contained=linker -Z unstable-options -C link-args=-Wl,-v + +all: + RUSTC_LOG=rustc_codegen_ssa::back::link=info $(RUSTC) $(RUSTC_FLAGS) empty.rs 2> $(TMPDIR)/output.txt + $(CGREP) "lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld/ld" < $(TMPDIR)/output.txt + $(CGREP) -e "^LLD [0-9]+\.[0-9]+\.[0-9]+" < $(TMPDIR)/output.txt diff --git a/src/test/run-make/rust-lld/empty.rs b/src/test/run-make/rust-lld/empty.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/src/test/run-make/rust-lld/empty.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/test/ui/linkers/gcc_ld_mismatch.rs b/src/test/ui/linkers/gcc_ld_mismatch.rs new file mode 100644 index 0000000000000..397bf16b9ff51 --- /dev/null +++ b/src/test/ui/linkers/gcc_ld_mismatch.rs @@ -0,0 +1,7 @@ +// check-fail +// compile-flags: -Zgcc-ld=lld -Clinker-flavor=gcc:not_lld -Zunstable-options + +// Test ensuring that until the unstable flag is removed (if ever), if both the linker-flavor and +// `gcc-ld` flags are used, they ask for the same linker. + +fn main() {} diff --git a/src/test/ui/linkers/gcc_ld_mismatch.stderr b/src/test/ui/linkers/gcc_ld_mismatch.stderr new file mode 100644 index 0000000000000..9e5c6eb0f6a73 --- /dev/null +++ b/src/test/ui/linkers/gcc_ld_mismatch.stderr @@ -0,0 +1,2 @@ +error: `-Zgcc-ld=lld` and `-Clinker-flavor` differ in their linker choice. The latter should be `-Clinker-flavor=gcc:lld` + diff --git a/src/test/ui/linkers/mentions_issue_79555.rs b/src/test/ui/linkers/mentions_issue_79555.rs new file mode 100644 index 0000000000000..aaefaca0c4cba --- /dev/null +++ b/src/test/ui/linkers/mentions_issue_79555.rs @@ -0,0 +1,11 @@ +// build-pass +// needs-profiler-support +// needs-rust-lld +// compile-flags: -C linker-flavor=gcc:lld -Z unstable-options -Clink-self-contained=y -Cinstrument-coverage + +// Test ensuring that a warning referencing lld known issue 79555 is emitted when: +// - we're asking to use lld, via the enriched gcc linker-flavor +// - the CRT object linking is on +// - either coverage or generating a profile is requested + +fn main() {} diff --git a/src/test/ui/linkers/mentions_issue_79555.stderr b/src/test/ui/linkers/mentions_issue_79555.stderr new file mode 100644 index 0000000000000..8a42d67708b97 --- /dev/null +++ b/src/test/ui/linkers/mentions_issue_79555.stderr @@ -0,0 +1,4 @@ +warning: 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 + +warning: 1 warning emitted + diff --git a/src/test/ui/linkers/unstable_link_self_contained.all.stderr b/src/test/ui/linkers/unstable_link_self_contained.all.stderr new file mode 100644 index 0000000000000..33c37c7119e6e --- /dev/null +++ b/src/test/ui/linkers/unstable_link_self_contained.all.stderr @@ -0,0 +1,2 @@ +error: only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, the `-Z unstable-options` flag must also be passed to use the unstable values + diff --git a/src/test/ui/linkers/unstable_link_self_contained.auto.stderr b/src/test/ui/linkers/unstable_link_self_contained.auto.stderr new file mode 100644 index 0000000000000..33c37c7119e6e --- /dev/null +++ b/src/test/ui/linkers/unstable_link_self_contained.auto.stderr @@ -0,0 +1,2 @@ +error: only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, the `-Z unstable-options` flag must also be passed to use the unstable values + diff --git a/src/test/ui/linkers/unstable_link_self_contained.crt.stderr b/src/test/ui/linkers/unstable_link_self_contained.crt.stderr new file mode 100644 index 0000000000000..33c37c7119e6e --- /dev/null +++ b/src/test/ui/linkers/unstable_link_self_contained.crt.stderr @@ -0,0 +1,2 @@ +error: only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, the `-Z unstable-options` flag must also be passed to use the unstable values + diff --git a/src/test/ui/linkers/unstable_link_self_contained.linker.stderr b/src/test/ui/linkers/unstable_link_self_contained.linker.stderr new file mode 100644 index 0000000000000..33c37c7119e6e --- /dev/null +++ b/src/test/ui/linkers/unstable_link_self_contained.linker.stderr @@ -0,0 +1,2 @@ +error: only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, the `-Z unstable-options` flag must also be passed to use the unstable values + diff --git a/src/test/ui/linkers/unstable_link_self_contained.rs b/src/test/ui/linkers/unstable_link_self_contained.rs new file mode 100644 index 0000000000000..c4e134f3ac44a --- /dev/null +++ b/src/test/ui/linkers/unstable_link_self_contained.rs @@ -0,0 +1,11 @@ +// check-fail +// revisions: crt auto linker all +// [crt] compile-flags: -C link-self-contained=crt +// [auto] compile-flags: -C link-self-contained=auto +// [linker] compile-flags: -C link-self-contained=linker +// [all] compile-flags: -C link-self-contained=all + +// Test ensuring that the unstable values of the stable `-C link-self-contained` flag +// require using `-Z unstable options` + +fn main() {} diff --git a/src/test/ui/linkers/unstable_linker_flavor.lld.stderr b/src/test/ui/linkers/unstable_linker_flavor.lld.stderr new file mode 100644 index 0000000000000..056d7e18238ff --- /dev/null +++ b/src/test/ui/linkers/unstable_linker_flavor.lld.stderr @@ -0,0 +1,2 @@ +error: the `gcc:*` linker flavor is unstable, the `-Z unstable-options` flag must also be passed to use it + diff --git a/src/test/ui/linkers/unstable_linker_flavor.other.stderr b/src/test/ui/linkers/unstable_linker_flavor.other.stderr new file mode 100644 index 0000000000000..056d7e18238ff --- /dev/null +++ b/src/test/ui/linkers/unstable_linker_flavor.other.stderr @@ -0,0 +1,2 @@ +error: the `gcc:*` linker flavor is unstable, the `-Z unstable-options` flag must also be passed to use it + diff --git a/src/test/ui/linkers/unstable_linker_flavor.rs b/src/test/ui/linkers/unstable_linker_flavor.rs new file mode 100644 index 0000000000000..096d4785b69f9 --- /dev/null +++ b/src/test/ui/linkers/unstable_linker_flavor.rs @@ -0,0 +1,9 @@ +// check-fail +// revisions: lld other +// [lld] compile-flags: -C linker-flavor=gcc:lld +// [other] compile-flags: -C linker-flavor=gcc:other + +// Test ensuring that the unstable `gcc:*` values of the stable `-C linker-flavor` flag require +// using `-Z unstable options` + +fn main() {}