about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-10-27 02:11:36 +0000
committerbors <bors@rust-lang.org>2023-10-27 02:11:36 +0000
commit31ffe48723dafd06cff75300cf37ba2f568221a1 (patch)
treed8fd3ebb3a1a552f3435fa5fd36efa9702986058
parentaa91057796695679e95329947d9f497cb5bdc5da (diff)
parent0b40c7c6822939e97c008864067d9dac59cc3931 (diff)
downloadrust-31ffe48723dafd06cff75300cf37ba2f568221a1.tar.gz
rust-31ffe48723dafd06cff75300cf37ba2f568221a1.zip
Auto merge of #116035 - lqd:mcp-510-target-specs, r=petrochenkov
Allow target specs to use an LLD flavor, and self-contained linking components

This PR allows:
- target specs to use an LLD linker-flavor: this is needed to switch `x86_64-unknown-linux-gnu` to using LLD, and is currently not possible because the current flavor json serialization fails to roundtrip on the modern linker-flavors. This can e.g. be seen in https://github.com/rust-lang/rust/pull/115622#discussion_r1321312880 which explains where an `Lld::Yes` is ultimately deserialized into an `Lld::No`.
- target specs to declare self-contained linking components: this is needed to switch `x86_64-unknown-linux-gnu` to using `rust-lld`
- adds an end-to-end test of a custom target json simulating `x86_64-unknown-linux-gnu` being switched to using `rust-lld`
- disables codegen backends from participating because they don't support `-Zgcc-ld=lld` which is the basis of mcp510.

r? `@petrochenkov:` if the approach discussed https://github.com/rust-lang/rust/pull/115622#discussion_r1329403467 and on zulip would work for you: basically, see if we can emit only modern linker flavors in the json specs, but accept both old and new flavors while reading them, to fix the roundtrip issue.

The backwards compatible `LinkSelfContainedDefault` variants are still serialized and deserialized in `crt-objects-fallback`, while the spec equivalent of e.g. `-Clink-self-contained=+linker` is serialized into a different json object (with future-proofing to incorporate `crt-objects-fallback`  in the future).

---

I've been test-driving this in https://github.com/rust-lang/rust/pull/113382 to test actually switching `x86_64-unknown-linux-gnu`  to `rust-lld` (and fix what needs to be fixed in CI, bootstrap, etc), and it seems to work fine.
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs131
-rw-r--r--compiler/rustc_target/src/spec/crt_objects.rs38
-rw-r--r--compiler/rustc_target/src/spec/linux_musl_base.rs6
-rw-r--r--compiler/rustc_target/src/spec/mod.rs202
-rw-r--r--compiler/rustc_target/src/spec/tests/tests_impl.rs2
-rw-r--r--compiler/rustc_target/src/spec/wasm32_wasi.rs3
-rw-r--r--compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs4
-rw-r--r--compiler/rustc_target/src/spec/wasm_base.rs2
-rw-r--r--compiler/rustc_target/src/spec/windows_gnu_base.rs5
-rw-r--r--tests/run-make/rust-lld-custom-target/Makefile7
-rw-r--r--tests/run-make/rust-lld-custom-target/custom-target.json57
-rw-r--r--tests/run-make/rust-lld-custom-target/lib.rs9
12 files changed, 372 insertions, 94 deletions
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index c0f6e7cb7b0..d8c89f5947f 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -22,7 +22,9 @@ use rustc_session::utils::NativeLibKind;
 /// need out of the shared crate context before we get rid of it.
 use rustc_session::{filesearch, Session};
 use rustc_span::symbol::Symbol;
-use rustc_target::spec::crt_objects::{CrtObjects, LinkSelfContainedDefault};
+use rustc_target::spec::crt_objects::CrtObjects;
+use rustc_target::spec::LinkSelfContainedComponents;
+use rustc_target::spec::LinkSelfContainedDefault;
 use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, Lld, PanicStrategy};
 use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo};
 
@@ -728,6 +730,7 @@ fn link_natively<'a>(
 ) -> Result<(), ErrorGuaranteed> {
     info!("preparing {:?} to {:?}", crate_type, out_filename);
     let (linker_path, flavor) = linker_and_flavor(sess);
+    let self_contained_components = self_contained_components(sess, crate_type);
     let mut cmd = linker_with_args(
         &linker_path,
         flavor,
@@ -737,6 +740,7 @@ fn link_natively<'a>(
         tmpdir,
         out_filename,
         codegen_results,
+        self_contained_components,
     )?;
 
     linker::disable_localization(&mut cmd);
@@ -812,14 +816,14 @@ fn link_natively<'a>(
                 "Linker does not support -static-pie command line option. Retrying with -static instead."
             );
             // Mirror `add_(pre,post)_link_objects` to replace CRT objects.
