diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 878a670cba3ef..15c5a6abc7cdd 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -9,7 +9,7 @@ use rustc_hir::def_id::CrateNum; use rustc_metadata::fs::{emit_metadata, METADATA_FILENAME}; 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, LdImpl, LinkerFlavorCli, 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}; @@ -17,7 +17,7 @@ use rustc_session::search_paths::PathKind; use rustc_session::utils::NativeLibKind; /// For all the linkers we support, and information they might /// need out of the shared crate context before we get rid of it. -use rustc_session::{filesearch, Session}; +use rustc_session::{config::InstrumentCoverage, filesearch, Session}; use rustc_span::symbol::Symbol; use rustc_span::DebuggerVisualizerFile; use rustc_target::spec::crt_objects::{CrtObjects, CrtObjectsFallback}; @@ -1130,7 +1130,7 @@ pub fn ignored_for_lto(sess: &Session, info: &CrateInfo, cnum: CrateNum) -> bool && (info.compiler_builtins == Some(cnum) || info.is_no_builtins.contains(&cnum)) } -// This functions tries to determine the appropriate linker (and corresponding LinkerFlavor) to use +/// This function tries to determine the appropriate linker (and corresponding LinkerFlavor) to use pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) { fn infer_from( sess: &Session, @@ -1203,9 +1203,13 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) { } } - // linker and linker flavor specified via command line have precedence over what the target - // specification specifies - if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), sess.opts.cg.linker_flavor) { + // Lower the potential `-C linker-flavor` CLI flag to its principal linker-flavor + let linker_flavor = + sess.opts.cg.linker_flavor.as_ref().map(|surface_flavor| surface_flavor.to_flavor()); + + // The `-C linker` and `-C linker-flavor` CLI flags have higher priority than what the target + // specification declares. + if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), linker_flavor) { return ret; } @@ -2012,7 +2016,7 @@ fn add_order_independent_options( out_filename: &Path, tmpdir: &Path, ) { - add_gcc_ld_path(cmd, sess, flavor); + handle_cli_linker_flavors(cmd, sess, flavor, crt_objects_fallback); add_apple_sdk(cmd, sess, flavor); @@ -2690,29 +2694,110 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result { } } -fn add_gcc_ld_path(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) { - if let Some(ld_impl) = sess.opts.unstable_opts.gcc_ld { - if let LinkerFlavor::Gcc = flavor { - match ld_impl { - LdImpl::Lld => { - let tools_path = sess.get_tools_search_paths(false); - let gcc_ld_dir = tools_path - .into_iter() - .map(|p| p.join("gcc-ld")) - .find(|p| { - p.join(if sess.host.is_like_windows { "ld.exe" } else { "ld" }).exists() - }) - .unwrap_or_else(|| sess.fatal("rust-lld (as ld) not found")); - cmd.arg({ - let mut arg = OsString::from("-B"); - arg.push(gcc_ld_dir); - arg - }); - cmd.arg(format!("-Wl,-rustc-lld-flavor={}", sess.target.lld_flavor.as_str())); - } +/// This takes care of the various possible enrichments to the linking that can be requested on the +/// CLI (and emitting errors and warnings when applicable): +/// - the unstable `-Zgcc-ld=lld` flag to use `rust-lld` +/// - the shortcut to `-fuse-ld=lld` with the `gcc` flavor, to eventually use `rust-lld` when +/// `-Clink-self-contained=linker` is introduced. +fn handle_cli_linker_flavors( + cmd: &mut dyn Linker, + sess: &Session, + flavor: LinkerFlavor, + crt_objects_fallback: bool, +) { + let unstable_gcc_lld = sess.opts.unstable_opts.gcc_ld == Some(LdImpl::Lld); + + // Sanity check: ensure `gcc` is the currently selected flavor. + if unstable_gcc_lld && LinkerFlavor::Gcc != flavor { + sess.fatal("`-Zgcc-ld` is used even though the linker flavor is not `gcc`"); + } + + let cg = &sess.opts.cg; + + // The `-C linker-flavor` CLI flag can optionally enrich linker-flavors. Check whether that's + // applicable, and emit errors if sanity checks fail. There's currently only one enrichment: + // adding an argument to the `cc` invocation to use `lld`. + match &cg.linker_flavor { + Some(LinkerFlavorCli::Gcc { use_ld }) => { + // Ensure `gcc` is the currently selected flavor. + // + // This should be the case as long as this piece of code stays downstream from the CLI + // linker-flavor lowering, and the actual `LinkerFlavor` is not changed or overriden by + // the time the order-independent options are added to the command args. + // + // If that changes by mistake (or intentionally) in the future, we panic. + if LinkerFlavor::Gcc != flavor { + bug!( + "`-Clinker-flavor=gcc:*` flag is used even though the \ + linker flavor is not `gcc`", + ); + } + + if *use_ld != LdImpl::Lld { + // We're not in a situation needing enrichments. + return; + } + } + + Some(LinkerFlavorCli::WellKnown(_)) | None => { + if !unstable_gcc_lld { + // We're not in a situation needing enrichments. + return; } - } else { - sess.fatal("option `-Z gcc-ld` is used even though linker flavor is not gcc"); } } + + // From now on in this function, we handle the `gcc` linker-flavor enrichment to use `lld`. + + // Start by checking if we're in the context of a known issue that users might hit when + // using `lld`: + // + // 1. when requesting self-contained CRT linking (or on a target that does it + // automatically), and coverage/profile generation: point at #79555 "Coverage is not + // generated when using rust-lld as linker" + let instrument_coverage = + cg.instrument_coverage.is_some() && cg.instrument_coverage != Some(InstrumentCoverage::Off); + let generate_profile = cg.profile_generate.enabled(); + if crt_objects_fallback && (instrument_coverage || generate_profile) { + sess.warn( + "Using `lld`, self-contained linking, and coverage or profile generation has known \ + issues. See issue #79555 for more details, at \ + https://github.com/rust-lang/rust/issues/79555", + ); + } + + // 2. Maybe point at https://github.com/flamegraph-rs/flamegraph/pull/157 or the + // corresponding rust/LLVM issue when/if it's tracked, depending on whether we use the + // workaround argument `--no-rosegment` by default when invoking `lld`. + // + // 3. If in the future, other linker flavors and targets are eligible to a `rust-lld` + // enrichment, maybe also point at target-specific issues like: + // - MSVC + ThinLTO blocker https://github.com/rust-lang/rust/issues/81408 + // - the "lld on MSVC" tracking issue https://github.com/rust-lang/rust/issues/71520 + // containing a list of blocking issues + + // Now, handle `rust-lld`. If the `-Zgcc-ld=lld` flag was provided, we use `rust-lld`, the + // rustup-distributed version of `lld` (when applicable, i.e. not in distro-builds) by: + // - checking the `lld-wrapper`s exist in the sysroot + // - adding their folder as a search path, or requesting to use a wrapper directly + if unstable_gcc_lld { + // A `gcc-ld` folder (containing the `lld-wrapper`s that will run `rust-lld`) is present in + // the sysroot's target-specific tool binaries folder. + let tools_path = sess.get_tools_search_paths(false); + let gcc_ld_dir = tools_path + .into_iter() + .map(|p| p.join("gcc-ld")) + .find(|p| p.join(if sess.host.is_like_windows { "ld.exe" } else { "ld" }).exists()) + .unwrap_or_else(|| sess.fatal("rust-lld (as ld) not found")); + + cmd.arg({ + let mut arg = OsString::from("-B"); + arg.push(gcc_ld_dir); + arg + }); + cmd.arg(format!("-Wl,-rustc-lld-flavor={}", sess.target.lld_flavor.as_str())); + } else { + // Otherwise, we were asked to use `lld` but not `rust-lld`. + cmd.arg("-fuse-ld=lld"); + } } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index d07e17f679251..e7dd70f40627b 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -3,6 +3,7 @@ use crate::interface::parse_cfgspecs; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig}; use rustc_session::config::InstrumentCoverage; +use rustc_session::config::LinkerFlavorCli; use rustc_session::config::Strip; use rustc_session::config::{build_configuration, build_session_options, to_crate_config}; use rustc_session::config::{ @@ -551,7 +552,7 @@ fn test_codegen_options_tracking_hash() { untracked!(link_args, vec![String::from("abc"), String::from("def")]); untracked!(link_self_contained, Some(true)); untracked!(linker, Some(PathBuf::from("linker"))); - untracked!(linker_flavor, Some(LinkerFlavor::Gcc)); + untracked!(linker_flavor, Some(LinkerFlavorCli::WellKnown(LinkerFlavor::Gcc))); untracked!(no_stack_check, true); untracked!(remark, Passes::Some(vec![String::from("pass1"), String::from("pass2")])); untracked!(rpath, true); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 55307b9cebb70..ec87f10d012d2 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -199,6 +199,56 @@ impl LinkerPluginLto { } } +/// The possible values `-C linker-flavor` can take: either one of the well-known linker flavors, or +/// an enrichment to one of them, for example representing additional arguments to its principal +/// linker flavor. +/// +/// This a surface enum for the CLI syntax, so that target specs don't have to deal with the +/// specifics of the CLI: they will always use the well-known principal linker flavor via the +/// `to_flavor` method, and never the enrichment variants. +/// +/// Currently used to represent finer-grained uses of `gcc`, to improve ease-of-use for wrapping a +/// given linker (currently: only `lld`). +#[derive(Clone, Debug, PartialEq)] +pub enum LinkerFlavorCli { + /// When the CLI option is one of the well-known linker-flavors that don't need special + /// handling. + WellKnown(LinkerFlavor), + + /// Enrichments to the `LinkerFlavor::Gcc` flavor, to specify the linker via `-fuse-ld`. + Gcc { use_ld: LdImpl }, +} + +impl LinkerFlavorCli { + /// Returns the principal linker flavor that this CLI option represents. + pub fn to_flavor(&self) -> LinkerFlavor { + match *self { + LinkerFlavorCli::WellKnown(flavor) => flavor, + LinkerFlavorCli::Gcc { .. } => LinkerFlavor::Gcc, + } + } +} + +/// Parses a `-C linker-flavor` option +impl FromStr for LinkerFlavorCli { + type Err = (); + + fn from_str(s: &str) -> Result { + // If the value is one of the existing flavor mappings, return that. + if let Some(flavor) = LinkerFlavor::from_str(s) { + return Ok(LinkerFlavorCli::WellKnown(flavor)); + } + + // Otherwise, it should be the enrichments to the gcc/cc flavor: wrapping a given linker + // separated by a colon like `gcc:lld`. + if s != "gcc:lld" { + return Err(()); + } + + Ok(LinkerFlavorCli::Gcc { use_ld: LdImpl::Lld }) + } +} + /// The different settings that can be enabled via the `-Z location-detail` flag. #[derive(Clone, PartialEq, Hash, Debug)] pub struct LocationDetail { @@ -2376,7 +2426,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { } } - if cg.linker_flavor == Some(LinkerFlavor::L4Bender) + if cg.linker_flavor == Some(LinkerFlavorCli::WellKnown(LinkerFlavor::L4Bender)) && !nightly_options::is_unstable_enabled(matches) { early_error( @@ -2386,6 +2436,18 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { ); } + if let Some(LinkerFlavorCli::Gcc { .. }) = &cg.linker_flavor { + // For testing purposes, until we have more feedback about these options: ensure `-Z + // unstable-options` is enabled when using the `gcc` linker flavor enrichments. + if !unstable_opts.unstable_options { + early_error( + error_format, + "the `gcc:lld` linker flavor is unstable, the `-Z unstable-options` \ + flag must also be passed to use it", + ); + } + } + let prints = collect_print_requests(&mut cg, &mut unstable_opts, matches, error_format); let cg = cg; diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index 44cf504864254..0f727750d4815 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -34,6 +34,9 @@ pub mod output; pub use getopts; +#[cfg(test)] +mod tests; + /// Requirements for a `StableHashingContext` to be used in this crate. /// This is a hack to allow using the `HashStable_Generic` derive macro /// instead of implementing everything in `rustc_middle`. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 1b583417ca089..5cd8f4d6ff2fc 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -5,7 +5,7 @@ use crate::lint; use crate::search_paths::SearchPath; use crate::utils::NativeLib; use rustc_errors::LanguageIdentifier; -use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet}; +use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, SanitizerSet}; use rustc_target::spec::{ RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, }; @@ -383,7 +383,8 @@ mod desc { "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`"; pub const parse_cfprotection: &str = "`none`|`no`|`n` (default), `branch`, `return`, or `full`|`yes`|`y` (equivalent to `branch` and `return`)"; pub const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`"; - pub const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavor::one_of(); + pub const parse_linker_flavor: &str = "one of: `em`, `gcc`, `l4-bender`, `ld`, `msvc`, \ + `ptx-linker`, `bpf-linker`, `wasm-ld`, `ld64.lld`, `ld.lld`, `lld-link`, or `gcc:lld`"; pub const parse_optimization_fuel: &str = "crate=integer"; pub const parse_mir_spanview: &str = "`statement` (default), `terminator`, or `block`"; pub const parse_instrument_coverage: &str = @@ -760,8 +761,8 @@ mod parse { true } - pub(crate) fn parse_linker_flavor(slot: &mut Option, v: Option<&str>) -> bool { - match v.and_then(LinkerFlavor::from_str) { + pub(crate) fn parse_linker_flavor(slot: &mut Option, v: Option<&str>) -> bool { + match v.and_then(|s| LinkerFlavorCli::from_str(s).ok()) { Some(lf) => *slot = Some(lf), _ => return false, } @@ -1120,7 +1121,7 @@ options! { on C toolchain installed in the system"), linker: Option = (None, parse_opt_pathbuf, [UNTRACKED], "system linker to link outputs with"), - linker_flavor: Option = (None, parse_linker_flavor, [UNTRACKED], + linker_flavor: Option = (None, parse_linker_flavor, [UNTRACKED], "linker flavor"), linker_plugin_lto: LinkerPluginLto = (LinkerPluginLto::Disabled, parse_linker_plugin_lto, [TRACKED], @@ -1616,7 +1617,7 @@ pub enum WasiExecModel { Reactor, } -#[derive(Clone, Copy, Hash)] +#[derive(Clone, Copy, Hash, PartialEq, Debug)] pub enum LdImpl { Lld, } diff --git a/compiler/rustc_session/src/tests.rs b/compiler/rustc_session/src/tests.rs new file mode 100644 index 0000000000000..9f9eef99fe3ba --- /dev/null +++ b/compiler/rustc_session/src/tests.rs @@ -0,0 +1,53 @@ +use std::str::FromStr; + +use crate::config::*; +use rustc_target::spec::{LinkerFlavor, LldFlavor}; + +/// When adding enrichments to `-C linker-flavor`, we want to ensure the existing `rustc_target` +/// `LinkerFlavor`s are still supported as-is: they are option values that can be used on +/// stable. +#[test] +pub fn parse_well_known_linker_flavor() { + // All `LinkerFlavor`s are wrapped as a whole, so there's no particular need to be + // exhaustive here. + assert_eq!(LinkerFlavorCli::from_str("gcc"), Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Gcc))); + assert_eq!( + LinkerFlavorCli::from_str("msvc"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Msvc)) + ); + assert_eq!( + LinkerFlavorCli::from_str("bpf-linker"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::BpfLinker)) + ); + assert_eq!( + LinkerFlavorCli::from_str("lld-link"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Lld(LldFlavor::Link))) + ); + assert_eq!( + LinkerFlavorCli::from_str("ld64.lld"), + Ok(LinkerFlavorCli::WellKnown(LinkerFlavor::Lld(LldFlavor::Ld64))) + ); + + // While other invalid values for well-known flavors are already errors + assert_eq!(LinkerFlavorCli::from_str("unknown_linker"), Err(())); +} + +/// Enrichments can currently allow the `gcc` flavor to use `lld`, much like you'd use `-fuse-ld` as +/// a link arg. +#[test] +pub fn parse_gcc_enrichment_linker_flavor() { + assert_eq!( + LinkerFlavorCli::from_str("gcc:lld"), + Ok(LinkerFlavorCli::Gcc { use_ld: LdImpl::Lld }) + ); + + // Only `gcc:lld` is supported for now + assert_eq!(LinkerFlavorCli::from_str("gcc:gold"), Err(())); + + // No linker actually mentioned + assert_eq!(LinkerFlavorCli::from_str("gcc:"), Err(())); + + // Only one `gcc:` separator allowed + assert_eq!(LinkerFlavorCli::from_str("gcc:gcc:"), Err(())); + assert_eq!(LinkerFlavorCli::from_str("gcc:gcc:linker"), Err(())); +} diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index f7abeafd38f10..4ae57e7627a88 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -165,6 +165,10 @@ macro_rules! flavor_mappings { ) } +// Note: until string concatenation can be done in const contexts, remember to update the +// `parse_linker_flavor` error message in rustc_session/src/options.rs whenever adding a new allowed +// value here, since it also mentions enriched CLI-allowed values like `gcc:lld` that are then +// lowered to these target specs `LinkerFlavor`s. flavor_mappings! { ((LinkerFlavor::Em), "em"), ((LinkerFlavor::Gcc), "gcc"), diff --git a/src/test/run-make/gcc-linker-flavor/Makefile b/src/test/run-make/gcc-linker-flavor/Makefile new file mode 100644 index 0000000000000..585220a758e44 --- /dev/null +++ b/src/test/run-make/gcc-linker-flavor/Makefile @@ -0,0 +1,9 @@ +# only-linux + +# Ensure that the enriched `gcc` linker-flavor passes the requested linker to `cc` + +-include ../../run-make-fulldeps/tools.mk +RUSTC_FLAGS = -C linker-flavor=gcc:lld -Z unstable-options --print link-args + +all: + $(RUSTC) $(RUSTC_FLAGS) empty.rs | $(CGREP) "fuse-ld=lld" diff --git a/src/test/run-make/gcc-linker-flavor/empty.rs b/src/test/run-make/gcc-linker-flavor/empty.rs new file mode 100644 index 0000000000000..4f6be7710134f --- /dev/null +++ b/src/test/run-make/gcc-linker-flavor/empty.rs @@ -0,0 +1,4 @@ +// Test ensuring that the enriched `gcc` linker flavor requesting `lld` (`-C +// linker-flavor=gcc:lld`) is passed to `cc` as `-fuse-ld=lld` + +fn main() {} diff --git a/src/test/ui/linkers/gcc_ld_mismatch.rs b/src/test/ui/linkers/gcc_ld_mismatch.rs new file mode 100644 index 0000000000000..2628bcb05f533 --- /dev/null +++ b/src/test/ui/linkers/gcc_ld_mismatch.rs @@ -0,0 +1,7 @@ +// build-fail +// compile-flags: -Zgcc-ld=lld -Clinker-flavor=em -Zunstable-options + +// Test ensuring that until the unstable flag is removed (if ever), if both the linker-flavor and +// `gcc-ld` flags are used, they ask for the same linker. + +fn main() {} diff --git a/src/test/ui/linkers/gcc_ld_mismatch.stderr b/src/test/ui/linkers/gcc_ld_mismatch.stderr new file mode 100644 index 0000000000000..c6ef47773556c --- /dev/null +++ b/src/test/ui/linkers/gcc_ld_mismatch.stderr @@ -0,0 +1,4 @@ +error: `-Zgcc-ld` is used even though the linker flavor is not `gcc` + +error: aborting due to previous error + diff --git a/src/test/ui/linkers/mentions_issue_79555.rs b/src/test/ui/linkers/mentions_issue_79555.rs new file mode 100644 index 0000000000000..aaefaca0c4cba --- /dev/null +++ b/src/test/ui/linkers/mentions_issue_79555.rs @@ -0,0 +1,11 @@ +// build-pass +// needs-profiler-support +// needs-rust-lld +// compile-flags: -C linker-flavor=gcc:lld -Z unstable-options -Clink-self-contained=y -Cinstrument-coverage + +// Test ensuring that a warning referencing lld known issue 79555 is emitted when: +// - we're asking to use lld, via the enriched gcc linker-flavor +// - the CRT object linking is on +// - either coverage or generating a profile is requested + +fn main() {} diff --git a/src/test/ui/linkers/mentions_issue_79555.stderr b/src/test/ui/linkers/mentions_issue_79555.stderr new file mode 100644 index 0000000000000..8a42d67708b97 --- /dev/null +++ b/src/test/ui/linkers/mentions_issue_79555.stderr @@ -0,0 +1,4 @@ +warning: Using `lld`, self-contained linking, and coverage or profile generation has known issues. See issue #79555 for more details, at https://github.com/rust-lang/rust/issues/79555 + +warning: 1 warning emitted + diff --git a/src/test/ui/linkers/unstable_linker_flavor.rs b/src/test/ui/linkers/unstable_linker_flavor.rs new file mode 100644 index 0000000000000..b0d3f2d261875 --- /dev/null +++ b/src/test/ui/linkers/unstable_linker_flavor.rs @@ -0,0 +1,7 @@ +// check-fail +// compile-flags: -C linker-flavor=gcc:lld + +// Test ensuring that the unstable `gcc:lld` value of the stable `-C linker-flavor` flag requires +// using `-Z unstable options` + +fn main() {} diff --git a/src/test/ui/linkers/unstable_linker_flavor.stderr b/src/test/ui/linkers/unstable_linker_flavor.stderr new file mode 100644 index 0000000000000..2826b8446697d --- /dev/null +++ b/src/test/ui/linkers/unstable_linker_flavor.stderr @@ -0,0 +1,2 @@ +error: the `gcc:lld` linker flavor is unstable, the `-Z unstable-options` flag must also be passed to use it +