From d9466af8f14e9f5783f1d9e614871d06beaeddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sun, 12 Jul 2026 21:01:56 +0200 Subject: [PATCH 1/9] Always generate private and hidden items in JSON docs of the stdlib --- src/bootstrap/src/core/build_steps/doc.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 0f051232757e8..0117ebd01465d 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -831,7 +831,9 @@ fn doc_std( cargo.rustdocflag(arg); } - if builder.config.library_docs_private_items { + // This is needed for cargo-semver-checks and potentially other downstream tools that consume + // the JSON data. + if format == DocumentationFormat::Json || builder.config.library_docs_private_items { cargo.rustdocflag("--document-private-items").rustdocflag("--document-hidden-items"); } From d2b26021379cc2bc66f391a7267c717ff08e15e7 Mon Sep 17 00:00:00 2001 From: kulst <60887784+kulst@users.noreply.github.com> Date: Sat, 10 Jan 2026 14:44:57 +0100 Subject: [PATCH 2/9] Improve error messages for disagreeing *target-modifiers* - Boolean target modifiers are now mentioned without a trailing `=` in the messages. - Wording improved for unset target modifiers. --- compiler/rustc_metadata/src/creader.rs | 2 ++ compiler/rustc_metadata/src/diagnostics.rs | 22 +++++++++++++++---- .../defaults_check.error.stderr | 2 +- ...compatible_fixedx18.error_generated.stderr | 4 ++-- .../no_value_bool.error.stderr | 2 +- .../no_value_bool.error_explicit.stderr | 2 +- ...izer-kcfi-normalize-ints.wrong_flag.stderr | 4 ++-- ...kcfi-normalize-ints.wrong_sanitizer.stderr | 2 +- ...zers-safestack-and-kcfi.missed_both.stderr | 2 +- 9 files changed, 29 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index dd063f5425511..bb91d855feaeb 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -380,6 +380,7 @@ impl CStore { flag_name, flag_name_prefixed, extern_value: extern_value.to_string(), + has_extern_value: !extern_value.is_empty(), }) } (Some(local_value), None) => { @@ -390,6 +391,7 @@ impl CStore { flag_name, flag_name_prefixed, local_value: local_value.to_string(), + has_local_value: !local_value.is_empty(), }) } (None, None) => panic!("Incorrect target modifiers report_diff(None, None)"), diff --git a/compiler/rustc_metadata/src/diagnostics.rs b/compiler/rustc_metadata/src/diagnostics.rs index 659406f8c84e9..01456377a234f 100644 --- a/compiler/rustc_metadata/src/diagnostics.rs +++ b/compiler/rustc_metadata/src/diagnostics.rs @@ -606,10 +606,16 @@ pub(crate) struct IncompatibleTargetModifiers { "the `{$flag_name_prefixed}` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely" )] #[note( - "unset `{$flag_name_prefixed}` in this crate is incompatible with `{$flag_name_prefixed}={$extern_value}` in dependency `{$extern_crate}`" + "`{$flag_name_prefixed}` is unset in this crate which is incompatible with {$has_extern_value -> + [false] `{$flag_name_prefixed}` being set + *[other] `{$flag_name_prefixed}={$extern_value}` + } in dependency `{$extern_crate}`" )] #[help( - "set `{$flag_name_prefixed}={$extern_value}` in this crate or unset `{$flag_name_prefixed}` in `{$extern_crate}`" + "set {$has_extern_value -> + [false] `{$flag_name_prefixed}` + *[other] `{$flag_name_prefixed}={$extern_value}` + } in this crate or unset `{$flag_name_prefixed}` in `{$extern_crate}`" )] #[help( "if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch={$flag_name}` to silence this error" @@ -622,6 +628,7 @@ pub(crate) struct IncompatibleTargetModifiersLMissed { pub flag_name: String, pub flag_name_prefixed: String, pub extern_value: String, + pub has_extern_value: bool, } #[derive(Diagnostic)] @@ -630,10 +637,16 @@ pub(crate) struct IncompatibleTargetModifiersLMissed { "the `{$flag_name_prefixed}` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely" )] #[note( - "`{$flag_name_prefixed}={$local_value}` in this crate is incompatible with unset `{$flag_name_prefixed}` in dependency `{$extern_crate}`" + "{$has_local_value -> + [false] `{$flag_name_prefixed}` being set + *[other] `{$flag_name_prefixed}={$local_value}` + } in this crate is incompatible with `{$flag_name_prefixed}` being unset in dependency `{$extern_crate}`" )] #[help( - "unset `{$flag_name_prefixed}` in this crate or set `{$flag_name_prefixed}={$local_value}` in `{$extern_crate}`" + "unset `{$flag_name_prefixed}` in this crate or set {$has_local_value -> + [false] `{$flag_name_prefixed}` + *[other] `{$flag_name_prefixed}={$local_value}` + } in `{$extern_crate}`" )] #[help( "if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch={$flag_name}` to silence this error" @@ -646,6 +659,7 @@ pub(crate) struct IncompatibleTargetModifiersRMissed { pub flag_name: String, pub flag_name_prefixed: String, pub local_value: String, + pub has_local_value: bool, } #[derive(Diagnostic)] diff --git a/tests/ui/target_modifiers/defaults_check.error.stderr b/tests/ui/target_modifiers/defaults_check.error.stderr index 644ec079ee3c7..106e64ff29356 100644 --- a/tests/ui/target_modifiers/defaults_check.error.stderr +++ b/tests/ui/target_modifiers/defaults_check.error.stderr @@ -5,7 +5,7 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zreg-struct-return` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: `-Zreg-struct-return=true` in this crate is incompatible with unset `-Zreg-struct-return` in dependency `default_reg_struct_return` + = note: `-Zreg-struct-return=true` in this crate is incompatible with `-Zreg-struct-return` being unset in dependency `default_reg_struct_return` = help: unset `-Zreg-struct-return` in this crate or set `-Zreg-struct-return=true` in `default_reg_struct_return` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=reg-struct-return` to silence this error diff --git a/tests/ui/target_modifiers/incompatible_fixedx18.error_generated.stderr b/tests/ui/target_modifiers/incompatible_fixedx18.error_generated.stderr index a8c13e0ed896d..bcdee625830a7 100644 --- a/tests/ui/target_modifiers/incompatible_fixedx18.error_generated.stderr +++ b/tests/ui/target_modifiers/incompatible_fixedx18.error_generated.stderr @@ -5,8 +5,8 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zfixed-x18` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: unset `-Zfixed-x18` in this crate is incompatible with `-Zfixed-x18=` in dependency `fixed_x18` - = help: set `-Zfixed-x18=` in this crate or unset `-Zfixed-x18` in `fixed_x18` + = note: `-Zfixed-x18` is unset in this crate which is incompatible with `-Zfixed-x18` being set in dependency `fixed_x18` + = help: set `-Zfixed-x18` in this crate or unset `-Zfixed-x18` in `fixed_x18` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=fixed-x18` to silence this error error: aborting due to 1 previous error diff --git a/tests/ui/target_modifiers/no_value_bool.error.stderr b/tests/ui/target_modifiers/no_value_bool.error.stderr index 479f7fb47cb72..c0e3178b89cf2 100644 --- a/tests/ui/target_modifiers/no_value_bool.error.stderr +++ b/tests/ui/target_modifiers/no_value_bool.error.stderr @@ -5,7 +5,7 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zreg-struct-return` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: unset `-Zreg-struct-return` in this crate is incompatible with `-Zreg-struct-return=true` in dependency `enabled_reg_struct_return` + = note: `-Zreg-struct-return` is unset in this crate which is incompatible with `-Zreg-struct-return=true` in dependency `enabled_reg_struct_return` = help: set `-Zreg-struct-return=true` in this crate or unset `-Zreg-struct-return` in `enabled_reg_struct_return` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=reg-struct-return` to silence this error diff --git a/tests/ui/target_modifiers/no_value_bool.error_explicit.stderr b/tests/ui/target_modifiers/no_value_bool.error_explicit.stderr index 479f7fb47cb72..c0e3178b89cf2 100644 --- a/tests/ui/target_modifiers/no_value_bool.error_explicit.stderr +++ b/tests/ui/target_modifiers/no_value_bool.error_explicit.stderr @@ -5,7 +5,7 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zreg-struct-return` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: unset `-Zreg-struct-return` in this crate is incompatible with `-Zreg-struct-return=true` in dependency `enabled_reg_struct_return` + = note: `-Zreg-struct-return` is unset in this crate which is incompatible with `-Zreg-struct-return=true` in dependency `enabled_reg_struct_return` = help: set `-Zreg-struct-return=true` in this crate or unset `-Zreg-struct-return` in `enabled_reg_struct_return` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=reg-struct-return` to silence this error diff --git a/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_flag.stderr b/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_flag.stderr index 1db79b025e9c9..c6abc4b574322 100644 --- a/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_flag.stderr +++ b/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_flag.stderr @@ -5,8 +5,8 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zsanitizer-cfi-normalize-integers` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: unset `-Zsanitizer-cfi-normalize-integers` in this crate is incompatible with `-Zsanitizer-cfi-normalize-integers=` in dependency `kcfi_normalize_ints` - = help: set `-Zsanitizer-cfi-normalize-integers=` in this crate or unset `-Zsanitizer-cfi-normalize-integers` in `kcfi_normalize_ints` + = note: `-Zsanitizer-cfi-normalize-integers` is unset in this crate which is incompatible with `-Zsanitizer-cfi-normalize-integers` being set in dependency `kcfi_normalize_ints` + = help: set `-Zsanitizer-cfi-normalize-integers` in this crate or unset `-Zsanitizer-cfi-normalize-integers` in `kcfi_normalize_ints` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=sanitizer-cfi-normalize-integers` to silence this error error: aborting due to 1 previous error diff --git a/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_sanitizer.stderr b/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_sanitizer.stderr index 5b949c87350b9..79e8ffbf04a5b 100644 --- a/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_sanitizer.stderr +++ b/tests/ui/target_modifiers/sanitizer-kcfi-normalize-ints.wrong_sanitizer.stderr @@ -5,7 +5,7 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zsanitizer` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: unset `-Zsanitizer` in this crate is incompatible with `-Zsanitizer=kcfi` in dependency `kcfi_normalize_ints` + = note: `-Zsanitizer` is unset in this crate which is incompatible with `-Zsanitizer=kcfi` in dependency `kcfi_normalize_ints` = help: set `-Zsanitizer=kcfi` in this crate or unset `-Zsanitizer` in `kcfi_normalize_ints` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=sanitizer` to silence this error diff --git a/tests/ui/target_modifiers/sanitizers-safestack-and-kcfi.missed_both.stderr b/tests/ui/target_modifiers/sanitizers-safestack-and-kcfi.missed_both.stderr index 44a2c24b2324e..adb627925c610 100644 --- a/tests/ui/target_modifiers/sanitizers-safestack-and-kcfi.missed_both.stderr +++ b/tests/ui/target_modifiers/sanitizers-safestack-and-kcfi.missed_both.stderr @@ -5,7 +5,7 @@ LL | #![feature(no_core)] | ^ | = help: the `-Zsanitizer` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely - = note: unset `-Zsanitizer` in this crate is incompatible with `-Zsanitizer=safestack,kcfi` in dependency `safestack_and_kcfi` + = note: `-Zsanitizer` is unset in this crate which is incompatible with `-Zsanitizer=safestack,kcfi` in dependency `safestack_and_kcfi` = help: set `-Zsanitizer=safestack,kcfi` in this crate or unset `-Zsanitizer` in `safestack_and_kcfi` = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=sanitizer` to silence this error From 76d51076582d9a3d5345a0c79d6953770b2979f9 Mon Sep 17 00:00:00 2001 From: kulst Date: Tue, 6 Jan 2026 21:42:06 +0100 Subject: [PATCH 3/9] Treat `-Ctarget-cpu` as a target-modifier when targeting AVR, AMDGCN and NVPTX For AVR, AMDGCN, and NVPTX, crates built with different target CPU values are not generally link-compatible. Add a `requires_consistent_cpu` flag to the target spec and enable it for these targets. When the flag is set, treat `-Ctarget-cpu` as a target modifier and require all linked crates to agree on its value. Reject `-Ctarget-cpu=native` before codegen for targets that set `requires_consistent_cpu` to true. Also do not include `native` in the printed `target-cpus` list for such targets. Add tests covering: - which built-in targets set `requires-consistent-cpu` - cross-crate behavior with and without `requires-consistent-cpu` - that an omitted `-Ctarget-cpu` compares equal to an explicitly specified default CPU - rejection and printing behavior for `native` - precedence of repeated `-Ctarget-cpu` flags in metadata comparison and LLVM IR --- compiler/rustc_codegen_cranelift/src/lib.rs | 4 +- compiler/rustc_codegen_gcc/src/gcc_util.rs | 3 +- compiler/rustc_codegen_llvm/src/llvm_util.rs | 12 +- compiler/rustc_metadata/src/rmeta/decoder.rs | 20 +++- compiler/rustc_session/src/config.rs | 3 + compiler/rustc_session/src/diagnostics.rs | 14 +++ compiler/rustc_session/src/options.rs | 30 ++++- compiler/rustc_session/src/session.rs | 13 +- compiler/rustc_target/src/spec/json.rs | 3 + compiler/rustc_target/src/spec/mod.rs | 14 +++ .../src/spec/targets/amdgcn_amd_amdhsa.rs | 2 + .../rustc_target/src/spec/targets/avr_none.rs | 2 + .../src/spec/targets/nvptx64_nvidia_cuda.rs | 3 + .../src/platform-support/amdgcn-amd-amdhsa.md | 8 +- .../rustc/src/platform-support/avr-none.md | 8 +- .../platform-support/nvptx64-nvidia-cuda.md | 3 +- .../unstable-book/src/compiler-flags/ls.md | 1 + tests/assembly-llvm/nvptx-arch-target-cpu.rs | 6 +- .../empty.rs | 3 + .../rmake.rs | 73 ++++++++++++ .../dependency.rs | 3 + .../target-cpu-as-target-modifier/main.rs | 5 + .../target-cpu-as-target-modifier/rmake.rs | 111 ++++++++++++++++++ tests/run-make/target-cpu-precedence/lib.rs | 37 ++++++ tests/run-make/target-cpu-precedence/rmake.rs | 41 +++++++ .../target-specs/require-explicit-cpu.json | 3 +- tests/ui/target-cpu/explicit-target-cpu.rs | 20 +--- .../auxiliary/target_cpu_default_explicit.rs | 8 ++ .../auxiliary/target_cpu_default_implicit.rs | 8 ++ .../auxiliary/target_cpu_non_default.rs | 8 ++ ...arget_cpu_default.explicit_mismatch.stderr | 13 ++ ...arget_cpu_default.implicit_mismatch.stderr | 13 ++ .../ui/target_modifiers/target_cpu_default.rs | 38 ++++++ 33 files changed, 497 insertions(+), 36 deletions(-) create mode 100644 tests/run-make/requires-consistent-cpu-no-native/empty.rs create mode 100644 tests/run-make/requires-consistent-cpu-no-native/rmake.rs create mode 100644 tests/run-make/target-cpu-as-target-modifier/dependency.rs create mode 100644 tests/run-make/target-cpu-as-target-modifier/main.rs create mode 100644 tests/run-make/target-cpu-as-target-modifier/rmake.rs create mode 100644 tests/run-make/target-cpu-precedence/lib.rs create mode 100644 tests/run-make/target-cpu-precedence/rmake.rs create mode 100644 tests/ui/target_modifiers/auxiliary/target_cpu_default_explicit.rs create mode 100644 tests/ui/target_modifiers/auxiliary/target_cpu_default_implicit.rs create mode 100644 tests/ui/target_modifiers/auxiliary/target_cpu_non_default.rs create mode 100644 tests/ui/target_modifiers/target_cpu_default.explicit_mismatch.stderr create mode 100644 tests/ui/target_modifiers/target_cpu_default.implicit_mismatch.stderr create mode 100644 tests/ui/target_modifiers/target_cpu_default.rs diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index af783f31a2136..f5c852a2bf970 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -42,7 +42,7 @@ use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig, back}; use rustc_log::tracing::info; use rustc_middle::dep_graph::WorkProductMap; use rustc_session::Session; -use rustc_session::config::OutputFilenames; +use rustc_session::config::{NATIVE_CPU, OutputFilenames}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{Arch, CfgAbi, Env, Os}; @@ -341,7 +341,7 @@ fn build_isa(sess: &Session, jit: bool) -> Arc { let flags = settings::Flags::new(flags_builder); let isa_builder = match sess.opts.cg.target_cpu.as_deref() { - Some("native") => cranelift_native::builder_with_options(true).unwrap(), + Some(NATIVE_CPU) => cranelift_native::builder_with_options(true).unwrap(), Some(value) => { let mut builder = cranelift_codegen::isa::lookup(target_triple.clone()).unwrap_or_else(|err| { diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 330b5ff6828d5..a95b4da28eb63 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -3,6 +3,7 @@ use gccjit::Context; use rustc_codegen_ssa::target_features; use rustc_data_structures::smallvec::{SmallVec, smallvec}; use rustc_session::Session; +use rustc_session::config::NATIVE_CPU; use rustc_target::spec::Arch; fn gcc_features_by_flags(sess: &Session, features: &mut Vec) { @@ -115,7 +116,7 @@ fn arch_to_gcc(name: &str) -> &str { } fn handle_native(name: &str) -> &str { - if name != "native" { + if name != NATIVE_CPU { return arch_to_gcc(name); } diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 73b7f699b606d..87d8676cd8018 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -14,7 +14,7 @@ use rustc_data_structures::small_c_str::SmallCStr; use rustc_fs_util::path_to_c_string; use rustc_middle::bug; use rustc_session::Session; -use rustc_session::config::{PrintKind, PrintRequest}; +use rustc_session::config::{NATIVE_CPU, PrintKind, PrintRequest}; use rustc_target::spec::{ Arch, CfgAbi, Env, MergeFunctions, Os, PanicStrategy, SmallDataThresholdSupport, }; @@ -514,10 +514,12 @@ fn print_target_cpus(sess: &Session, tm: &llvm::TargetMachine, out: &mut String) // Only print the "native" entry when host and target are the same arch, // since otherwise it could be wrong or misleading. - if sess.host.arch == sess.target.arch { + // Also do not print it if `requires_consistent_cpu` is set, because in this case + // "native" would be rejected. + if sess.host.arch == sess.target.arch && !sess.target.requires_consistent_cpu { let host = get_host_cpu_name(); cpus.push_front(Cpu { - cpu_name: "native", + cpu_name: NATIVE_CPU, remark: format!(" - Select the CPU of the current host (currently {host})."), }); } @@ -612,7 +614,7 @@ fn get_host_cpu_name() -> &'static str { /// LLVM. Otherwise, the string is returned as-is. fn handle_native(cpu_name: &str) -> &str { match cpu_name { - "native" => get_host_cpu_name(), + NATIVE_CPU => get_host_cpu_name(), _ => cpu_name, } } @@ -666,7 +668,7 @@ pub(crate) fn global_llvm_features(sess: &Session, only_base_features: bool) -> // -Ctarget-cpu=native match sess.opts.cg.target_cpu { - Some(ref s) if s == "native" => { + Some(ref s) if s == NATIVE_CPU => { // We have already figured out the actual CPU name with `LLVMRustGetHostCPUName` and set // that for LLVM, so the features implied by that CPU name will be available everywhere. // However, that is not sufficient: e.g. `skylake` alone is not sufficient to tell if diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index bed2be51a3468..efd167b7d7598 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -749,6 +749,7 @@ impl MetadataBlob { "lang_items".to_owned(), "features".to_owned(), "items".to_owned(), + "target_modifiers".to_owned(), ]; let ls_kinds = if ls_kinds.contains(&"all".to_owned()) { &all_ls_kinds } else { ls_kinds }; @@ -918,11 +919,28 @@ impl MetadataBlob { write!(out, "\n")?; } + "target_modifiers" => { + writeln!(out, "=Target modifiers=")?; + + for modifier in root.decode_target_modifiers(self) { + let extended = modifier.extend(); + + writeln!( + out, + "-{}{}={} [{}]", + extended.prefix, + extended.name, + modifier.value_name, + extended.tech_value, + )?; + } + } _ => { writeln!( out, - "unknown -Zls kind. allowed values are: all, root, lang_items, features, items" + "unknown -Zls kind. allowed values are: all, root, lang_items, features, items, \ + target_modifiers" )?; } } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 8ed01315ade53..37488ebbf1e8f 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -46,6 +46,9 @@ mod native_libs; mod print_request; pub mod sigpipe; +/// Special CPU name requesting the CPU of the current host. +pub const NATIVE_CPU: &str = "native"; + /// The different settings that the `-C strip` flag can have. #[derive(Clone, Copy, PartialEq, Hash, Debug)] pub enum Strip { diff --git a/compiler/rustc_session/src/diagnostics.rs b/compiler/rustc_session/src/diagnostics.rs index d1989609cefe2..29f60c7612b4b 100644 --- a/compiler/rustc_session/src/diagnostics.rs +++ b/compiler/rustc_session/src/diagnostics.rs @@ -716,3 +716,17 @@ pub(crate) struct ThinLtoNotSupportedByBackend; #[derive(Diagnostic)] #[diag("`-Zpacked-stack` is only supported on s390x")] pub(crate) struct UnsupportedPackedStack; + +#[derive(Diagnostic)] +#[diag("`-Ctarget-cpu=native` is not allowed for target `{$target_triple}`")] +#[note("this target requires consistent `-Ctarget-cpu` values across all crates")] +#[help( + "specify the target CPU explicitly {$need_explicit_cpu -> + [false] or leave it blank to use the default + *[other] {\"\"} + }" +)] +pub(crate) struct NativeTargetCpuNotAllowed<'a> { + pub(crate) target_triple: &'a TargetTuple, + pub(crate) need_explicit_cpu: bool, +} diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 6f3a505c2c26f..7ea7274cafd8b 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -130,6 +130,28 @@ mod target_modifier_consistency_check { } true } + pub(super) fn target_cpu( + sess: &Session, + l: &TargetModifier, + r: Option<&TargetModifier>, + ) -> bool { + if !sess.target.requires_consistent_cpu { + return true; + } + let l_tech_value = l.extend().tech_value; + let r_tech_value = match r { + Some(r) => r.extend().tech_value, + // If only one of the two compared crates specifies the CPU + // explicitly we compare against the target's default CPU. + None => { + // We reuse the same parsing logic. + CodegenOptionsTargetModifiers::TargetCpu + .reparse(sess.target.cpu.as_ref()) + .tech_value + } + }; + l_tech_value == r_tech_value + } } impl TargetModifier { @@ -152,7 +174,11 @@ impl TargetModifier { } _ => {} }, - _ => {} + OptionsTargetModifiers::CodegenOptions(codegen) => match codegen { + CodegenOptionsTargetModifiers::TargetCpu => { + return target_modifier_consistency_check::target_cpu(sess, self, other); + } + }, }; match other { Some(other) => self.extend().tech_value == other.extend().tech_value, @@ -2273,7 +2299,7 @@ options! { symbol_mangling_version: Option = (None, parse_symbol_mangling_version, [TRACKED], "which mangling version to use for symbol names ('legacy', 'v0' (default), or 'hashed')"), - target_cpu: Option = (None, parse_opt_string, [TRACKED], + target_cpu: Option = (None, parse_opt_string, [TRACKED] { TARGET_MODIFIER: TargetCpu }, "select target processor (`rustc --print target-cpus` for details)"), target_feature: String = (String::new(), parse_target_feature, [TRACKED], "target specific attributes. (`rustc --print target-features` for details). \ diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 733470a7e0471..ab704787191e5 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -38,8 +38,8 @@ use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use crate::config::{ self, Cfg, CheckCfg, CoverageLevel, CoverageOptions, CrateType, DebugInfo, ErrorOutputType, - FunctionReturn, Input, InstrumentCoverage, InstrumentMcount, OptLevel, OutFileName, OutputType, - PointerAuthOption, SwitchWithOptPath, + FunctionReturn, Input, InstrumentCoverage, InstrumentMcount, NATIVE_CPU, OptLevel, OutFileName, + OutputType, PointerAuthOption, SwitchWithOptPath, }; use crate::filesearch::FileSearch; use crate::lint::LintId; @@ -1695,6 +1695,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) { sess.dcx().emit_err(diagnostics::UnsupportedPackedStack); } } + + if let Some(ref cpu_name) = sess.opts.cg.target_cpu { + if cpu_name == NATIVE_CPU && sess.target.requires_consistent_cpu { + sess.dcx().emit_fatal(diagnostics::NativeTargetCpuNotAllowed { + target_triple: &sess.opts.target_triple, + need_explicit_cpu: sess.target.need_explicit_cpu, + }); + } + } } /// Holds data on the current incremental compilation session, if there is one. diff --git a/compiler/rustc_target/src/spec/json.rs b/compiler/rustc_target/src/spec/json.rs index 922b1fb3dff8d..03dbbcb5b481d 100644 --- a/compiler/rustc_target/src/spec/json.rs +++ b/compiler/rustc_target/src/spec/json.rs @@ -116,6 +116,7 @@ impl Target { forward!(asm_args); forward!(cpu); forward!(need_explicit_cpu); + forward!(requires_consistent_cpu); forward!(unsupported_cpus); forward!(features); forward!(dynamic_linking); @@ -321,6 +322,7 @@ impl ToJson for Target { target_option_val!(asm_args); target_option_val!(cpu); target_option_val!(need_explicit_cpu); + target_option_val!(requires_consistent_cpu); target_option_val!(unsupported_cpus); target_option_val!(features); target_option_val!(dynamic_linking); @@ -543,6 +545,7 @@ struct TargetSpecJson { asm_args: Option]>>, cpu: Option>, need_explicit_cpu: Option, + requires_consistent_cpu: Option, unsupported_cpus: Option]>>, features: Option>, dynamic_linking: Option, diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index caac3bbc7c505..a747b0aec7b28 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1,3 +1,4 @@ +// ignore-tidy-filelength //! [Flexible target specification.](https://github.com/rust-lang/rfcs/pull/131) //! //! Rust targets a wide variety of usecases, and in the interest of flexibility, @@ -2403,6 +2404,10 @@ pub struct TargetOptions { /// Whether a cpu needs to be explicitly set. /// Set to true if there is no default cpu. Defaults to false. pub need_explicit_cpu: bool, + /// Whether `-Ctarget-cpu` is treated as a target modifier. If this is set + /// all crates that are linked together must have been compiled with the + /// same target-cpu. Defaults to false. + pub requires_consistent_cpu: bool, /// A list of CPUs that are provided by LLVM but are considered unsupported by Rust. /// These CPUs are omitted from `--print target-cpus` output and will cause an error /// if used with `-Ctarget-cpu`. @@ -2860,6 +2865,7 @@ impl Default for TargetOptions { asm_args: cvs![], cpu: "generic".into(), need_explicit_cpu: false, + requires_consistent_cpu: false, unsupported_cpus: cvs![], features: "".into(), direct_access_external_data: None, @@ -3636,6 +3642,14 @@ impl Target { } } + // Check that the target cpu constraints make sense. + if self.need_explicit_cpu { + check!( + self.requires_consistent_cpu, + "if `need_explicit_cpu` is set, then `requires_consistent_cpu` must be set" + ); + } + // Check that the given target-features string makes some basic sense. if !self.features.is_empty() { let mut features_enabled = FxHashSet::default(); diff --git a/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs b/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs index d6a2cfc2aab55..9bb36b8147c0b 100644 --- a/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs +++ b/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs @@ -24,6 +24,8 @@ pub(crate) fn target() -> Target { // There are many CPUs, one for each hardware generation. // Require to set one explicitly as there is no good default. need_explicit_cpu: true, + // crates with different `target-cpu`s are not link-compatible for amdgcn + requires_consistent_cpu: true, max_atomic_width: Some(64), diff --git a/compiler/rustc_target/src/spec/targets/avr_none.rs b/compiler/rustc_target/src/spec/targets/avr_none.rs index eaf4e956f55ec..0dcd2428fc703 100644 --- a/compiler/rustc_target/src/spec/targets/avr_none.rs +++ b/compiler/rustc_target/src/spec/targets/avr_none.rs @@ -26,6 +26,8 @@ pub(crate) fn target() -> Target { atomic_cas: false, relocation_model: RelocModel::Static, need_explicit_cpu: true, + // crates with different `target-cpu`s are not link-compatible for AVR + requires_consistent_cpu: true, ..TargetOptions::default() }, } diff --git a/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs b/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs index b74aca48bb9ec..c6f7ee8da5abc 100644 --- a/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs +++ b/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs @@ -29,6 +29,9 @@ pub(crate) fn target() -> Target { "sm_60", "sm_61", "sm_62" ), + // crates with different `target-cpu`s are not link-compatible for NVPTX + requires_consistent_cpu: true, + // FIXME: create tests for the atomics. max_atomic_width: Some(64), diff --git a/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md b/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md index 8934e7085b8d7..e530457348f77 100644 --- a/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md +++ b/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md @@ -61,7 +61,7 @@ Build the library as `cdylib`: crate-type = ["cdylib"] ``` -The target-cpu must be from the list [supported by LLVM] (or printed with `rustc --target amdgcn-amd-amdhsa --print target-cpus`). +The target-cpu[^1] must be from the list [supported by LLVM] (or printed with `rustc --target amdgcn-amd-amdhsa --print target-cpus`). The GPU version on the current system can be found e.g. with [`rocminfo`]. For a GPU series that has xnack support but the target GPU has not, the `-xnack-support` target-feature needs to be enabled. I.e. if the ISA info as printed with [`rocminfo`] says something about `xnack-`, e.g. `gfx1010:xnack-`, add `-Ctarget-feature=-xnack-support` to the rustflags. @@ -78,6 +78,12 @@ rustflags = ["-Ctarget-cpu=gfx1100"] build-std = ["core"] # Optional: "alloc" ``` +[^1]: For this target, crates with different values of `-C target-cpu` are not link-compatible. +Because of this, the compiler ensures that all crates that are linked together +were compiled with the same `target-cpu`. This must be considered when +using `rustc` commands for building. It is less relevant for Cargo, since Cargo +generally uses the same `target-cpu` for all crates in a build. + ## Running Rust programs To run a binary on an AMD GPU, a host runtime is needed. diff --git a/src/doc/rustc/src/platform-support/avr-none.md b/src/doc/rustc/src/platform-support/avr-none.md index 36874387b8048..1862890ca43f3 100644 --- a/src/doc/rustc/src/platform-support/avr-none.md +++ b/src/doc/rustc/src/platform-support/avr-none.md @@ -63,13 +63,19 @@ The final binary will be placed into Note that since AVRs have rather small amounts of registers, ROM and RAM, it's recommended to always use `--release` to avoid running out of space. -Also, please note that specifying `-C target-cpu` is required - here's a list of +Also, please note that specifying `-C target-cpu`[^1] is required - here's a list of the possible variants: https://github.com/llvm/llvm-project/blob/093d4db2f3c874d4683fb01194b00dbb20e5c713/clang/lib/Basic/Targets/AVR.cpp#L32 Note that devices that have no SRAM are not supported, same as when compiling C/C++ programs with avr-gcc or Clang. +[^1]: For this target, crates with different values of `-C target-cpu` are not link-compatible. +Because of this, the compiler ensures that all crates that are linked together +were compiled with the same `target-cpu`. This must be considered when +using `rustc` commands for building. It is less relevant for Cargo, since Cargo +generally uses the same `target-cpu` for all crates in a build. + ## Testing You can use [`simavr`](https://github.com/buserror/simavr) to emulate the diff --git a/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md b/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md index 5c74e7b95d9ab..5ee0ae28cafb2 100644 --- a/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md +++ b/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md @@ -30,7 +30,8 @@ It is often beneficial to specify the target SM architecture, such as `-C target One can use `-C target-feature=+ptx80` to choose a later PTX version without changing the target SM architecture (the default in this case, `ptx78`, requires CUDA driver version 11.8, while `ptx80` would require driver version 12.0). Later PTX versions may allow more efficient code generation. -Although Rust follows LLVM in representing `ptx*` and `sm_*` as target features, they should be thought of as having crate granularity, set via (either via `-Ctarget-cpu` and optionally `-Ctarget-feature`). +For this target, the compiler enforces that all crates built into a binary use the same value for `-C target-cpu`. Therefore, if specifying a value different from the current default (`sm_70`), it is necessary to also build `core` manually with this value. This is possible by invoking cargo with `-Z build-std=core` using a nightly toolchain. +Although Rust follows LLVM in representing `ptx*` and `sm_*` as target features, they should also be thought of as having binary granularity. However, this is not enforced by the compiler. When building crates together, the same value for all crates should be set via `-C target-feature`. While the compiler accepts `#[target_feature(enable = "ptx80", enable = "sm_89")]`, it is not supported, may not behave as intended, and may become erroneous in the future. ## Minimum SM and PTX support by Rust version diff --git a/src/doc/unstable-book/src/compiler-flags/ls.md b/src/doc/unstable-book/src/compiler-flags/ls.md index 812f2eee1d57f..bf94a6c1156ae 100644 --- a/src/doc/unstable-book/src/compiler-flags/ls.md +++ b/src/doc/unstable-book/src/compiler-flags/ls.md @@ -14,6 +14,7 @@ Allowed values are: - `lang_items`: Language items used and missing, if any. - `features`: Library features defined via the `#[stable]` and `#[unstable]` internal attributes. - `items`: All items (such as modules, functions...) in the crate, including attributes like their visibility +- `target_modifiers`: Values of command-line arguments that rustc may require to match across linked crates. - `all`: All of the above ## Example diff --git a/tests/assembly-llvm/nvptx-arch-target-cpu.rs b/tests/assembly-llvm/nvptx-arch-target-cpu.rs index 8e1ef9b13fa40..bc4a84c6460c5 100644 --- a/tests/assembly-llvm/nvptx-arch-target-cpu.rs +++ b/tests/assembly-llvm/nvptx-arch-target-cpu.rs @@ -2,10 +2,8 @@ //@ compile-flags: --crate-type cdylib -C target-cpu=sm_87 //@ only-nvptx64 -#![no_std] - -//@ aux-build: breakpoint-panic-handler.rs -extern crate breakpoint_panic_handler; +#![feature(no_core)] +#![no_core] // Verify target arch override via `target-cpu`. // CHECK: .target sm_87 diff --git a/tests/run-make/requires-consistent-cpu-no-native/empty.rs b/tests/run-make/requires-consistent-cpu-no-native/empty.rs new file mode 100644 index 0000000000000..c12c35de302a1 --- /dev/null +++ b/tests/run-make/requires-consistent-cpu-no-native/empty.rs @@ -0,0 +1,3 @@ +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] diff --git a/tests/run-make/requires-consistent-cpu-no-native/rmake.rs b/tests/run-make/requires-consistent-cpu-no-native/rmake.rs new file mode 100644 index 0000000000000..6fdb68dd513e2 --- /dev/null +++ b/tests/run-make/requires-consistent-cpu-no-native/rmake.rs @@ -0,0 +1,73 @@ +// Check how `native` interacts with targets that require consistent +// `-Ctarget-cpu` values across crates. +// +// First, the test derives a custom target from the host target and removes +// `requires-consistent-cpu` if present. Since host and target architecture match, +// this target prints `native` in `--print target-cpus`. +// Then, the test derives a second custom target from the host target and sets +// `requires-consistent-cpu` to true. This target must not print `native` in +// `--print target-cpus`. +// Finally, the test verifies that `-Ctarget-cpu=native` is rejected when using +// the second custom target. + +use std::fs; + +use run_make_support::*; +use serde_json::{Value, json}; + +fn main() { + let is_native_cpu_line = |line: &str| line.trim_start().starts_with("native "); + let target_cpus = |target: &str| { + rustc().target(target).arg("-Zunstable-options").print("target-cpus").run().stdout_utf8() + }; + + let host = rustc().print("host-tuple").run().stdout_utf8().trim().to_owned(); + + let host_target_with_cpu_mismatch_allowed = custom_host(&host, false); + let cpus = target_cpus(&host_target_with_cpu_mismatch_allowed); + assert!( + cpus.lines().any(is_native_cpu_line), + "`native` should be printed for the host target without `requires-consistent-cpu`;\n\ + output was:\n{cpus}" + ); + + let host_target_with_requires_consistent_cpu = custom_host(&host, true); + let cpus = target_cpus(&host_target_with_requires_consistent_cpu); + assert!( + !cpus.lines().any(is_native_cpu_line), + "`native` must not be printed for targets with `requires-consistent-cpu`;\n\ + output was:\n{cpus}" + ); + + rustc() + .arg("-Zunstable-options") + .target(&host_target_with_requires_consistent_cpu) + .target_cpu("native") + .input("empty.rs") + .run_fail() + .assert_stderr_contains("`-Ctarget-cpu=native` is not allowed") + .assert_stderr_contains("requires consistent `-Ctarget-cpu` values"); +} + +fn custom_host(host: &str, requires_consistent_cpu: bool) -> String { + let json = rustc() + .arg("-Zunstable-options") + .target(host) + .print("target-spec-json") + .run() + .stdout_utf8(); + + let mut spec: Value = serde_json::from_str(&json).unwrap(); + let spec = spec.as_object_mut().expect("expected target-spec JSON to be an object"); + let filename = if requires_consistent_cpu { + spec.insert("requires-consistent-cpu".to_string(), json!(true)); + format!("{host}-requires-consistent-cpu.json") + } else { + spec.remove("requires-consistent-cpu"); + format!("{host}-cpu-mismatch-allowed.json") + }; + + fs::write(&filename, serde_json::to_string_pretty(&spec).unwrap()).unwrap(); + + filename +} diff --git a/tests/run-make/target-cpu-as-target-modifier/dependency.rs b/tests/run-make/target-cpu-as-target-modifier/dependency.rs new file mode 100644 index 0000000000000..c12c35de302a1 --- /dev/null +++ b/tests/run-make/target-cpu-as-target-modifier/dependency.rs @@ -0,0 +1,3 @@ +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] diff --git a/tests/run-make/target-cpu-as-target-modifier/main.rs b/tests/run-make/target-cpu-as-target-modifier/main.rs new file mode 100644 index 0000000000000..bc32fc9c304b7 --- /dev/null +++ b/tests/run-make/target-cpu-as-target-modifier/main.rs @@ -0,0 +1,5 @@ +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] + +extern crate dependency; diff --git a/tests/run-make/target-cpu-as-target-modifier/rmake.rs b/tests/run-make/target-cpu-as-target-modifier/rmake.rs new file mode 100644 index 0000000000000..a3be062dd8090 --- /dev/null +++ b/tests/run-make/target-cpu-as-target-modifier/rmake.rs @@ -0,0 +1,111 @@ +// This test verifies that only the expected built-in targets opt in to treating +// `-C target-cpu` as a target modifier. +// +// The test first asks rustc for the full list of supported built-in targets, +// then performs two checks for each target. +// +// 1. Target-spec check +// +// The test prints the target specification as JSON and verifies that +// `requires-consistent-cpu` is set to `true` only for the expected targets. +// All other targets must either omit the field or set it to `false`. +// +// 2. Cross-crate compatibility check +// +// The test builds `dependency.rs` with target CPU `A`, then builds `main.rs` +// against that dependency under several configurations. +// +// The `main.rs` build must succeed when: +// - it is built with the same target CPU `A`; +// - it is built with a different target CPU `B`, but the target does not opt +// in to `requires-consistent-cpu`; +// - it is built with a different target CPU `B`, the target does opt in to +// `requires-consistent-cpu`, and the compiler is invoked with +// `-C unsafe-allow-abi-mismatch=target-cpu`. +// +// The `main.rs` build must fail when it is built with target CPU `B`, the +// target opts in to `requires-consistent-cpu`, and +// `-C unsafe-allow-abi-mismatch=target-cpu` is not used. +// +// The test only verifies the target-modifier compatibility check, so it does +// not need to run code generation and uses `--emit=metadata`. +// +// To avoid depending on whether a target is supported by the selected codegen +// backend, the test also uses `-Z codegen-backend=dummy`. + +use std::collections::BTreeSet; +use std::sync::LazyLock; + +use run_make_support::*; +use serde_json::Value; + +static EXPECTED: LazyLock> = + LazyLock::new(|| BTreeSet::from(["amdgcn-amd-amdhsa", "avr-none", "nvptx64-nvidia-cuda"])); + +use run_make_support::rustc; +fn main() { + verify_target_specs(); + verify_cross_crate_compatibility(); +} + +fn verify_target_specs() { + let requires_consistent_cpu = |spec: &Value| { + spec.get("requires-consistent-cpu").and_then(Value::as_bool).unwrap_or(false) + }; + + let json = rustc().arg("-Zunstable-options").print("all-target-specs-json").run().stdout_utf8(); + + let specs: Value = serde_json::from_str(&json).unwrap(); + + let actual = specs + .as_object() + .expect("expected all-target-specs-json to be a JSON object") + .iter() + .filter_map(|(target, spec)| requires_consistent_cpu(spec).then_some(target.as_str())) + .collect::>(); + + assert_eq!( + actual, *EXPECTED, + "unexpected set of built-in targets with `requires-consistent-cpu = true`", + ); +} + +fn verify_cross_crate_compatibility() { + let target_list = rustc().print("target-list").run().stdout_utf8(); + let targets: Vec<&str> = target_list.lines().collect(); + + for target in targets.iter() { + let compiler = |cpu: &str, input: &str| { + let mut cmd = rustc(); + cmd.target(target) + .target_cpu(cpu) + .input(input) + .panic("abort") + .args(["--emit=metadata", "-Zcodegen-backend=dummy"]); + cmd + }; + let (first_cpu, second_cpu) = ("A", "B"); + + // Build dependency.rs using the first target-cpu + compiler(first_cpu, "dependency.rs").run(); + + if EXPECTED.contains(target) { + // Testing targets where `-Ctarget-cpu` acts as a target modifier: + // Building with the same target cpu must succeed. + compiler(first_cpu, "main.rs").run(); + // Building with a different target cpu must succeed if + // rustc is invoked with `-Cunsafe-allow-abi-mismatch=target-cpu` + compiler(second_cpu, "main.rs").arg("-Cunsafe-allow-abi-mismatch=target-cpu").run(); + // Building with a different target cpu must fail if + // rustc is _not_ invoked with `-Cunsafe-allow-abi-mismatch=target-cpu` + compiler(second_cpu, "main.rs").run_fail().assert_stderr_contains( + "error: mixing `-Ctarget-cpu` will cause \ + an ABI mismatch in crate `main`", + ); + } else { + // Testing targets where `-Ctarget-cpu` does not act as a target modifier: + // Building with a different target cpu must succeed. + compiler(second_cpu, "main.rs").run(); + } + } +} diff --git a/tests/run-make/target-cpu-precedence/lib.rs b/tests/run-make/target-cpu-precedence/lib.rs new file mode 100644 index 0000000000000..3f92f54eb357e --- /dev/null +++ b/tests/run-make/target-cpu-precedence/lib.rs @@ -0,0 +1,37 @@ +#![feature(no_core, lang_items)] +#![no_core] +#![crate_type = "rlib"] + +#[lang = "pointee_sized"] +#[diagnostic::on_unimplemented( + message = "values of type `{Self}` may or may not have a size", + label = "may or may not have a known size" +)] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +#[diagnostic::on_unimplemented( + message = "the size for values of type `{Self}` cannot be known", + label = "doesn't have a known size" +)] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +#[diagnostic::on_unimplemented( + message = "the size for values of type `{Self}` cannot be known at compilation time", + label = "doesn't have a size known at compile-time" +)] +pub trait Sized: MetaSized {} + +// Capture the effective CPU from LLVM IR. This also verifies that the second +// `-Ctarget-cpu` argument took precedence. +// CHECK-LABEL: target triple = "nvptx64-nvidia-cuda" +// CHECK-LABEL: define {{.*}} @foo() {{.*}} #0 +// CHECK-LABEL: attributes #0 = {{.*}} "target-cpu"="sm_80" {{.*}} +#[no_mangle] +pub fn foo() { + () +} +// The value reconstructed from crate metadata must be identical. +// CHECK-LABEL: =Target modifiers= +// CHECK-LABEL: -Ctarget-cpu=sm_80 [Some("sm_80")] diff --git a/tests/run-make/target-cpu-precedence/rmake.rs b/tests/run-make/target-cpu-precedence/rmake.rs new file mode 100644 index 0000000000000..13dfcd72e3891 --- /dev/null +++ b/tests/run-make/target-cpu-precedence/rmake.rs @@ -0,0 +1,41 @@ +// Check that, when rustc is invoked with multiple `-Ctarget-cpu` options, +// the last value is used both for the corresponding target modifier in the +// crate metadata and for the target CPU recorded in the LLVM IR. +//@ needs-llvm-components: nvptx + +use run_make_support::*; + +const TARGET: &str = "nvptx64-nvidia-cuda"; +const FIRST_CPU: &str = "sm_70"; +const LAST_CPU: &str = "sm_80"; + +fn main() { + // Compile lib.rs, emit llvm-ir and metadata + rustc() + .input("lib.rs") + .crate_name("target_cpu_precedence") + .target(TARGET) + .target_cpu(FIRST_CPU) + .target_cpu(LAST_CPU) + .emit("llvm-ir=output.ll,metadata=output.rmeta") + .run(); + + let llvm_ir = rfs::read_to_string("output.ll"); + // Decode the metadata. + let target_modifiers = + rustc().arg("-Zls=target_modifiers").input("output.rmeta").run().stdout_utf8(); + + // Make sure the first target cpu did not survive in either artifact. + assert_not_contains(&llvm_ir, format!(r#""target-cpu"="{FIRST_CPU}""#)); + assert_not_contains(&target_modifiers, format!(r#"target-cpu: Some("{FIRST_CPU}")"#)); + + // Combine LLVM-IR and the metadata output into one file. + // Use FileCheck to verify that both LLVM IR and metadata contain the + // last mentioned target-cpu + let filecheck_input = format!( + "{llvm_ir}\n\ + ; --- target modifiers decoded from output.rmeta ---\n\ + {target_modifiers}" + ); + llvm_filecheck().patterns("lib.rs").stdin_buf(filecheck_input).run(); +} diff --git a/tests/run-make/target-specs/require-explicit-cpu.json b/tests/run-make/target-specs/require-explicit-cpu.json index 4f23b644d8cf6..28de8f64ba597 100644 --- a/tests/run-make/target-specs/require-explicit-cpu.json +++ b/tests/run-make/target-specs/require-explicit-cpu.json @@ -6,5 +6,6 @@ "target-pointer-width": 32, "arch": "x86", "os": "linux", - "need-explicit-cpu": true + "need-explicit-cpu": true, + "requires-consistent-cpu": true } diff --git a/tests/ui/target-cpu/explicit-target-cpu.rs b/tests/ui/target-cpu/explicit-target-cpu.rs index cfec444372728..29f8e9de1f6ea 100644 --- a/tests/ui/target-cpu/explicit-target-cpu.rs +++ b/tests/ui/target-cpu/explicit-target-cpu.rs @@ -21,24 +21,14 @@ //@[avr_cpu] needs-llvm-components: avr //@[avr_cpu] compile-flags: -Ctarget-cpu=atmega328p //@[avr_cpu] build-pass + //@ ignore-backends: gcc #![crate_type = "rlib"] - -// FIXME(#140038): this can't use `minicore` yet because `minicore` doesn't currently propagate the -// `-C target-cpu` for targets that *require* a `target-cpu` being specified. -#![feature(no_core, lang_items)] +// We don't want to link in any other crate as this would make it necessary to specify +// a `-Ctarget-cpu` for them resulting in a *target-modifier* disagreement error instead of the +// error mentioned below. +#![feature(no_core)] #![no_core] -#[lang = "pointee_sized"] -pub trait PointeeSized {} - -#[lang = "meta_sized"] -pub trait MetaSized: PointeeSized {} - -#[lang="sized"] -trait Sized {} - -pub fn foo() {} - //[amdgcn_nocpu,avr_nocpu]~? ERROR target requires explicitly specifying a cpu with `-C target-cpu` diff --git a/tests/ui/target_modifiers/auxiliary/target_cpu_default_explicit.rs b/tests/ui/target_modifiers/auxiliary/target_cpu_default_explicit.rs new file mode 100644 index 0000000000000..3c56f64dfdb3a --- /dev/null +++ b/tests/ui/target_modifiers/auxiliary/target_cpu_default_explicit.rs @@ -0,0 +1,8 @@ +//@ no-prefer-dynamic +//@ compile-flags: --target nvptx64-nvidia-cuda -Ctarget-cpu=sm_70 +//@ needs-llvm-components: nvptx +//@ ignore-backends: gcc + +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] diff --git a/tests/ui/target_modifiers/auxiliary/target_cpu_default_implicit.rs b/tests/ui/target_modifiers/auxiliary/target_cpu_default_implicit.rs new file mode 100644 index 0000000000000..6b71ca5eb034d --- /dev/null +++ b/tests/ui/target_modifiers/auxiliary/target_cpu_default_implicit.rs @@ -0,0 +1,8 @@ +//@ no-prefer-dynamic +//@ compile-flags: --target nvptx64-nvidia-cuda +//@ needs-llvm-components: nvptx +//@ ignore-backends: gcc + +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] diff --git a/tests/ui/target_modifiers/auxiliary/target_cpu_non_default.rs b/tests/ui/target_modifiers/auxiliary/target_cpu_non_default.rs new file mode 100644 index 0000000000000..a4fa7e2a33af6 --- /dev/null +++ b/tests/ui/target_modifiers/auxiliary/target_cpu_non_default.rs @@ -0,0 +1,8 @@ +//@ no-prefer-dynamic +//@ compile-flags: --target nvptx64-nvidia-cuda -Ctarget-cpu=sm_80 +//@ needs-llvm-components: nvptx +//@ ignore-backends: gcc + +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] diff --git a/tests/ui/target_modifiers/target_cpu_default.explicit_mismatch.stderr b/tests/ui/target_modifiers/target_cpu_default.explicit_mismatch.stderr new file mode 100644 index 0000000000000..775569818175b --- /dev/null +++ b/tests/ui/target_modifiers/target_cpu_default.explicit_mismatch.stderr @@ -0,0 +1,13 @@ +error: mixing `-Ctarget-cpu` will cause an ABI mismatch in crate `target_cpu_default` + --> $DIR/target_cpu_default.rs:25:1 + | +LL | #![feature(no_core)] + | ^ + | + = help: the `-Ctarget-cpu` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely + = note: `-Ctarget-cpu=sm_70` in this crate is incompatible with `-Ctarget-cpu=sm_80` in dependency `target_cpu_non_default` + = help: set `-Ctarget-cpu=sm_80` in this crate or `-Ctarget-cpu=sm_70` in `target_cpu_non_default` + = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=target-cpu` to silence this error + +error: aborting due to 1 previous error + diff --git a/tests/ui/target_modifiers/target_cpu_default.implicit_mismatch.stderr b/tests/ui/target_modifiers/target_cpu_default.implicit_mismatch.stderr new file mode 100644 index 0000000000000..ecfef76992d37 --- /dev/null +++ b/tests/ui/target_modifiers/target_cpu_default.implicit_mismatch.stderr @@ -0,0 +1,13 @@ +error: mixing `-Ctarget-cpu` will cause an ABI mismatch in crate `target_cpu_default` + --> $DIR/target_cpu_default.rs:25:1 + | +LL | #![feature(no_core)] + | ^ + | + = help: the `-Ctarget-cpu` flag modifies the ABI so Rust crates compiled with different values of this flag cannot be used together safely + = note: `-Ctarget-cpu` is unset in this crate which is incompatible with `-Ctarget-cpu=sm_80` in dependency `target_cpu_non_default` + = help: set `-Ctarget-cpu=sm_80` in this crate or unset `-Ctarget-cpu` in `target_cpu_non_default` + = help: if you are sure this will not cause problems, you may use `-Cunsafe-allow-abi-mismatch=target-cpu` to silence this error + +error: aborting due to 1 previous error + diff --git a/tests/ui/target_modifiers/target_cpu_default.rs b/tests/ui/target_modifiers/target_cpu_default.rs new file mode 100644 index 0000000000000..b4f3a5afda2e6 --- /dev/null +++ b/tests/ui/target_modifiers/target_cpu_default.rs @@ -0,0 +1,38 @@ +// Check that an implicit default `-Ctarget-cpu` and an explicit default +// `-Ctarget-cpu` compare equal. +// +// NVPTX requires consistent `-Ctarget-cpu` values across crates, but it does +// not require the CPU to be specified explicitly. Therefore, compiling one crate +// without `-Ctarget-cpu` and another crate with the target's default CPU +// explicitly specified must be accepted. +// +// The mismatch revisions additionally check that an implicit or explicit default +// CPU must not match a different explicitly specified CPU. + +//@ aux-build:target_cpu_default_implicit.rs +//@ aux-build:target_cpu_default_explicit.rs +//@ aux-build:target_cpu_non_default.rs +//@ compile-flags: --target nvptx64-nvidia-cuda +//@ needs-llvm-components: nvptx +//@ ignore-backends: gcc + +//@ revisions: implicit_default explicit_default implicit_mismatch explicit_mismatch +//@[implicit_default] check-pass +//@[explicit_default] compile-flags: -Ctarget-cpu=sm_70 +//@[explicit_default] check-pass +//@[explicit_mismatch] compile-flags: -Ctarget-cpu=sm_70 + +#![feature(no_core)] +//[implicit_mismatch]~^ ERROR mixing `-Ctarget-cpu` will cause an ABI mismatch +//[explicit_mismatch]~^^ ERROR mixing `-Ctarget-cpu` will cause an ABI mismatch +#![crate_type = "rlib"] +#![no_core] + +#[cfg(any(implicit_default, explicit_default))] +extern crate target_cpu_default_implicit; + +#[cfg(any(implicit_default, explicit_default))] +extern crate target_cpu_default_explicit; + +#[cfg(any(implicit_mismatch, explicit_mismatch))] +extern crate target_cpu_non_default; From 40bfe4fafd0c220786bf1ff7dfc7aa5c11b129fa Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 19 Jul 2026 08:19:00 +0200 Subject: [PATCH 4/9] restrict const-eval-related 'content' triggers to library/ folder --- triagebot.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index 262ea3a60b0b7..a161f7c23d5fb 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1476,6 +1476,7 @@ cc = ["@rust-lang/miri"] [mentions."#[miri::intrinsic_fallback_is_spec]"] type = "content" +trigger_files = ["library"] message = """ ⚠️ `#[miri::intrinsic_fallback_is_spec]` must only be used if the function actively checks for all UB cases, and explores the possible non-determinism of the intrinsic. @@ -1484,6 +1485,7 @@ cc = ["@rust-lang/miri"] [mentions."#[rustc_allow_const_fn_unstable"] type = "content" +trigger_files = ["library"] message = """ ⚠️ `#[rustc_allow_const_fn_unstable]` needs careful audit to avoid accidentally exposing unstable implementation details on stable. @@ -1492,6 +1494,7 @@ cc = ["@rust-lang/wg-const-eval"] [mentions."#[rustc_intrinsic_const_stable_indirect]"] type = "content" +trigger_files = ["library"] message = """ ⚠️ `#[rustc_intrinsic_const_stable_indirect]` controls whether intrinsics can be exposed to stable const code; adding it needs t-lang approval. From 245b3e54a37674ea28e896596ae5c45f41295ed2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 19 Jul 2026 08:19:27 +0200 Subject: [PATCH 5/9] move a non-content mention out of the content section --- triagebot.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/triagebot.toml b/triagebot.toml index a161f7c23d5fb..707d54941aeee 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1464,6 +1464,13 @@ message = """ """ cc = ["@jieyouxu"] +[mentions."compiler/rustc_attr_parsing/src/attributes/diagnostic"] +message = "Some changes occurred to diagnostic attributes." +cc = ["@mejrs"] +[mentions."compiler/rustc_hir/src/attrs/diagnostic.rs"] +message = "Some changes occurred to diagnostic attributes." +cc = ["@mejrs"] + # Content-based mentions [mentions."miri"] @@ -1501,13 +1508,6 @@ code; adding it needs t-lang approval. """ cc = ["@rust-lang/wg-const-eval"] -[mentions."compiler/rustc_attr_parsing/src/attributes/diagnostic"] -message = "Some changes occurred to diagnostic attributes." -cc = ["@mejrs"] -[mentions."compiler/rustc_hir/src/attrs/diagnostic.rs"] -message = "Some changes occurred to diagnostic attributes." -cc = ["@mejrs"] - # ------------------------------------------------------------------------------ # PR assignments # ------------------------------------------------------------------------------ From c84d86263539f6617bd08c5c77633774cbac5396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sun, 19 Jul 2026 10:23:59 +0200 Subject: [PATCH 6/9] Fix documentation in stdarch --- library/std_detect/src/detect/os/linux/aarch64.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std_detect/src/detect/os/linux/aarch64.rs b/library/std_detect/src/detect/os/linux/aarch64.rs index b733b8a9eb236..936f06859491e 100644 --- a/library/std_detect/src/detect/os/linux/aarch64.rs +++ b/library/std_detect/src/detect/os/linux/aarch64.rs @@ -258,7 +258,7 @@ impl AtHwcap { /// Initializes the cache from the feature -bits. /// /// The feature dependencies here come directly from LLVM's feature definitions: - /// https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/AArch64/AArch64.td + /// fn cache(self, is_exynos9810: bool) -> cache::Initializer { let mut value = cache::Initializer::default(); { From 2c380b43d82cc2495c6fc80b7c7566a799479ed0 Mon Sep 17 00:00:00 2001 From: Romain Perier Date: Sun, 19 Jul 2026 16:44:08 +0200 Subject: [PATCH 7/9] Update rustc crate crossbeam-epoch to 0.9.20 [SECURITY] See https://github.com/crossbeam-rs/crossbeam/pull/1276 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7608b83edaa1..d5a3a9974fe5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1001,9 +1001,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] From ec05c7f6a07b09511dab3c51b1b88b37444a5efd Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 19 Jul 2026 17:20:13 +0200 Subject: [PATCH 8/9] fix ICE is opsem inhabitedness check --- .../rustc_middle/src/ty/inhabitedness/mod.rs | 8 ++++++-- .../pointee-type-with-error-issue-159560.rs | 10 ++++++++++ .../pointee-type-with-error-issue-159560.stderr | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.rs create mode 100644 tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.stderr diff --git a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs index 1d4c55aa34690..8e5316bab8fbb 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs @@ -339,8 +339,12 @@ fn is_opsem_inhabited_recursor<'tcx, SEEN>( }) } - ty::Error(_) - | ty::Infer(..) + ty::Error(_error_guaranteed) => { + // We have a token proving there was an error, so we can return a dummy value. + true + } + + ty::Infer(..) | ty::Placeholder(..) | ty::Bound(..) | ty::Param(..) diff --git a/tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.rs b/tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.rs new file mode 100644 index 0000000000000..52a3b1ee2f0c1 --- /dev/null +++ b/tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.rs @@ -0,0 +1,10 @@ +//@ compile-flags: -Zextra-const-ub-checks + +struct A { + f: _, //~ERROR: not allowed +} + +// FIXME: the error message makes no sense +static B: &A = B; //~ERROR: access itself + +fn main() {} diff --git a/tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.stderr b/tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.stderr new file mode 100644 index 0000000000000..bd738ad505705 --- /dev/null +++ b/tests/ui/consts/extra-const-ub/pointee-type-with-error-issue-159560.stderr @@ -0,0 +1,16 @@ +error[E0121]: the placeholder `_` is not allowed within types on item signatures for structs + --> $DIR/pointee-type-with-error-issue-159560.rs:4:8 + | +LL | f: _, + | ^ not allowed in type signatures + +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/pointee-type-with-error-issue-159560.rs:8:16 + | +LL | static B: &A = B; + | ^ evaluation of `B` failed here + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0080, E0121. +For more information about an error, try `rustc --explain E0080`. From 3b7025f3a35e74943089a7e2dfe139bf2f821a89 Mon Sep 17 00:00:00 2001 From: landsharkiest Date: Sun, 19 Jul 2026 17:18:52 +0000 Subject: [PATCH 9/9] Add mul_add_relaxed methods for floating-point types --- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics/mod.rs | 12 ++++ library/core/src/num/f128.rs | 32 +++++++++ library/core/src/num/f16.rs | 32 +++++++++ library/core/src/num/f32.rs | 44 ++++++++++++ library/core/src/num/f64.rs | 44 ++++++++++++ tests/ui/intrinsics/float-mul-add-relaxed.rs | 76 ++++++++++++++++++++ 7 files changed, 241 insertions(+) create mode 100644 tests/ui/intrinsics/float-mul-add-relaxed.rs diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 287b790ea6d67..269ec9d5bc62e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -973,6 +973,7 @@ symbols! { fields, file, final_associated_functions, + float_mul_add_relaxed, float_to_int_unchecked, floorf16, floorf32, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index c5da5055e16d9..db74834596852 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -1475,6 +1475,9 @@ pub const fn fmaf128(a: f128, b: f128, c: f128) -> f128; /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f16::mul_add_relaxed`](../../std/primitive.f16.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1491,6 +1494,9 @@ pub const fn fmuladdf16(a: f16, b: f16, c: f16) -> f16 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f32::mul_add_relaxed`](../../std/primitive.f32.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1507,6 +1513,9 @@ pub const fn fmuladdf32(a: f32, b: f32, c: f32) -> f32 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f64::mul_add_relaxed`](../../std/primitive.f64.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1523,6 +1532,9 @@ pub const fn fmuladdf64(a: f64, b: f64, c: f64) -> f64 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f128::mul_add_relaxed`](../../std/primitive.f128.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 4875835695e69..30dc135938e91 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1836,6 +1836,38 @@ impl f128 { intrinsics::fmaf128(self, a, b) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`](Self::mul_add), but the intermediate + /// result may be rounded differently depending on the implementation. + /// The operation is either executed as a single fused multiply-add + /// instruction, or as separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// #![feature(float_mul_add_relaxed)] + /// # #[cfg(any(miri, target_has_reliable_f128_math))] { // Miri uses softfloats, always works + /// + /// let result = 1.0f128.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn mul_add_relaxed(self, a: f128, b: f128) -> f128 { + intrinsics::fmuladdf128(self, a, b) + } + /// Calculates Euclidean division, the matching method for `rem_euclid`. /// /// This computes the integer `n` such that diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 186945db9ae92..6865c41b75d6d 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1822,6 +1822,38 @@ impl f16 { intrinsics::fmaf16(self, a, b) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`](Self::mul_add), but the intermediate + /// result may be rounded differently depending on the implementation. + /// The operation is either executed as a single fused multiply-add + /// instruction, or as separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// #![feature(float_mul_add_relaxed)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// let result = 1.0f16.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn mul_add_relaxed(self, a: f16, b: f16) -> f16 { + intrinsics::fmuladdf16(self, a, b) + } + /// Calculates Euclidean division, the matching method for `rem_euclid`. /// /// This computes the integer `n` such that diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 97bf54ecde47a..2ae8129866098 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1744,6 +1744,50 @@ impl f32 { pub const fn algebraic_rem(self, rhs: f32) -> f32 { intrinsics::frem_algebraic(self, rhs) } + + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`], but the intermediate result may be + /// rounded differently depending on the implementation. The operation is + /// either executed as a single fused multiply-add instruction, or as + /// separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Precision + /// + /// The result of this operation is not guaranteed: it is either the result + /// of [`mul_add`] (one rounding of the infinite-precision result) or of + /// `self * a + b` (two roundings, with an intermediate rounding of the + /// product). + /// + /// # Examples + /// + /// ``` + /// #![feature(float_mul_add_relaxed)] + /// + /// let result = 1.0f32.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 5.2154064e-10 is the fused result (one rounding) + /// // - 9.313226e-10 is the unfused result (two roundings) + /// let r = 0.1_f32.mul_add_relaxed(0.1_f32, -0.01_f32); + /// assert!(r == 5.2154064e-10 || r == 9.313226e-10); + /// ``` + /// + /// [`mul_add`]: ../std/primitive.f32.html#method.mul_add + #[must_use = "method returns a new number and does not mutate the original value"] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[inline] + pub const fn mul_add_relaxed(self, a: f32, b: f32) -> f32 { + intrinsics::fmuladdf32(self, a, b) + } } /// Experimental implementations of floating point functions in `core`. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 15ad61fe3e6e3..73dd4ce4466a3 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1724,6 +1724,50 @@ impl f64 { pub const fn algebraic_rem(self, rhs: f64) -> f64 { intrinsics::frem_algebraic(self, rhs) } + + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`], but the intermediate result may be + /// rounded differently depending on the implementation. The operation is + /// either executed as a single fused multiply-add instruction, or as + /// separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Precision + /// + /// The result of this operation is not guaranteed: it is either the result + /// of [`mul_add`] (one rounding of the infinite-precision result) or of + /// `self * a + b` (two roundings, with an intermediate rounding of the + /// product). + /// + /// # Examples + /// + /// ``` + /// #![feature(float_mul_add_relaxed)] + /// + /// let result = 1.0f64.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 9.020562075079397e-19 is the fused result (one rounding) + /// // - 1.734723475976807e-18 is the unfused result (two roundings) + /// let r = 0.1_f64.mul_add_relaxed(0.1_f64, -0.01_f64); + /// assert!(r == 9.020562075079397e-19 || r == 1.734723475976807e-18); + /// ``` + /// + /// [`mul_add`]: ../std/primitive.f64.html#method.mul_add + #[must_use = "method returns a new number and does not mutate the original value"] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[inline] + pub const fn mul_add_relaxed(self, a: f64, b: f64) -> f64 { + intrinsics::fmuladdf64(self, a, b) + } } #[unstable(feature = "core_float_math", issue = "137578")] diff --git a/tests/ui/intrinsics/float-mul-add-relaxed.rs b/tests/ui/intrinsics/float-mul-add-relaxed.rs new file mode 100644 index 0000000000000..ad222b7be2c6a --- /dev/null +++ b/tests/ui/intrinsics/float-mul-add-relaxed.rs @@ -0,0 +1,76 @@ +//@ run-pass +//@ compile-flags: -O + +// Check that `mul_add_relaxed` returns either the fused result (one rounding) +// or the unfused result (two roundings), including with optimizations enabled +// where the operation may be const-folded or lowered to a fused instruction. + +#![feature(float_mul_add_relaxed)] +#![feature(f16)] +#![feature(f128)] +#![feature(cfg_target_has_reliable_f16_f128)] +// `f16`/`f128` go unused on targets without reliable f16/f128 math, where the +// gated test functions below are compiled out. +#![allow(unused_features)] +// `target_has_reliable_*` are not "known" configs since they are unstable. +#![expect(unexpected_cfgs)] + +use std::hint::black_box; + +fn main() { + test_f32(); + test_f64(); + #[cfg(target_has_reliable_f16_math)] + test_f16(); + #[cfg(target_has_reliable_f128_math)] + test_f128(); +} + +fn test_f32() { + // Exactly representable results are the same whether or not the + // operation is fused. + assert_eq!(black_box(2.0_f32).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f32).mul_add_relaxed(1.0, 1.0), 2.0); + + // `0.1 * 0.1` is inexact, so the fused (one rounding) and unfused (two + // roundings) results differ; either is allowed. + let r = black_box(0.1_f32).mul_add_relaxed(0.1, -0.01); + assert!(r == 5.2154064e-10 || r == 9.313226e-10); + + // Edge cases behave like `a * b + c` regardless of fusion. + assert!(black_box(f32::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f32::INFINITY).mul_add_relaxed(2.0, 1.0), f32::INFINITY); + assert!(black_box(0.0_f32).mul_add_relaxed(f32::INFINITY, 1.0).is_nan()); +} + +fn test_f64() { + assert_eq!(black_box(2.0_f64).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f64).mul_add_relaxed(1.0, 1.0), 2.0); + + let r = black_box(0.1_f64).mul_add_relaxed(0.1, -0.01); + assert!(r == 9.020562075079397e-19 || r == 1.734723475976807e-18); + + assert!(black_box(f64::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f64::INFINITY).mul_add_relaxed(2.0, 1.0), f64::INFINITY); + assert!(black_box(0.0_f64).mul_add_relaxed(f64::INFINITY, 1.0).is_nan()); +} + +#[cfg(target_has_reliable_f16_math)] +fn test_f16() { + assert_eq!(black_box(2.0_f16).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f16).mul_add_relaxed(1.0, 1.0), 2.0); + + assert!(black_box(f16::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f16::INFINITY).mul_add_relaxed(2.0, 1.0), f16::INFINITY); + assert!(black_box(0.0_f16).mul_add_relaxed(f16::INFINITY, 1.0).is_nan()); +} + +#[cfg(target_has_reliable_f128_math)] +fn test_f128() { + assert_eq!(black_box(2.0_f128).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f128).mul_add_relaxed(1.0, 1.0), 2.0); + + assert!(black_box(f128::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f128::INFINITY).mul_add_relaxed(2.0, 1.0), f128::INFINITY); + assert!(black_box(0.0_f128).mul_add_relaxed(f128::INFINITY, 1.0).is_nan()); +}