about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-07-02 02:25:01 +0000
committerbors <bors@rust-lang.org>2023-07-02 02:25:01 +0000
commit8e2d5e3a584b15ffee14984cb0b0986f00754b3d (patch)
tree66ce348487ffec3e36e9379451d509ecdb8b09e8
parentba76096bf10b66d222868ca26437ba38c24c7783 (diff)
parent38f5a9964ba7e1eeeffc7783d4cf2e4032ca0f31 (diff)
downloadrust-8e2d5e3a584b15ffee14984cb0b0986f00754b3d.tar.gz
rust-8e2d5e3a584b15ffee14984cb0b0986f00754b3d.zip
Auto merge of #112910 - lqd:mcp510, r=petrochenkov
Implement most of MCP510

This implements most of what remains to be done for MCP510:
- turns `-C link-self-contained` into a `+`/`-` list of components, like `-C link-self-contained=+linker,+crto,+libc,+unwind,+sanitizers,+mingw`. The scaffolding is present for all these expected components to be implemented and stabilized in the future on their own time. This PR only handles the `-Zgcc-ld=lld` subset of these link-self-contained components as  `-Clink-self-contained=+linker`
- handles  `-C link-self-contained=y|n`  as-is today, for compatibility with `rustc_codegen_ssa::back::link::self_contained`'s [explicit opt-in and opt-out](https://github.com/lqd/rust/blob/9eee230cd0a56bfba3ce65121798d9f9f4341cdd/compiler/rustc_codegen_ssa/src/back/link.rs#L1671-L1676).
- therefore supports our plan to opt out of `rust-lld` (when it's enabled by default) even for current `-Clink-self-contained` users, with e.g. `-Clink-self-contained -Clink-self-contained=-linker`
- turns `add_gcc_ld_path` into its expected final form, by using the `-C link-self-contained=+linker`  CLI flag, and whether the `LinkerFlavor`  has the expected `Cc::Yes` and `Lld::Yes` shape (this is not yet the case in practice for any CLI linker flavor)
- makes the [new clean linker flavors](https://github.com/rust-lang/rust/pull/96827#issuecomment-1208441595) selectable in the CLI in addition to the legacy ones, in order to opt-in to using `cc` and `lld` to emulate `-Zgcc-ld=lld`
- ensure the new `-C link-self-contained` components, and `-C linker-flavor`s are unstable, and require `-Z unstable-options` to be used

The up-to-date set of flags for the future stable CLI version of `-Zgcc-ld=lld` is currently: `-Clink-self-contained=+linker -Clinker-flavor=gnu-lld-cc -Zunstable-options`.

It's possible we'll also need to do something for distros that don't ship `rust-lld`, but maybe there are already no tool search paths to be added to `cc` in this situation anyways.

r? `@petrochenkov`
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs116
-rw-r--r--compiler/rustc_interface/src/tests.rs3
-rw-r--r--compiler/rustc_session/Cargo.toml1
-rw-r--r--compiler/rustc_session/src/config.rs164
-rw-r--r--compiler/rustc_session/src/options.rs34
-rw-r--r--compiler/rustc_target/src/spec/mod.rs141
-rw-r--r--tests/run-make/rust-lld/Makefile8
-rw-r--r--tests/run-make/rust-lld/main.rs4
-rw-r--r--tests/ui/linkage-attr/incompatible-flavor.stderr2
-rw-r--r--tests/ui/linkage-attr/unstable-flavor.bpf.stderr2
-rw-r--r--tests/ui/linkage-attr/unstable-flavor.ptx.stderr2
-rw-r--r--tests/ui/linkage-attr/unstable-flavor.rs8
13 files changed, 398 insertions, 88 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f350108cfa5..2483390a243 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3995,6 +3995,7 @@ name = "rustc_session"
 version = "0.0.0"
 dependencies = [
  "atty",
+ "bitflags",
  "getopts",
  "libc",
  "rustc_ast",
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 557b120b2c8..c7925be0d7d 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -12,7 +12,7 @@ use rustc_metadata::fs::{copy_to_stdout, emit_wrapper_file, METADATA_FILENAME};
 use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
 use rustc_middle::middle::dependency_format::Linkage;
 use rustc_middle::middle::exported_symbols::SymbolExportKind;
-use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip};
+use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, Strip};
 use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, SplitDwarfKind};
 use rustc_session::cstore::DllImport;
 use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
