From 0bca45f620ef436f2101d24d88c1f5a6b79eb988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Wed, 20 Sep 2023 22:13:43 +0000 Subject: [PATCH] allow target specs to declare self-contained linking components --- compiler/rustc_codegen_ssa/src/back/link.rs | 39 ++++- .../rustc_target/src/spec/linux_musl_base.rs | 5 +- compiler/rustc_target/src/spec/mod.rs | 159 +++++++++++++++++- .../rustc_target/src/spec/tests/tests_impl.rs | 2 +- compiler/rustc_target/src/spec/wasm32_wasi.rs | 5 +- .../src/spec/wasm32_wasi_preview1_threads.rs | 5 +- compiler/rustc_target/src/spec/wasm_base.rs | 4 +- .../rustc_target/src/spec/windows_gnu_base.rs | 5 +- 8 files changed, 197 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 28a51711b93..9a796356593 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -22,7 +22,8 @@ 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::LinkSelfContained; use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, Lld, PanicStrategy}; use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo}; @@ -1703,21 +1704,37 @@ 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 { + // Emit an error if the user requested self-contained mode on the CLI but the target explicitly + // refuses it. if let Some(self_contained) = sess.opts.cg.link_self_contained.explicitly_set { - if sess.target.link_self_contained == LinkSelfContainedDefault::False { + if sess.target.link_self_contained.is_disabled() { sess.emit_err(errors::UnsupportedLinkSelfContained); } return self_contained; } match sess.target.link_self_contained { - LinkSelfContainedDefault::False => false, - LinkSelfContainedDefault::True => true, + LinkSelfContained::True => true, + LinkSelfContained::False => false, + LinkSelfContained::WithComponents(components) => { + if components.is_all() { + true + } else if components.is_empty() { + false + } else { + // FIXME: Currently no target makes use of individual components to mean + // self-contained linking is fully enabled, in the sense of what the code downstream + // from here expects. Until components are handled a bit more deeply, we can + // consider that it's disabled and remain backwards compatible. + false + } + } + // 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 => { + LinkSelfContained::InferredForMusl => sess.crt_static(Some(crate_type)), + LinkSelfContained::InferredForMingw => { sess.host == sess.target && sess.target.vendor != "uwp" && detect_self_contained_mingw(&sess) @@ -2978,9 +2995,13 @@ 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_linker = sess.opts.cg.link_self_contained.is_linker_enabled() + || sess.target.options.link_self_contained.is_linker_enabled(); + 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/linux_musl_base.rs b/compiler/rustc_target/src/spec/linux_musl_base.rs index 61553e71b45..318d06e9889 100644 --- a/compiler/rustc_target/src/spec/linux_musl_base.rs +++ b/compiler/rustc_target/src/spec/linux_musl_base.rs @@ -1,4 +1,5 @@ -use crate::spec::crt_objects::{self, LinkSelfContainedDefault}; +use crate::spec::crt_objects; +use crate::spec::LinkSelfContained; use crate::spec::TargetOptions; pub fn opts() -> TargetOptions { @@ -7,7 +8,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 = LinkSelfContained::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 16f70cf43b3..8c41acdf4c6 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -520,6 +520,92 @@ 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 LinkSelfContained { + /// 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), +} + +impl ToJson for LinkSelfContained { + fn to_json(&self) -> Json { + match *self { + LinkSelfContained::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 values backwards-compatible with `LinkSelfContainedDefault` + LinkSelfContained::True => "true".to_json(), + LinkSelfContained::False => "false".to_json(), + LinkSelfContained::InferredForMusl => "musl".to_json(), + LinkSelfContained::InferredForMingw => "mingw".to_json(), + } + } +} + +impl LinkSelfContained { + /// 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 == LinkSelfContained::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 { + LinkSelfContained::True => true, + LinkSelfContained::False => false, + LinkSelfContained::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 { + LinkSelfContained::WithComponents(_) => "link-self-contained", + _ => "crt-objects-fallback", + } + } +} + +impl From for LinkSelfContained { + fn from(value: LinkSelfContainedDefault) -> Self { + match value { + LinkSelfContainedDefault::True => LinkSelfContained::True, + LinkSelfContainedDefault::False => LinkSelfContained::False, + LinkSelfContainedDefault::Musl => LinkSelfContained::InferredForMusl, + LinkSelfContainedDefault::Mingw => LinkSelfContained::InferredForMingw, + } + } +} + bitflags::bitflags! { #[derive(Default)] /// The `-C link-self-contained` components that can individually be enabled or disabled. @@ -594,6 +680,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, @@ -1769,7 +1871,9 @@ 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, - pub link_self_contained: LinkSelfContainedDefault, + /// Behavior for the self-contained linking mode: inferred for some targets, or explicitly + /// enabled (in bulk, or with individual components). + pub link_self_contained: LinkSelfContained, /// Linker arguments that are passed *before* any user-defined libraries. pub pre_link_args: LinkArgs, @@ -2242,7 +2346,7 @@ impl Default for TargetOptions { post_link_objects: Default::default(), pre_link_objects_self_contained: Default::default(), post_link_objects_self_contained: Default::default(), - link_self_contained: LinkSelfContainedDefault::False, + link_self_contained: LinkSelfContained::False, pre_link_args: LinkArgs::new(), pre_link_args_json: LinkArgsCli::new(), late_link_args: LinkArgs::new(), @@ -2723,12 +2827,47 @@ impl Target { } Ok::<(), String>(()) } ); - - ($key_name:ident = $json_name:expr, link_self_contained) => ( { + ($key_name:ident, LinkSelfContained) => ( { + // Skeleton of what needs to be parsed: + // + // ``` + // $name: { + // "components": [ + // + // ] + // } + // ``` + 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 = LinkSelfContained::WithComponents(components); + } else { + incorrect_type.push(name) + } + } + Ok::<(), String>(()) + } ); + ($key_name:ident = $json_name:expr, LinkSelfContainedDefault) => ( { let name = $json_name; obj.remove(name).and_then(|o| o.as_str().and_then(|s| { match s.parse::() { - Ok(lsc_default) => base.$key_name = lsc_default, + Ok(lsc_default) => base.$key_name = lsc_default.into(), _ => return Some(Err(format!("'{}' is not a valid `-Clink-self-contained` default. \ Use 'false', 'true', 'musl' or 'mingw'", s))), } @@ -2877,7 +3016,10 @@ 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", LinkSelfContainedDefault)?; + // Deserializes the components variant of `-Clink-self-contained` + key!(link_self_contained, LinkSelfContained)?; 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); @@ -3133,7 +3275,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"); @@ -3230,6 +3371,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..2c00c601b39 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::LinkSelfContained; use super::{wasm_base, Cc, LinkerFlavor, Target}; pub fn target() -> Target { @@ -85,7 +86,7 @@ pub fn target() -> Target { options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); // FIXME: Figure out cases in which WASM needs to link with a native toolchain. - options.link_self_contained = LinkSelfContainedDefault::True; + options.link_self_contained = LinkSelfContained::True; // Right now this is a bit of a workaround but we're currently saying that // the target by default has a static crt which we're taking as a signal 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..93a49acb151 100644 --- a/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs +++ b/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.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::LinkSelfContained; use super::{wasm_base, Cc, LinkerFlavor, Target}; pub fn target() -> Target { @@ -98,7 +99,7 @@ pub fn target() -> Target { options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); // FIXME: Figure out cases in which WASM needs to link with a native toolchain. - options.link_self_contained = LinkSelfContainedDefault::True; + options.link_self_contained = LinkSelfContained::True; // Right now this is a bit of a workaround but we're currently saying that // the target by default has a static crt which we're taking as a signal diff --git a/compiler/rustc_target/src/spec/wasm_base.rs b/compiler/rustc_target/src/spec/wasm_base.rs index 341763aadba..a29bddd849b 100644 --- a/compiler/rustc_target/src/spec/wasm_base.rs +++ b/compiler/rustc_target/src/spec/wasm_base.rs @@ -1,5 +1,5 @@ -use super::crt_objects::LinkSelfContainedDefault; use super::{cvs, Cc, LinkerFlavor, PanicStrategy, RelocModel, TargetOptions, TlsModel}; +use crate::spec::LinkSelfContained; pub fn options() -> TargetOptions { macro_rules! args { @@ -100,7 +100,7 @@ pub fn options() -> TargetOptions { // rust-lang/rust#104137: cannot blindly remove this without putting in // some other way to compensate for lack of `-nostartfiles` in linker // invocation. - link_self_contained: LinkSelfContainedDefault::True, + link_self_contained: LinkSelfContained::True, // This has no effect in LLVM 8 or prior, but in LLVM 9 and later when // PIC code is implemented this has quite a drastic effect if it stays diff --git a/compiler/rustc_target/src/spec/windows_gnu_base.rs b/compiler/rustc_target/src/spec/windows_gnu_base.rs index 2231983f071..d99a95a77e1 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::LinkSelfContained; 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: LinkSelfContained::InferredForMingw, late_link_args, late_link_args_dynamic, late_link_args_static,