-            let self_contained = self_contained(sess, crate_type);
+            let self_contained_crt_objects = self_contained_components.is_crt_objects_enabled();
             let opts = &sess.target;
-            let pre_objects = if self_contained {
+            let pre_objects = if self_contained_crt_objects {
                 &opts.pre_link_objects_self_contained
             } else {
                 &opts.pre_link_objects
             };
-            let post_objects = if self_contained {
+            let post_objects = if self_contained_crt_objects {
                 &opts.post_link_objects_self_contained
             } else {
                 &opts.post_link_objects
@@ -830,7 +834,9 @@ fn link_natively<'a>(
                     .iter()
                     .copied()
                     .flatten()
-                    .map(|obj| get_object_file_path(sess, obj, self_contained).into_os_string())
+                    .map(|obj| {
+                        get_object_file_path(sess, obj, self_contained_crt_objects).into_os_string()
+                    })
                     .collect::<Vec<_>>()
             };
             let pre_objects_static_pie = get_objects(pre_objects, LinkOutputKind::StaticPicExe);
@@ -1710,26 +1716,43 @@ fn detect_self_contained_mingw(sess: &Session) -> bool {
 /// Various toolchain components used during linking are used from rustc distribution
 /// 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.explicitly_set {
-        if sess.target.link_self_contained == LinkSelfContainedDefault::False {
-            sess.emit_err(errors::UnsupportedLinkSelfContained);
-        }
-        return self_contained;
-    }
+fn self_contained_components(sess: &Session, crate_type: CrateType) -> LinkSelfContainedComponents {
+    // Turn the backwards compatible bool values for `self_contained` into fully inferred
+    // `LinkSelfContainedComponents`.
+    let self_contained =
+        if let Some(self_contained) = sess.opts.cg.link_self_contained.explicitly_set {
+            // Emit an error if the user requested self-contained mode on the CLI but the target
+            // explicitly refuses it.
+            if sess.target.link_self_contained.is_disabled() {
+                sess.emit_err(errors::UnsupportedLinkSelfContained);
+            }
+            self_contained
+        } else {
+            match sess.target.link_self_contained {
+                LinkSelfContainedDefault::False => false,
+                LinkSelfContainedDefault::True => true,
+
+                LinkSelfContainedDefault::WithComponents(components) => {
+                    // For target specs with explicitly enabled components, we can return them
+                    // directly.
+                    return components;
+                }
 
-    match sess.target.link_self_contained {
-        LinkSelfContainedDefault::False => false,
-        LinkSelfContainedDefault::True => true,
-        // FIXME: Find a better heuristic for "native musl toolchain is available",
-        // based on host and linker path, for example.
-        // (https://github.com/rust-lang/rust/pull/71769#issuecomment-626330237).
-        LinkSelfContainedDefault::Musl => sess.crt_static(Some(crate_type)),
-        LinkSelfContainedDefault::Mingw => {
-            sess.host == sess.target
-                && sess.target.vendor != "uwp"
-                && detect_self_contained_mingw(&sess)
-        }
+                // FIXME: Find a better heuristic for "native musl toolchain is available",
+                // based on host and linker path, for example.
+                // (https://github.com/rust-lang/rust/pull/71769#issuecomment-626330237).
+                LinkSelfContainedDefault::InferredForMusl => sess.crt_static(Some(crate_type)),
+                LinkSelfContainedDefault::InferredForMingw => {
+                    sess.host == sess.target
+                        && sess.target.vendor != "uwp"
+                        && detect_self_contained_mingw(&sess)
+                }
+            }
+        };
+    if self_contained {
+        LinkSelfContainedComponents::all()
+    } else {
+        LinkSelfContainedComponents::empty()
     }
 }
 
@@ -2030,13 +2053,14 @@ fn linker_with_args<'a>(
     tmpdir: &Path,
     out_filename: &Path,
     codegen_results: &CodegenResults,
+    self_contained_components: LinkSelfContainedComponents,
 ) -> Result<Command, ErrorGuaranteed> {
-    let self_contained = self_contained(sess, crate_type);
+    let self_contained_crt_objects = self_contained_components.is_crt_objects_enabled();
     let cmd = &mut *super::linker::get_linker(
         sess,
         path,
         flavor,
-        self_contained,
+        self_contained_components.are_any_components_enabled(),
         &codegen_results.crate_info.target_cpu,
     );
     let link_output_kind = link_output_kind(sess, crate_type);
@@ -2063,7 +2087,7 @@ fn linker_with_args<'a>(
     // ------------ Object code and libraries, order-dependent ------------
 
     // Pre-link CRT objects.
-    add_pre_link_objects(cmd, sess, flavor, link_output_kind, self_contained);
+    add_pre_link_objects(cmd, sess, flavor, link_output_kind, self_contained_crt_objects);
 
     add_linked_symbol_object(
         cmd,
@@ -2206,7 +2230,7 @@ fn linker_with_args<'a>(
         cmd,
         sess,
         link_output_kind,
-        self_contained,
+        self_contained_components,
         flavor,
         crate_type,
         codegen_results,
@@ -2222,7 +2246,7 @@ fn linker_with_args<'a>(
     // ------------ Object code and libraries, order-dependent ------------
 
     // Post-link CRT objects.
-    add_post_link_objects(cmd, sess, link_output_kind, self_contained);
+    add_post_link_objects(cmd, sess, link_output_kind, self_contained_crt_objects);
 
     // ------------ Late order-dependent options ------------
 
@@ -2239,7 +2263,7 @@ fn add_order_independent_options(
     cmd: &mut dyn Linker,
     sess: &Session,
     link_output_kind: LinkOutputKind,
-    self_contained: bool,
+    self_contained_components: LinkSelfContainedComponents,
     flavor: LinkerFlavor,
     crate_type: CrateType,
     codegen_results: &CodegenResults,
@@ -2247,7 +2271,7 @@ fn add_order_independent_options(
     tmpdir: &Path,
 ) {
     // Take care of the flavors and CLI options requesting the `lld` linker.
-    add_lld_args(cmd, sess, flavor);
+    add_lld_args(cmd, sess, flavor, self_contained_components);
 
     add_apple_sdk(cmd, sess, flavor);
 
@@ -2272,7 +2296,7 @@ fn add_order_independent_options(
     // Make the binary compatible with data execution prevention schemes.
     cmd.add_no_exec();
 
-    if self_contained {
+    if self_contained_components.is_crt_objects_enabled() {
         cmd.no_crt_objects();
     }
 
@@ -2303,7 +2327,7 @@ fn add_order_independent_options(
 
     cmd.linker_plugin_lto();
 
-    add_library_search_dirs(cmd, sess, self_contained);
+    add_library_search_dirs(cmd, sess, self_contained_components.are_any_components_enabled());
 
     cmd.output_filename(out_filename);
 
@@ -2953,8 +2977,16 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, errors::AppleSdkRootErro
 /// 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) {
-    debug!("add_lld_args requested, flavor: '{flavor:?}'");
+fn add_lld_args(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    flavor: LinkerFlavor,
+    self_contained_components: LinkSelfContainedComponents,
+) {
+    debug!(
+        "add_lld_args requested, flavor: '{:?}', target self-contained components: {:?}",
+        flavor, self_contained_components,
+    );
 
     // 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.
@@ -2963,9 +2995,32 @@ fn add_lld_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
     }
 
     // 1. Implement the "self-contained" part of this feature by adding rustc distribution
-    // directories to the tool's search path:
-    // - if the self-contained linker is enabled on the CLI.
-    if sess.opts.cg.link_self_contained.is_linker_enabled() {
+    // directories to the tool's search path, depending on a mix between what users can specify on
+    // the CLI, and what the target spec enables (as it can't disable components):
+    // - if the self-contained linker is enabled on the CLI or by the target spec,
+    // - and if the self-contained linker is not disabled on the CLI.
+    let self_contained_cli = sess.opts.cg.link_self_contained.is_linker_enabled();
+    let self_contained_target = self_contained_components.is_linker_enabled();
+
+    // FIXME: in the future, codegen backends may need to have more control over this process: they
+    // don't always support all the features the linker expects here, and vice versa. For example,
+    // at the time of writing this, lld expects a newer style of aarch64 TLS relocations that
+    // cranelift doesn't implement yet. That in turn can impact whether linking would succeed on
+    // such a target when using the `cg_clif` backend and lld.
+    //
+    // Until interactions between backends and linker features are expressible, we limit target
+    // specs to opt-in to lld only when we're on the llvm backend, where it's expected to work and
+    // tested on CI. As usual, the CLI still has precedence over this, so that users and developers
+    // can still override this default when needed (e.g. for tests).
+    let uses_llvm_backend =
+        matches!(sess.opts.unstable_opts.codegen_backend.as_deref(), None | Some("llvm"));
+    if !uses_llvm_backend && !self_contained_cli && sess.opts.cg.linker_flavor.is_none() {
+        // We bail if we're not using llvm and lld was not explicitly requested on the CLI.
+        return;
+    }
+
+    let self_contained_linker = self_contained_cli || self_contained_target;
+    if self_contained_linker && !sess.opts.cg.link_self_contained.is_linker_disabled() {
         for path in sess.get_tools_search_paths(false) {
             cmd.arg({
                 let mut arg = OsString::from("-B");
diff --git a/compiler/rustc_target/src/spec/crt_objects.rs b/compiler/rustc_target/src/spec/crt_objects.rs
index c126390f5a9..53f710b8f9e 100644
--- a/compiler/rustc_target/src/spec/crt_objects.rs
+++ b/compiler/rustc_target/src/spec/crt_objects.rs
@@ -40,11 +40,9 @@
 //! but not gcc's. As a result rustc cannot link with C++ static libraries (#36710)
 //! when linking in self-contained mode.
 
-use crate::json::{Json, ToJson};
 use crate::spec::LinkOutputKind;
 use std::borrow::Cow;
 use std::collections::BTreeMap;
-use std::str::FromStr;
 
 pub type CrtObjects = BTreeMap<LinkOutputKind, Vec<Cow<'static, str>>>;
 
@@ -123,39 +121,3 @@ pub(super) fn pre_wasi_self_contained() -> CrtObjects {
 pub(super) fn post_wasi_self_contained() -> CrtObjects {
     new(&[])
 }
-
-/// Which logic to use to determine whether to use self-contained linking mode
-/// if `-Clink-self-contained` is not specified explicitly.
-#[derive(Clone, Copy, PartialEq, Hash, Debug)]
-pub enum LinkSelfContainedDefault {
-    False,
-    True,
-    Musl,
-    Mingw,
-}
-
-impl FromStr for LinkSelfContainedDefault {
-    type Err = ();
-
-    fn from_str(s: &str) -> Result<LinkSelfContainedDefault, ()> {
-        Ok(match s {
-            "false" => LinkSelfContainedDefault::False,
-            "true" | "wasm" => LinkSelfContainedDefault::True,
-            "musl" => LinkSelfContainedDefault::Musl,
-            "mingw" => LinkSelfContainedDefault::Mingw,
-            _ => return Err(()),
-        })
-    }
-}
-
-impl ToJson for LinkSelfContainedDefault {
-    fn to_json(&self) -> Json {
-        match *self {
-            LinkSelfContainedDefault::False => "false",
-            LinkSelfContainedDefault::True => "true",
-            LinkSelfContainedDefault::Musl => "musl",
-            LinkSelfContainedDefault::Mingw => "mingw",
-        }
-        .to_json()
-    }
-}
diff --git a/compiler/rustc_target/src/spec/linux_musl_base.rs b/compiler/rustc_target/src/spec/linux_musl_base.rs
index 61553e71b45..b698bcbcef6 100644
--- a/compiler/rustc_target/src/spec/linux_musl_base.rs
+++ b/compiler/rustc_target/src/spec/linux_musl_base.rs
@@ -1,5 +1,5 @@
-use crate::spec::crt_objects::{self, LinkSelfContainedDefault};
-use crate::spec::TargetOptions;
+use crate::spec::crt_objects;
+use crate::spec::{LinkSelfContainedDefault, TargetOptions};
 
 pub fn opts() -> TargetOptions {
     let mut base = super::linux_base::opts();
@@ -7,7 +7,7 @@ pub fn opts() -> TargetOptions {
     base.env = "musl".into();
     base.pre_link_objects_self_contained = crt_objects::pre_musl_self_contained();
     base.post_link_objects_self_contained = crt_objects::post_musl_self_contained();
-    base.link_self_contained = LinkSelfContainedDefault::Musl;
+    base.link_self_contained = LinkSelfContainedDefault::InferredForMusl;
 
     // These targets statically link libc by default
     base.crt_static_default = true;
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index f1c7513d885..3541810d437 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -38,7 +38,7 @@ use crate::abi::call::Conv;
 use crate::abi::{Endian, Integer, Size, TargetDataLayout, TargetDataLayoutErrors};
 use crate::json::{Json, ToJson};
 use crate::spec::abi::{lookup as lookup_abi, Abi};
-use crate::spec::crt_objects::{CrtObjects, LinkSelfContainedDefault};
+use crate::spec::crt_objects::CrtObjects;
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_fs_util::try_canonicalize;
 use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
@@ -278,6 +278,7 @@ impl LinkerFlavor {
         }
     }
 
+    /// Returns the corresponding backwards-compatible CLI flavor.
     fn to_cli(self) -> LinkerFlavorCli {
         match self {
             LinkerFlavor::Gnu(Cc::Yes, _)
@@ -298,6 +299,20 @@ impl LinkerFlavor {
         }
     }
 
+    /// Returns the modern CLI flavor that is the counterpart of this flavor.
+    fn to_cli_counterpart(self) -> LinkerFlavorCli {
+        match self {
+            LinkerFlavor::Gnu(cc, lld) => LinkerFlavorCli::Gnu(cc, lld),
+            LinkerFlavor::Darwin(cc, lld) => LinkerFlavorCli::Darwin(cc, lld),
+            LinkerFlavor::WasmLld(cc) => LinkerFlavorCli::WasmLld(cc),
+            LinkerFlavor::Unix(cc) => LinkerFlavorCli::Unix(cc),
+            LinkerFlavor::Msvc(lld) => LinkerFlavorCli::Msvc(lld),
+            LinkerFlavor::EmCc => LinkerFlavorCli::EmCc,
+            LinkerFlavor::Bpf => LinkerFlavorCli::Bpf,
+            LinkerFlavor::Ptx => LinkerFlavorCli::Ptx,
+        }
+    }
+
     fn infer_cli_hints(cli: LinkerFlavorCli) -> (Option<Cc>, Option<Lld>) {
         match cli {
             LinkerFlavorCli::Gnu(cc, lld) | LinkerFlavorCli::Darwin(cc, lld) => {
@@ -520,6 +535,98 @@ impl ToJson for LinkerFlavorCli {
     }
 }
 
+/// The different `-Clink-self-contained` options that can be specified in a target spec:
+/// - enabling or disabling in bulk
+/// - some target-specific pieces of inference to determine whether to use self-contained linking
+///   if `-Clink-self-contained` is not specified explicitly (e.g. on musl/mingw)
+/// - explicitly enabling some of the self-contained linking components, e.g. the linker component
+///   to use `rust-lld`
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub enum LinkSelfContainedDefault {
+    /// The target spec explicitly enables self-contained linking.
+    True,
+
+    /// The target spec explicitly disables self-contained linking.
+    False,
+
+    /// The target spec requests that the self-contained mode is inferred, in the context of musl.
+    InferredForMusl,
+
+    /// The target spec requests that the self-contained mode is inferred, in the context of mingw.
+    InferredForMingw,
+
+    /// The target spec explicitly enables a list of self-contained linking components: e.g. for
+    /// targets opting into a subset of components like the CLI's `-C link-self-contained=+linker`.
+    WithComponents(LinkSelfContainedComponents),
+}
+
+/// Parses a backwards-compatible `-Clink-self-contained` option string, without components.
+impl FromStr for LinkSelfContainedDefault {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<LinkSelfContainedDefault, ()> {
+        Ok(match s {
+            "false" => LinkSelfContainedDefault::False,
+            "true" | "wasm" => LinkSelfContainedDefault::True,
+            "musl" => LinkSelfContainedDefault::InferredForMusl,
+            "mingw" => LinkSelfContainedDefault::InferredForMingw,
+            _ => return Err(()),
+        })
+    }
+}
+
+impl ToJson for LinkSelfContainedDefault {
+    fn to_json(&self) -> Json {
+        match *self {
+            LinkSelfContainedDefault::WithComponents(components) => {
+                // Serialize the components in a json object's `components` field, to prepare for a
+                // future where `crt-objects-fallback` is removed from the json specs and
+                // incorporated as a field here.
+                let mut map = BTreeMap::new();
+                map.insert("components", components);
+                map.to_json()
+            }
+
+            // Stable backwards-compatible values
+            LinkSelfContainedDefault::True => "true".to_json(),
+            LinkSelfContainedDefault::False => "false".to_json(),
+            LinkSelfContainedDefault::InferredForMusl => "musl".to_json(),
+            LinkSelfContainedDefault::InferredForMingw => "mingw".to_json(),
+        }
+    }
+}
+
+impl LinkSelfContainedDefault {
+    /// Returns whether the target spec has self-contained linking explicitly disabled. Used to emit
+    /// errors if the user then enables it on the CLI.
+    pub fn is_disabled(self) -> bool {
+        self == LinkSelfContainedDefault::False
+    }
+
+    /// Returns whether the target spec explictly requests self-contained linking, i.e. not via
+    /// inference.
+    pub fn is_linker_enabled(self) -> bool {
+        match self {
+            LinkSelfContainedDefault::True => true,
+            LinkSelfContainedDefault::False => false,
+            LinkSelfContainedDefault::WithComponents(c) => {
+                c.contains(LinkSelfContainedComponents::LINKER)
+            }
+            _ => false,
+        }
+    }
+
+    /// Returns the key to use when serializing the setting to json:
+    /// - individual components in a `link-self-contained` object value
+    /// - the other variants as a backwards-compatible `crt-objects-fallback` string
+    fn json_key(self) -> &'static str {
+        match self {
+            LinkSelfContainedDefault::WithComponents(_) => "link-self-contained",
+            _ => "crt-objects-fallback",
+        }
+    }
+}
+
 bitflags::bitflags! {
     #[derive(Default)]
     /// The `-C link-self-contained` components that can individually be enabled or disabled.
@@ -579,6 +686,21 @@ impl LinkSelfContainedComponents {
             LinkSelfContainedComponents::MINGW,
         ]
     }
+
+    /// Returns whether at least a component is enabled.
+    pub fn are_any_components_enabled(self) -> bool {
+        !self.is_empty()
+    }
+
+    /// Returns whether `LinkSelfContainedComponents::LINKER` is enabled.
+    pub fn is_linker_enabled(self) -> bool {
+        self.contains(LinkSelfContainedComponents::LINKER)
+    }
+
+    /// Returns whether `LinkSelfContainedComponents::CRT_OBJECTS` is enabled.
+    pub fn is_crt_objects_enabled(self) -> bool {
+        self.contains(LinkSelfContainedComponents::CRT_OBJECTS)
+    }
 }
 
 impl IntoIterator for LinkSelfContainedComponents {
@@ -594,6 +716,22 @@ impl IntoIterator for LinkSelfContainedComponents {
     }
 }
 
+impl ToJson for LinkSelfContainedComponents {
+    fn to_json(&self) -> Json {
+        let components: Vec<_> = Self::all_components()
+            .into_iter()
+            .filter(|c| self.contains(*c))
+            .map(|c| {
+                // We can unwrap because we're iterating over all the known singular components,
+                // not an actual set of flags where `as_str` can fail.
+                c.as_str().unwrap().to_owned()
+            })
+            .collect();
+
+        components.to_json()
+    }
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Hash, Encodable, Decodable, HashStable_Generic)]
 pub enum PanicStrategy {
     Unwind,
@@ -1770,6 +1908,8 @@ pub struct TargetOptions {
     /// Same as `(pre|post)_link_objects`, but when self-contained linking mode is enabled.
     pub pre_link_objects_self_contained: CrtObjects,
     pub post_link_objects_self_contained: CrtObjects,
+    /// Behavior for the self-contained linking mode: inferred for some targets, or explicitly
+    /// enabled (in bulk, or with individual components).
     pub link_self_contained: LinkSelfContainedDefault,
 
     /// Linker arguments that are passed *before* any user-defined libraries.
@@ -2170,7 +2310,7 @@ impl TargetOptions {
     }
 
     fn update_to_cli(&mut self) {
-        self.linker_flavor_json = self.linker_flavor.to_cli();
+        self.linker_flavor_json = self.linker_flavor.to_cli_counterpart();
         self.lld_flavor_json = self.linker_flavor.lld_flavor();
         self.linker_is_gnu_json = self.linker_flavor.is_gnu();
         for (args, args_json) in [
@@ -2180,8 +2320,10 @@ impl TargetOptions {
             (&self.late_link_args_static, &mut self.late_link_args_static_json),
             (&self.post_link_args, &mut self.post_link_args_json),
         ] {
-            *args_json =
-                args.iter().map(|(flavor, args)| (flavor.to_cli(), args.clone())).collect();
+            *args_json = args
+                .iter()
+                .map(|(flavor, args)| (flavor.to_cli_counterpart(), args.clone()))
+                .collect();
         }
     }
 }
@@ -2724,8 +2866,43 @@ impl Target {
                 }
                 Ok::<(), String>(())
             } );
-
-            ($key_name:ident = $json_name:expr, link_self_contained) => ( {
+            ($key_name:ident, link_self_contained_components) => ( {
+                // Skeleton of what needs to be parsed:
+                //
+                // ```
+                // $name: {
+                //     "components": [
+                //         <array of strings>
+                //     ]
+                // }
+                // ```
+                let name = (stringify!($key_name)).replace("_", "-");
+                if let Some(o) = obj.remove(&name) {
+                    if let Some(o) = o.as_object() {
+                        let component_array = o.get("components")
+                            .ok_or_else(|| format!("{name}: expected a \
+                                JSON object with a `components` field."))?;
+                        let component_array = component_array.as_array()
+                            .ok_or_else(|| format!("{name}.components: expected a JSON array"))?;
+                        let mut components = LinkSelfContainedComponents::empty();
+                        for s in component_array {
+                            components |= match s.as_str() {
+                                Some(s) => {
+                                    LinkSelfContainedComponents::from_str(s)
+                                        .ok_or_else(|| format!("unknown \
+                                        `-Clink-self-contained` component: {s}"))?
+                                },
+                                _ => return Err(format!("not a string: {:?}", s)),
+                            };
+                        }
+                        base.$key_name = LinkSelfContainedDefault::WithComponents(components);
+                    } else {
+                        incorrect_type.push(name)
+                    }
+                }
+                Ok::<(), String>(())
+            } );
+            ($key_name:ident = $json_name:expr, link_self_contained_backwards_compatible) => ( {
                 let name = $json_name;
                 obj.remove(name).and_then(|o| o.as_str().and_then(|s| {
                     match s.parse::<LinkSelfContainedDefault>() {
@@ -2878,7 +3055,13 @@ impl Target {
         key!(post_link_objects = "post-link-objects", link_objects);
         key!(pre_link_objects_self_contained = "pre-link-objects-fallback", link_objects);
         key!(post_link_objects_self_contained = "post-link-objects-fallback", link_objects);
-        key!(link_self_contained = "crt-objects-fallback", link_self_contained)?;
+        // Deserializes the backwards-compatible variants of `-Clink-self-contained`
+        key!(
+            link_self_contained = "crt-objects-fallback",
+            link_self_contained_backwards_compatible
+        )?;
+        // Deserializes the components variant of `-Clink-self-contained`
+        key!(link_self_contained, link_self_contained_components)?;
         key!(pre_link_args_json = "pre-link-args", link_args);
         key!(late_link_args_json = "late-link-args", link_args);
         key!(late_link_args_dynamic_json = "late-link-args-dynamic", link_args);
@@ -3134,7 +3317,6 @@ impl ToJson for Target {
         target_option_val!(post_link_objects);
         target_option_val!(pre_link_objects_self_contained, "pre-link-objects-fallback");
         target_option_val!(post_link_objects_self_contained, "post-link-objects-fallback");
-        target_option_val!(link_self_contained, "crt-objects-fallback");
         target_option_val!(link_args - pre_link_args_json, "pre-link-args");
         target_option_val!(link_args - late_link_args_json, "late-link-args");
         target_option_val!(link_args - late_link_args_dynamic_json, "late-link-args-dynamic");
@@ -3231,6 +3413,10 @@ impl ToJson for Target {
             d.insert("default-adjusted-cabi".into(), Abi::name(abi).to_json());
         }
 
+        // Serializing `-Clink-self-contained` needs a dynamic key to support the
+        // backwards-compatible variants.
+        d.insert(self.link_self_contained.json_key().into(), self.link_self_contained.to_json());
+
         Json::Object(d)
     }
 }
diff --git a/compiler/rustc_target/src/spec/tests/tests_impl.rs b/compiler/rustc_target/src/spec/tests/tests_impl.rs
index e0ecf8037c3..257867b1b80 100644
--- a/compiler/rustc_target/src/spec/tests/tests_impl.rs
+++ b/compiler/rustc_target/src/spec/tests/tests_impl.rs
@@ -97,7 +97,7 @@ impl Target {
             );
         }
 
-        if self.link_self_contained == LinkSelfContainedDefault::False {
+        if self.link_self_contained.is_disabled() {
             assert!(
                 self.pre_link_objects_self_contained.is_empty()
                     && self.post_link_objects_self_contained.is_empty()
diff --git a/compiler/rustc_target/src/spec/wasm32_wasi.rs b/compiler/rustc_target/src/spec/wasm32_wasi.rs
index a0476d542e6..23fabcdc90d 100644
--- a/compiler/rustc_target/src/spec/wasm32_wasi.rs
+++ b/compiler/rustc_target/src/spec/wasm32_wasi.rs
@@ -72,7 +72,8 @@
 //! best we can with this target. Don't start relying on too much here unless
 //! you know what you're getting in to!
 
-use super::crt_objects::{self, LinkSelfContainedDefault};
+use super::crt_objects;
+use super::LinkSelfContainedDefault;
 use super::{wasm_base, Cc, LinkerFlavor, Target};
 
 pub fn target() -> Target {
diff --git a/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs b/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs
index c567155fee6..ba9a99ae380 100644
--- a/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs
+++ b/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs
@@ -72,8 +72,8 @@
 //! best we can with this target. Don't start relying on too much here unless
 //! you know what you're getting in to!
 
-use super::crt_objects::{self, LinkSelfContainedDefault};
-use super::{wasm_base, Cc, LinkerFlavor, Target};
+use super::{crt_objects, wasm_base};
+use super::{Cc, LinkSelfContainedDefault, LinkerFlavor, Target};
 
 pub fn target() -> Target {
     let mut options = wasm_base::options();
diff --git a/compiler/rustc_target/src/spec/wasm_base.rs b/compiler/rustc_target/src/spec/wasm_base.rs
index 341763aadba..82a3afeae31 100644
--- a/compiler/rustc_target/src/spec/wasm_base.rs
+++ b/compiler/rustc_target/src/spec/wasm_base.rs
@@ -1,4 +1,4 @@
-use super::crt_objects::LinkSelfContainedDefault;
+use super::LinkSelfContainedDefault;
 use super::{cvs, Cc, LinkerFlavor, PanicStrategy, RelocModel, TargetOptions, TlsModel};
 
 pub fn options() -> TargetOptions {
diff --git a/compiler/rustc_target/src/spec/windows_gnu_base.rs b/compiler/rustc_target/src/spec/windows_gnu_base.rs
index 2231983f071..b84e0fc0783 100644
--- a/compiler/rustc_target/src/spec/windows_gnu_base.rs
+++ b/compiler/rustc_target/src/spec/windows_gnu_base.rs
@@ -1,4 +1,5 @@
-use crate::spec::crt_objects::{self, LinkSelfContainedDefault};
+use crate::spec::crt_objects;
+use crate::spec::LinkSelfContainedDefault;
 use crate::spec::{cvs, Cc, DebuginfoKind, LinkerFlavor, Lld, SplitDebuginfo, TargetOptions};
 use std::borrow::Cow;
 
@@ -90,7 +91,7 @@ pub fn opts() -> TargetOptions {
         post_link_objects: crt_objects::post_mingw(),
         pre_link_objects_self_contained: crt_objects::pre_mingw_self_contained(),
         post_link_objects_self_contained: crt_objects::post_mingw_self_contained(),
-        link_self_contained: LinkSelfContainedDefault::Mingw,
+        link_self_contained: LinkSelfContainedDefault::InferredForMingw,
         late_link_args,
         late_link_args_dynamic,
         late_link_args_static,
diff --git a/tests/run-make/rust-lld-custom-target/Makefile b/tests/run-make/rust-lld-custom-target/Makefile
new file mode 100644
index 00000000000..007493ab0b9
--- /dev/null
+++ b/tests/run-make/rust-lld-custom-target/Makefile
@@ -0,0 +1,7 @@
+include ../tools.mk
+
+# needs-rust-lld
+# only-x86_64-unknown-linux-gnu
+all:
+	RUSTC_LOG=rustc_codegen_ssa::back::link=info $(RUSTC) --crate-type cdylib --target custom-target.json -Clink-args=-Wl,-v lib.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-custom-target/custom-target.json b/tests/run-make/rust-lld-custom-target/custom-target.json
new file mode 100644
index 00000000000..7828a99f235
--- /dev/null
+++ b/tests/run-make/rust-lld-custom-target/custom-target.json
@@ -0,0 +1,57 @@
+{
+  "arch": "x86_64",
+  "cpu": "x86-64",
+  "crt-static-respected": true,
+  "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
+  "dynamic-linking": true,
+  "env": "gnu",
+  "has-rpath": true,
+  "has-thread-local": true,
+  "link-self-contained": {
+    "components": [
+      "linker"
+    ]
+  },
+  "linker-flavor": "gnu-lld-cc",
+  "llvm-target": "x86_64-unknown-linux-gnu",
+  "max-atomic-width": 64,
+  "os": "linux",
+  "plt-by-default": false,
+  "position-independent-executables": true,
+  "pre-link-args": {
+    "gnu-cc": [
+      "-m64"
+    ],
+    "gnu-lld-cc": [
+      "-m64"
+    ]
+  },
+  "relro-level": "full",
+  "stack-probes": {
+    "kind": "inline-or-call",
+    "min-llvm-version-for-inline": [
+      16,
+      0,
+      0
+    ]
+  },
+  "static-position-independent-executables": true,
+  "supported-sanitizers": [
+    "address",
+    "cfi",
+    "leak",
+    "memory",
+    "thread",
+    "safestack"
+  ],
+  "supported-split-debuginfo": [
+    "packed",
+    "unpacked",
+    "off"
+  ],
+  "supports-xray": true,
+  "target-family": [
+    "unix"
+  ],
+  "target-pointer-width": "64"
+}
diff --git a/tests/run-make/rust-lld-custom-target/lib.rs b/tests/run-make/rust-lld-custom-target/lib.rs
new file mode 100644
index 00000000000..d8f5e310821
--- /dev/null
+++ b/tests/run-make/rust-lld-custom-target/lib.rs
@@ -0,0 +1,9 @@
+// Test linking using `cc` with `rust-lld`, using a custom target with features described in MCP 510
+// see https://github.com/rust-lang/compiler-team/issues/510 for more info:
+//
+// Starting from the `x86_64-unknown-linux-gnu` target spec, we add the following options:
+// - a linker-flavor using lld via a C compiler
+// - the self-contained linker component is enabled
+
+#![feature(no_core)]
+#![no_core]