@@ -1688,7 +1688,7 @@ fn detect_self_contained_mingw(sess: &Session) -> bool {
 /// instead of being found somewhere on the host system.
 /// We only provide such support for a very limited number of targets.
 fn self_contained(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.explicitly_set {
         if sess.target.link_self_contained == LinkSelfContainedDefault::False {
             sess.emit_err(errors::UnsupportedLinkSelfContained);
         }
@@ -2246,7 +2246,8 @@ fn add_order_independent_options(
     out_filename: &Path,
     tmpdir: &Path,
 ) {
-    add_gcc_ld_path(cmd, sess, flavor);
+    // Take care of the flavors and CLI options requesting the `lld` linker.
+    add_lld_args(cmd, sess, flavor);
 
     add_apple_sdk(cmd, sess, flavor);
 
@@ -2948,55 +2949,66 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
     }
 }
 
-fn add_gcc_ld_path(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
-    if let Some(ld_impl) = sess.opts.unstable_opts.gcc_ld {
-        if let LinkerFlavor::Gnu(Cc::Yes, _)
-        | LinkerFlavor::Darwin(Cc::Yes, _)
-        | LinkerFlavor::WasmLld(Cc::Yes) = flavor
-        {
-            match ld_impl {
-                LdImpl::Lld => {
-                    // Implement the "self-contained" part of -Zgcc-ld
-                    // by adding rustc distribution directories to the tool search path.
-                    for path in sess.get_tools_search_paths(false) {
-                        cmd.arg({
-                            let mut arg = OsString::from("-B");
-                            arg.push(path.join("gcc-ld"));
-                            arg
-                        });
-                    }
-                    // Implement the "linker flavor" part of -Zgcc-ld
-                    // by asking cc to use some kind of lld.
-                    cmd.arg("-fuse-ld=lld");
-
-                    if !flavor.is_gnu() {
-                        // Tell clang to use a non-default LLD flavor.
-                        // Gcc doesn't understand the target option, but we currently assume
-                        // that gcc is not used for Apple and Wasm targets (#97402).
-                        //
-                        // Note that we don't want to do that by default on macOS: e.g. passing a
-                        // 10.7 target to LLVM works, but not to recent versions of clang/macOS, as
-                        // shown in issue #101653 and the discussion in PR #101792.
-                        //
-                        // It could be required in some cases of cross-compiling with
-                        // `-Zgcc-ld=lld`, but this is generally unspecified, and we don't know
-                        // which specific versions of clang, macOS SDK, host and target OS
-                        // combinations impact us here.
-                        //
-                        // So we do a simple first-approximation until we know more of what the
-                        // Apple targets require (and which would be handled prior to hitting this
-                        // `-Zgcc-ld=lld` codepath anyway), but the expectation is that until then
-                        // this should be manually passed if needed. We specify the target when
-                        // targeting a different linker flavor on macOS, and that's also always
-                        // the case when targeting WASM.
-                        if sess.target.linker_flavor != sess.host.linker_flavor {
-                            cmd.arg(format!("--target={}", sess.target.llvm_target));
-                        }
-                    }
-                }
-            }
-        } else {
-            sess.emit_fatal(errors::OptionGccOnly);
+/// When using the linker flavors opting in to `lld`, or the unstable `-Zgcc-ld=lld` flag, add the
+/// necessary paths and arguments to invoke it:
+/// - when the self-contained linker flag is active: the build of `lld` distributed with rustc,
+/// - or any `lld` available to `cc`.
+fn add_lld_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
+    let unstable_use_lld = sess.opts.unstable_opts.gcc_ld.is_some();
+    debug!("add_lld_args requested, flavor: '{flavor:?}', `-Zgcc-ld=lld`: {unstable_use_lld}");
+
+    // Sanity check: using the old unstable `-Zgcc-ld=lld` option requires a `cc`-using flavor.
+    let flavor_uses_cc = flavor.uses_cc();
+    if unstable_use_lld && !flavor_uses_cc {
+        sess.emit_fatal(errors::OptionGccOnly);
+    }
+
+    // If the flavor doesn't use a C/C++ compiler to invoke the linker, or doesn't opt in to `lld`,
+    // we don't need to do anything.
+    let use_lld = flavor.uses_lld() || unstable_use_lld;
+    if !flavor_uses_cc || !use_lld {
+        return;
+    }
+
+    // 1. Implement the "self-contained" part of this feature by adding rustc distribution
+    //    directories to the tool's search path.
+    let self_contained_linker = sess.opts.cg.link_self_contained.linker() || unstable_use_lld;
+    if self_contained_linker {
+        for path in sess.get_tools_search_paths(false) {
+            cmd.arg({
+                let mut arg = OsString::from("-B");
+                arg.push(path.join("gcc-ld"));
+                arg
+            });
+        }
+    }
+
+    // 2. Implement the "linker flavor" part of this feature by asking `cc` to use some kind of
+    //    `lld` as the linker.
+    cmd.arg("-fuse-ld=lld");
+
+    if !flavor.is_gnu() {
+        // Tell clang to use a non-default LLD flavor.
+        // Gcc doesn't understand the target option, but we currently assume
+        // that gcc is not used for Apple and Wasm targets (#97402).
+        //
+        // Note that we don't want to do that by default on macOS: e.g. passing a
+        // 10.7 target to LLVM works, but not to recent versions of clang/macOS, as
+        // shown in issue #101653 and the discussion in PR #101792.
+        //
+        // It could be required in some cases of cross-compiling with
+        // `-Zgcc-ld=lld`, but this is generally unspecified, and we don't know
+        // which specific versions of clang, macOS SDK, host and target OS
+        // combinations impact us here.
+        //
+        // So we do a simple first-approximation until we know more of what the
+        // Apple targets require (and which would be handled prior to hitting this
+        // `-Zgcc-ld=lld` codepath anyway), but the expectation is that until then
+        // this should be manually passed if needed. We specify the target when
+        // targeting a different linker flavor on macOS, and that's also always
+        // the case when targeting WASM.
+        if sess.target.linker_flavor != sess.host.linker_flavor {
+            cmd.arg(format!("--target={}", sess.target.llvm_target));
         }
     }
 }
diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs
index 6054b48b0b5..0f5dba1df14 100644
--- a/compiler/rustc_interface/src/tests.rs
+++ b/compiler/rustc_interface/src/tests.rs
@@ -8,6 +8,7 @@ use rustc_session::config::rustc_optgroups;
 use rustc_session::config::DebugInfo;
 use rustc_session::config::Input;
 use rustc_session::config::InstrumentXRay;
+use rustc_session::config::LinkSelfContained;
 use rustc_session::config::TraitSolver;
 use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
 use rustc_session::config::{
@@ -579,7 +580,7 @@ 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(LinkerFlavorCli::Gcc));
     untracked!(no_stack_check, true);
diff --git a/compiler/rustc_session/Cargo.toml b/compiler/rustc_session/Cargo.toml
index 90ad3f90f2c..1291d1454a6 100644
--- a/compiler/rustc_session/Cargo.toml
+++ b/compiler/rustc_session/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 atty = "0.2.13"
+bitflags = "1.2.1"
 getopts = "0.2"
 rustc_macros = { path = "../rustc_macros" }
 tracing = "0.1"
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 480d2478e81..7f66b7895d4 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -9,10 +9,9 @@ use crate::{lint, HashStableContext};
 use crate::{EarlyErrorHandler, Session};
 
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-
 use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey};
 use rustc_target::abi::Align;
-use rustc_target::spec::{LinkerFlavorCli, PanicStrategy, SanitizerSet, SplitDebuginfo};
+use rustc_target::spec::{PanicStrategy, SanitizerSet, SplitDebuginfo};
 use rustc_target::spec::{Target, TargetTriple, TargetWarnings, TARGETS};
 
 use crate::parse::{CrateCheckConfig, CrateConfig};
@@ -201,6 +200,128 @@ pub enum LinkerPluginLto {
     Disabled,
 }
 
+impl LinkerPluginLto {
+    pub fn enabled(&self) -> bool {
+        match *self {
+            LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true,
+            LinkerPluginLto::Disabled => false,
+        }
+    }
+}
+
+/// The different values `-C link-self-contained` can take: a list of individually enabled or
+/// disabled components used during linking, coming from the rustc distribution, instead of being
+/// found somewhere on the host system.
+///
+/// They can be set in bulk via `-C link-self-contained=yes|y|on` or `-C
+/// link-self-contained=no|n|off`, and those boolean values are the historical defaults.
+///
+/// But each component is fine-grained, and can be unstably targeted, to use:
+/// - some CRT objects
+/// - the libc static library
+/// - libgcc/libunwind libraries
+/// - a linker we distribute
+/// - some sanitizer runtime libraries
+/// - all other MinGW libraries and Windows import libs
+///
+#[derive(Default, Clone, PartialEq, Debug)]
+pub struct LinkSelfContained {
+    /// Whether the user explicitly set `-C link-self-contained` on or off, the historical values.
+    /// Used for compatibility with the existing opt-in and target inference.
+    pub explicitly_set: Option<bool>,
+
+    /// The components that are enabled.
+    components: LinkSelfContainedComponents,
+}
+
+bitflags::bitflags! {
+    #[derive(Default)]
+    /// The `-C link-self-contained` components that can individually be enabled or disabled.
+    pub struct LinkSelfContainedComponents: u8 {
+        /// CRT objects (e.g. on `windows-gnu`, `musl`, `wasi` targets)
+        const CRT_OBJECTS = 1 << 0;
+        /// libc static library (e.g. on `musl`, `wasi` targets)
+        const LIBC        = 1 << 1;
+        /// libgcc/libunwind (e.g. on `windows-gnu`, `fuchsia`, `fortanix`, `gnullvm` targets)
+        const UNWIND      = 1 << 2;
+        /// Linker, dlltool, and their necessary libraries (e.g. on `windows-gnu` and for `rust-lld`)
+        const LINKER      = 1 << 3;
+        /// Sanitizer runtime libraries
+        const SANITIZERS  = 1 << 4;
+        /// Other MinGW libs and Windows import libs
+        const MINGW       = 1 << 5;
+    }
+}
+
+impl FromStr for LinkSelfContainedComponents {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s {
+            "crto" => LinkSelfContainedComponents::CRT_OBJECTS,
+            "libc" => LinkSelfContainedComponents::LIBC,
+            "unwind" => LinkSelfContainedComponents::UNWIND,
+            "linker" => LinkSelfContainedComponents::LINKER,
+            "sanitizers" => LinkSelfContainedComponents::SANITIZERS,
+            "mingw" => LinkSelfContainedComponents::MINGW,
+            _ => return Err(()),
+        })
+    }
+}
+
+impl LinkSelfContained {
+    /// Incorporates an enabled or disabled component as specified on the CLI, if possible.
+    /// For example: `+linker`, and `-crto`.
+    pub(crate) fn handle_cli_component(&mut self, component: &str) -> Result<(), ()> {
+        // Note that for example `-Cself-contained=y -Cself-contained=-linker` is not an explicit
+        // set of all values like `y` or `n` used to be. Therefore, if this flag had previously been
+        // set in bulk with its historical values, then manually setting a component clears that
+        // `explicitly_set` state.
+        if let Some(component_to_enable) = component.strip_prefix("+") {
+            self.explicitly_set = None;
+            self.components.insert(component_to_enable.parse()?);
+            Ok(())
+        } else if let Some(component_to_disable) = component.strip_prefix("-") {
+            self.explicitly_set = None;
+            self.components.remove(component_to_disable.parse()?);
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+
+    /// Turns all components on or off and records that this was done explicitly for compatibility
+    /// purposes.
+    pub(crate) fn set_all_explicitly(&mut self, enabled: bool) {
+        self.explicitly_set = Some(enabled);
+        self.components = if enabled {
+            LinkSelfContainedComponents::all()
+        } else {
+            LinkSelfContainedComponents::empty()
+        };
+    }
+
+    /// Helper creating a fully enabled `LinkSelfContained` instance. Used in tests.
+    pub fn on() -> Self {
+        let mut on = LinkSelfContained::default();
+        on.set_all_explicitly(true);
+        on
+    }
+
+    /// To help checking CLI usage while some of the values are unstable: returns whether one of the
+    /// components was set individually. This would also require the `-Zunstable-options` flag, to
+    /// be allowed.
+    fn are_unstable_variants_set(&self) -> bool {
+        let any_component_set = !self.components.is_empty();
+        self.explicitly_set.is_none() && any_component_set
+    }
+
+    /// Returns whether the self-contained linker component is enabled.
+    pub fn linker(&self) -> bool {
+        self.components.contains(LinkSelfContainedComponents::LINKER)
+    }
+}
+
 /// Used with `-Z assert-incr-state`.
 #[derive(Clone, Copy, PartialEq, Hash, Debug)]
 pub enum IncrementalStateAssertion {
@@ -213,15 +334,6 @@ pub enum IncrementalStateAssertion {
     NotLoaded,
 }
 
-impl LinkerPluginLto {
-    pub fn enabled(&self) -> bool {
-        match *self {
-            LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true,
-            LinkerPluginLto::Disabled => false,
-        }
-    }
-}
-
 /// The different settings that can be enabled via the `-Z location-detail` flag.
 #[derive(Copy, Clone, PartialEq, Hash, Debug)]
 pub struct LocationDetail {
@@ -2544,16 +2656,28 @@ pub fn build_session_options(
         }
     }
 
-    if let Some(flavor) = cg.linker_flavor {
-        if matches!(flavor, LinkerFlavorCli::BpfLinker | LinkerFlavorCli::PtxLinker)
-            && !nightly_options::is_unstable_enabled(matches)
-        {
-            let msg = format!(
-                "linker flavor `{}` is unstable, `-Z unstable-options` \
-                 flag must also be passed to explicitly use it",
-                flavor.desc()
+    // For testing purposes, until we have more feedback about these options: ensure `-Z
+    // unstable-options` is required when using the unstable `-C link-self-contained` options, like
+    // `-C link-self-contained=+linker`, and when using the unstable `-C linker-flavor` options, like
+    // `-C linker-flavor=gnu-lld-cc`.
+    if !nightly_options::is_unstable_enabled(matches) {
+        let uses_unstable_self_contained_option =
+            cg.link_self_contained.are_unstable_variants_set();
+        if uses_unstable_self_contained_option {
+            handler.early_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",
             );
-            handler.early_error(msg);
+        }
+
+        if let Some(flavor) = cg.linker_flavor {
+            if flavor.is_unstable() {
+                handler.early_error(format!(
+                    "the linker flavor `{}` is unstable, the `-Z unstable-options` \
+                        flag must also be passed to use the unstable values",
+                    flavor.desc()
+                ));
+            }
         }
     }
 
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index e5063eef47a..65cb94a9ab8 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -410,6 +410,8 @@ 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`";
+    pub const parse_link_self_contained: &str = "one of: `y`, `yes`, `on`, `n`, `no`, `off`, or a list of enabled (`+` prefix) and disabled (`-` prefix) \
+        components: `crto`, `libc`, `unwind`, `linker`, `sanitizers`, `mingw`";
     pub const parse_stack_protector: &str =
         "one of (`none` (default), `basic`, `strong`, or `all`)";
     pub const parse_branch_protection: &str =
@@ -1122,6 +1124,34 @@ mod parse {
         }
     }
 
+    pub(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`, the historical value of this flag.
+        //
+        // 1. Parse historical single bool values
+        let s = v.unwrap_or("y");
+        match s {
+            "y" | "yes" | "on" => {
+                slot.set_all_explicitly(true);
+                return true;
+            }
+            "n" | "no" | "off" => {
+                slot.set_all_explicitly(false);
+                return true;
+            }
+            _ => {}
+        }
+
+        // 2. Parse a list of enabled and disabled components.
+        for comp in s.split(",") {
+            if slot.handle_cli_component(comp).is_err() {
+                return false;
+            }
+        }
+
+        true
+    }
+
     pub(crate) fn parse_wasi_exec_model(slot: &mut Option<WasiExecModel>, v: Option<&str>) -> bool {
         match v {
             Some("command") => *slot = Some(WasiExecModel::Command),
@@ -1265,9 +1295,9 @@ options! {
     #[rustc_lint_opt_deny_field_access("use `Session::link_dead_code` instead of this field")]
     link_dead_code: Option<bool> = (None, parse_opt_bool, [TRACKED],
         "keep dead code at link time (useful for code coverage) (default: no)"),
-    link_self_contained: Option<bool> = (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"),
+        on a C toolchain or linker installed in the system"),
     linker: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
         "system linker to link outputs with"),
     linker_flavor: Option<LinkerFlavorCli> = (None, parse_linker_flavor, [UNTRACKED],
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 0a42c4c2544..2e5bb3db886 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -161,15 +161,49 @@ pub enum LinkerFlavor {
 /// linker flavors (`LinkerFlavor`).
 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
 pub enum LinkerFlavorCli {
+    // New (unstable) flavors, with direct counterparts in `LinkerFlavor`.
+    Gnu(Cc, Lld),
+    Darwin(Cc, Lld),
+    WasmLld(Cc),
+    Unix(Cc),
+    // Note: `Msvc(Lld::No)` is also a stable value.
+    Msvc(Lld),
+    EmCc,
+    Bpf,
+    Ptx,
+
+    // Below: the legacy stable values.
     Gcc,
     Ld,
     Lld(LldFlavor),
-    Msvc,
     Em,
     BpfLinker,
     PtxLinker,
 }
 
+impl LinkerFlavorCli {
+    /// Returns whether this `-C linker-flavor` option is one of the unstable values.
+    pub fn is_unstable(&self) -> bool {
+        match self {
+            LinkerFlavorCli::Gnu(..)
+            | LinkerFlavorCli::Darwin(..)
+            | LinkerFlavorCli::WasmLld(..)
+            | LinkerFlavorCli::Unix(..)
+            | LinkerFlavorCli::Msvc(Lld::Yes)
+            | LinkerFlavorCli::EmCc
+            | LinkerFlavorCli::Bpf
+            | LinkerFlavorCli::Ptx
+            | LinkerFlavorCli::BpfLinker
+            | LinkerFlavorCli::PtxLinker => true,
+            LinkerFlavorCli::Gcc
+            | LinkerFlavorCli::Ld
+            | LinkerFlavorCli::Lld(..)
+            | LinkerFlavorCli::Msvc(Lld::No)
+            | LinkerFlavorCli::Em => false,
+        }
+    }
+}
+
 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
 pub enum LldFlavor {
     Wasm,
@@ -212,6 +246,16 @@ impl LinkerFlavor {
     /// of truth, other flags are used in case of ambiguities.
     fn from_cli_json(cli: LinkerFlavorCli, lld_flavor: LldFlavor, is_gnu: bool) -> LinkerFlavor {
         match cli {
+            LinkerFlavorCli::Gnu(cc, lld) => LinkerFlavor::Gnu(cc, lld),
+            LinkerFlavorCli::Darwin(cc, lld) => LinkerFlavor::Darwin(cc, lld),
+            LinkerFlavorCli::WasmLld(cc) => LinkerFlavor::WasmLld(cc),
+            LinkerFlavorCli::Unix(cc) => LinkerFlavor::Unix(cc),
+            LinkerFlavorCli::Msvc(lld) => LinkerFlavor::Msvc(lld),
+            LinkerFlavorCli::EmCc => LinkerFlavor::EmCc,
+            LinkerFlavorCli::Bpf => LinkerFlavor::Bpf,
+            LinkerFlavorCli::Ptx => LinkerFlavor::Ptx,
+
+            // Below: legacy stable values
             LinkerFlavorCli::Gcc => match lld_flavor {
                 LldFlavor::Ld if is_gnu => LinkerFlavor::Gnu(Cc::Yes, Lld::No),
                 LldFlavor::Ld64 => LinkerFlavor::Darwin(Cc::Yes, Lld::No),
@@ -227,7 +271,6 @@ impl LinkerFlavor {
             LinkerFlavorCli::Lld(LldFlavor::Ld64) => LinkerFlavor::Darwin(Cc::No, Lld::Yes),
             LinkerFlavorCli::Lld(LldFlavor::Wasm) => LinkerFlavor::WasmLld(Cc::No),
             LinkerFlavorCli::Lld(LldFlavor::Link) => LinkerFlavor::Msvc(Lld::Yes),
-            LinkerFlavorCli::Msvc => LinkerFlavor::Msvc(Lld::No),
             LinkerFlavorCli::Em => LinkerFlavor::EmCc,
             LinkerFlavorCli::BpfLinker => LinkerFlavor::Bpf,
             LinkerFlavorCli::PtxLinker => LinkerFlavor::Ptx,
@@ -247,7 +290,7 @@ impl LinkerFlavor {
                 LinkerFlavorCli::Ld
             }
             LinkerFlavor::Msvc(Lld::Yes) => LinkerFlavorCli::Lld(LldFlavor::Link),
-            LinkerFlavor::Msvc(..) => LinkerFlavorCli::Msvc,
+            LinkerFlavor::Msvc(..) => LinkerFlavorCli::Msvc(Lld::No),
             LinkerFlavor::EmCc => LinkerFlavorCli::Em,
             LinkerFlavor::Bpf => LinkerFlavorCli::BpfLinker,
             LinkerFlavor::Ptx => LinkerFlavorCli::PtxLinker,
@@ -256,9 +299,20 @@ impl LinkerFlavor {
 
     fn infer_cli_hints(cli: LinkerFlavorCli) -> (Option<Cc>, Option<Lld>) {
         match cli {
-            LinkerFlavorCli::Gcc | LinkerFlavorCli::Em => (Some(Cc::Yes), None),
+            LinkerFlavorCli::Gnu(cc, lld) | LinkerFlavorCli::Darwin(cc, lld) => {
+                (Some(cc), Some(lld))
+            }
+            LinkerFlavorCli::WasmLld(cc) => (Some(cc), Some(Lld::Yes)),
+            LinkerFlavorCli::Unix(cc) => (Some(cc), None),
+            LinkerFlavorCli::Msvc(lld) => (Some(Cc::No), Some(lld)),
+            LinkerFlavorCli::EmCc => (Some(Cc::Yes), Some(Lld::Yes)),
+            LinkerFlavorCli::Bpf | LinkerFlavorCli::Ptx => (None, None),
+
+            // Below: legacy stable values
+            LinkerFlavorCli::Gcc => (Some(Cc::Yes), None),
+            LinkerFlavorCli::Ld => (Some(Cc::No), Some(Lld::No)),
             LinkerFlavorCli::Lld(_) => (Some(Cc::No), Some(Lld::Yes)),
-            LinkerFlavorCli::Ld | LinkerFlavorCli::Msvc => (Some(Cc::No), Some(Lld::No)),
+            LinkerFlavorCli::Em => (Some(Cc::Yes), Some(Lld::Yes)),
             LinkerFlavorCli::BpfLinker | LinkerFlavorCli::PtxLinker => (None, None),
         }
     }
@@ -321,8 +375,24 @@ impl LinkerFlavor {
     }
 
     pub fn check_compatibility(self, cli: LinkerFlavorCli) -> Option<String> {
-        // The CLI flavor should be compatible with the target if it survives this roundtrip.
-        let compatible = |cli| cli == self.with_cli_hints(cli).to_cli();
+        let compatible = |cli| {
+            // The CLI flavor should be compatible with the target if:
+            // 1. they are counterparts: they have the same principal flavor.
+            match (self, cli) {
+                (LinkerFlavor::Gnu(..), LinkerFlavorCli::Gnu(..))
+                | (LinkerFlavor::Darwin(..), LinkerFlavorCli::Darwin(..))
+                | (LinkerFlavor::WasmLld(..), LinkerFlavorCli::WasmLld(..))
+                | (LinkerFlavor::Unix(..), LinkerFlavorCli::Unix(..))
+                | (LinkerFlavor::Msvc(..), LinkerFlavorCli::Msvc(..))
+                | (LinkerFlavor::EmCc, LinkerFlavorCli::EmCc)
+                | (LinkerFlavor::Bpf, LinkerFlavorCli::Bpf)
+                | (LinkerFlavor::Ptx, LinkerFlavorCli::Ptx) => return true,
+                _ => {}
+            }
+
+            // 2. or, the flavor is legacy and survives this roundtrip.
+            cli == self.with_cli_hints(cli).to_cli()
+        };
         (!compatible(cli)).then(|| {
             LinkerFlavorCli::all()
                 .iter()
@@ -349,6 +419,43 @@ impl LinkerFlavor {
     pub fn is_gnu(self) -> bool {
         matches!(self, LinkerFlavor::Gnu(..))
     }
+
+    /// Returns whether the flavor uses the `lld` linker.
+    pub fn uses_lld(self) -> bool {
+        // Exhaustive match in case new flavors are added in the future.
+        match self {
+            LinkerFlavor::Gnu(_, Lld::Yes)
+            | LinkerFlavor::Darwin(_, Lld::Yes)
+            | LinkerFlavor::WasmLld(..)
+            | LinkerFlavor::EmCc
+            | LinkerFlavor::Msvc(Lld::Yes) => true,
+            LinkerFlavor::Gnu(..)
+            | LinkerFlavor::Darwin(..)
+            | LinkerFlavor::Msvc(_)
+            | LinkerFlavor::Unix(_)
+            | LinkerFlavor::Bpf
+            | LinkerFlavor::Ptx => false,
+        }
+    }
+
+    /// Returns whether the flavor calls the linker via a C/C++ compiler.
+    pub fn uses_cc(self) -> bool {
+        // Exhaustive match in case new flavors are added in the future.
+        match self {
+            LinkerFlavor::Gnu(Cc::Yes, _)
+            | LinkerFlavor::Darwin(Cc::Yes, _)
+            | LinkerFlavor::WasmLld(Cc::Yes)
+            | LinkerFlavor::Unix(Cc::Yes)
+            | LinkerFlavor::EmCc => true,
+            LinkerFlavor::Gnu(..)
+            | LinkerFlavor::Darwin(..)
+            | LinkerFlavor::WasmLld(_)
+            | LinkerFlavor::Msvc(_)
+            | LinkerFlavor::Unix(_)
+            | LinkerFlavor::Bpf
+            | LinkerFlavor::Ptx => false,
+        }
+    }
 }
 
 macro_rules! linker_flavor_cli_impls {
@@ -379,13 +486,31 @@ macro_rules! linker_flavor_cli_impls {
 }
 
 linker_flavor_cli_impls! {
+    (LinkerFlavorCli::Gnu(Cc::No, Lld::No)) "gnu"
+    (LinkerFlavorCli::Gnu(Cc::No, Lld::Yes)) "gnu-lld"
+    (LinkerFlavorCli::Gnu(Cc::Yes, Lld::No)) "gnu-cc"
+    (LinkerFlavorCli::Gnu(Cc::Yes, Lld::Yes)) "gnu-lld-cc"
+    (LinkerFlavorCli::Darwin(Cc::No, Lld::No)) "darwin"
+    (LinkerFlavorCli::Darwin(Cc::No, Lld::Yes)) "darwin-lld"
+    (LinkerFlavorCli::Darwin(Cc::Yes, Lld::No)) "darwin-cc"
+    (LinkerFlavorCli::Darwin(Cc::Yes, Lld::Yes)) "darwin-lld-cc"
+    (LinkerFlavorCli::WasmLld(Cc::No)) "wasm-lld"
+    (LinkerFlavorCli::WasmLld(Cc::Yes)) "wasm-lld-cc"
+    (LinkerFlavorCli::Unix(Cc::No)) "unix"
+    (LinkerFlavorCli::Unix(Cc::Yes)) "unix-cc"
+    (LinkerFlavorCli::Msvc(Lld::Yes)) "msvc-lld"
+    (LinkerFlavorCli::Msvc(Lld::No)) "msvc"
+    (LinkerFlavorCli::EmCc) "em-cc"
+    (LinkerFlavorCli::Bpf) "bpf"
+    (LinkerFlavorCli::Ptx) "ptx"
+
+    // Below: legacy stable values
     (LinkerFlavorCli::Gcc) "gcc"
     (LinkerFlavorCli::Ld) "ld"
     (LinkerFlavorCli::Lld(LldFlavor::Ld)) "ld.lld"
     (LinkerFlavorCli::Lld(LldFlavor::Ld64)) "ld64.lld"
     (LinkerFlavorCli::Lld(LldFlavor::Link)) "lld-link"
     (LinkerFlavorCli::Lld(LldFlavor::Wasm)) "wasm-ld"
-    (LinkerFlavorCli::Msvc) "msvc"
     (LinkerFlavorCli::Em) "em"
     (LinkerFlavorCli::BpfLinker) "bpf-linker"
     (LinkerFlavorCli::PtxLinker) "ptx-linker"
diff --git a/tests/run-make/rust-lld/Makefile b/tests/run-make/rust-lld/Makefile
new file mode 100644
index 00000000000..f8526530d4d
--- /dev/null
+++ b/tests/run-make/rust-lld/Makefile
@@ -0,0 +1,8 @@
+include ../tools.mk
+
+# ignore-msvc
+# needs-rust-lld
+# ignore-s390x lld does not yet support s390x as target
+all:
+	RUSTC_LOG=rustc_codegen_ssa::back::link=info $(RUSTC) -Clink-self-contained=+linker -Clinker-flavor=gnu-lld-cc -Zunstable-options -Clink-args=-Wl,-v main.rs 2> $(TMPDIR)/output.txt
+	$(CGREP) -e "^LLD [0-9]+\.[0-9]+\.[0-9]+" < $(TMPDIR)/output.txt
diff --git a/tests/run-make/rust-lld/main.rs b/tests/run-make/rust-lld/main.rs
new file mode 100644
index 00000000000..bf159cd941c
--- /dev/null
+++ b/tests/run-make/rust-lld/main.rs
@@ -0,0 +1,4 @@
+// Test linking using `cc` with `rust-lld`, using the unstable CLI described in MCP 510
+// see https://github.com/rust-lang/compiler-team/issues/510 for more info
+
+fn main() {}
diff --git a/tests/ui/linkage-attr/incompatible-flavor.stderr b/tests/ui/linkage-attr/incompatible-flavor.stderr
index e07e778521c..aabdd14b69b 100644
--- a/tests/ui/linkage-attr/incompatible-flavor.stderr
+++ b/tests/ui/linkage-attr/incompatible-flavor.stderr
@@ -1,6 +1,6 @@
 error: linker flavor `msvc` is incompatible with the current target
    |
-   = note: compatible flavors are: gcc, ld, ld.lld
+   = note: compatible flavors are: gnu, gnu-lld, gnu-cc, gnu-lld-cc, gcc, ld, ld.lld
 
 error: aborting due to previous error
 
diff --git a/tests/ui/linkage-attr/unstable-flavor.bpf.stderr b/tests/ui/linkage-attr/unstable-flavor.bpf.stderr
index 3346d12c20e..594a461769b 100644
--- a/tests/ui/linkage-attr/unstable-flavor.bpf.stderr
+++ b/tests/ui/linkage-attr/unstable-flavor.bpf.stderr
@@ -1,2 +1,2 @@
-error: linker flavor `bpf-linker` is unstable, `-Z unstable-options` flag must also be passed to explicitly use it
+error: the linker flavor `bpf-linker` is unstable, the `-Z unstable-options` flag must also be passed to use the unstable values
 
diff --git a/tests/ui/linkage-attr/unstable-flavor.ptx.stderr b/tests/ui/linkage-attr/unstable-flavor.ptx.stderr
index 03ca2a01246..714c09df53f 100644
--- a/tests/ui/linkage-attr/unstable-flavor.ptx.stderr
+++ b/tests/ui/linkage-attr/unstable-flavor.ptx.stderr
@@ -1,2 +1,2 @@
-error: linker flavor `ptx-linker` is unstable, `-Z unstable-options` flag must also be passed to explicitly use it
+error: the linker flavor `ptx-linker` is unstable, the `-Z unstable-options` flag must also be passed to use the unstable values
 
diff --git a/tests/ui/linkage-attr/unstable-flavor.rs b/tests/ui/linkage-attr/unstable-flavor.rs
index 5487882dc24..b58fd055fdc 100644
--- a/tests/ui/linkage-attr/unstable-flavor.rs
+++ b/tests/ui/linkage-attr/unstable-flavor.rs
@@ -1,9 +1,13 @@
+// Even though this test only checks 2 of the 10 or so unstable linker flavors, it exercizes the
+// unique codepath checking all unstable options (see `LinkerFlavorCli::is_unstable` and its
+// caller). If it passes, all the other unstable options are rejected as well.
+//
 // revisions: bpf ptx
 // [bpf] compile-flags: --target=bpfel-unknown-none -C linker-flavor=bpf-linker --crate-type=rlib
-// [bpf] error-pattern: linker flavor `bpf-linker` is unstable, `-Z unstable-options` flag
+// [bpf] error-pattern: linker flavor `bpf-linker` is unstable, the `-Z unstable-options` flag
 // [bpf] needs-llvm-components:
 // [ptx] compile-flags: --target=nvptx64-nvidia-cuda -C linker-flavor=ptx-linker --crate-type=rlib
-// [ptx] error-pattern: linker flavor `ptx-linker` is unstable, `-Z unstable-options` flag
+// [ptx] error-pattern: linker flavor `ptx-linker` is unstable, the `-Z unstable-options` flag
 // [ptx] needs-llvm-components:
 
 #![feature(no_core)]