diff --git a/README.md b/README.md index 66971d5fa7..606e4fc643 100644 --- a/README.md +++ b/README.md @@ -162,15 +162,22 @@ endian-sensitive code. ### Controlling target features Controlling target features works similar to regular rustc invocations: -`RUSTFLAGS="-Ctarget-feature=+avx512f" cargo miri test` runs the tests with AVX512 enabled. (Miri -only supports very few AVX512 intrinsics at the moment.) `-Ctarget-cpu` also works. If target -features are also relevant for doctests, you have to also set `RUSTDOCFLAGS`. +`RUSTFLAGS="-Ctarget-feature=+avx2" cargo miri test` runs the tests with AVX2 enabled. +`-Ctarget-cpu` also works. If target features are also relevant for doctests, you have to also set +`RUSTDOCFLAGS`. Unlike regular rustc, this flag has *two* effects: it builds the code with that target feature available (which affects `cfg(target_feature)`), and it tells Miri to consider the "virtual CPU" that the interpreted program runs on as having the feature available (meaning the code is allowed to invoke the corresponding intrinsics). +By default, Miri will interpret `-Ctarget-feature` and `-Ctarget-cpu` the same way rustc does. +However, not all these target features are actually supported by Miri. You can instruct Miri to +automatically disable unsupported target features by setting +`MIRI_DISABLE_UNSUPPORTED_TARGET_FEATURES=1`. (You have to `cargo clean` when changing this setting +as it affects dependencies as well.) Note that this can disable even some target features that are +usually available by default on the current target, which could cause surprising behavior. + ### Nextest integration Miri can be combined with [`cargo-nextest`](https://nexte.st): diff --git a/cargo-miri/src/setup.rs b/cargo-miri/src/setup.rs index 47c99f9392..320f7cfcdb 100644 --- a/cargo-miri/src/setup.rs +++ b/cargo-miri/src/setup.rs @@ -117,6 +117,8 @@ pub fn setup( // `RUSTC_WRAPPER` to the empty string overwrites `build.rustc-wrapper` set via // `bootstrap.toml`. command.env("RUSTC_WRAPPER", ""); + // Don't let `MIRI_DISABLE_UNSUPPORTED_TARGET_FEATURES` affect the sysroot build. + command.env_remove("MIRI_DISABLE_UNSUPPORTED_TARGET_FEATURES"); if show_setup { // Forward output. Even make it verbose, if requested. diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 19d1f5d6f4..b78bff3b0e 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -216,9 +216,11 @@ impl CodegenBackend for MiriCodegenBackend { } fn target_config(&self, sess: &Session) -> TargetConfig { - let native_target_config = self.native.target_config(sess); + let mut internal_target_features = self.native.target_config(sess).internal_target_features; + miri::remove_unsupported_target_features(sess, &mut internal_target_features); + TargetConfig { - internal_target_features: native_target_config.internal_target_features, + internal_target_features, // The basic types and ABI always work. has_reliable_f16: true, diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 1d88a79273..e7463014e0 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -58,7 +58,10 @@ pub enum TerminationInfo { extra: Option<&'static str>, retag_explain: bool, }, - UnsupportedForeignItem(String), + UnsupportedForeignItem { + msg: String, + help: Option, + }, } pub struct RacingOp { @@ -99,7 +102,7 @@ impl fmt::Display for TerminationInfo { op2.action, op2.thread_info ), - UnsupportedForeignItem(msg) => write!(f, "{msg}"), + UnsupportedForeignItem { msg, .. } => write!(f, "{msg}"), } } } @@ -254,8 +257,9 @@ pub fn report_result<'tcx>( &Exit { code, leak_check } => return Some((code, leak_check)), Abort(_) => Some("abnormal termination"), Interrupted => None, - UnsupportedInIsolation(_) | Int2PtrWithStrictProvenance | UnsupportedForeignItem(_) => - Some("unsupported operation"), + UnsupportedInIsolation(_) + | Int2PtrWithStrictProvenance + | UnsupportedForeignItem { .. } => Some("unsupported operation"), StackedBorrowsUb { .. } | TreeBorrowsUb { .. } | DataRace { .. } => Some("Undefined Behavior"), GenmcMoot => { @@ -301,10 +305,14 @@ pub fn report_result<'tcx>( note!("set `MIRIFLAGS=-Zmiri-disable-isolation` to disable isolation;"), note!("or set `MIRIFLAGS=-Zmiri-isolation-error=warn` to make Miri return an error code from isolated operations (if supported for that operation) and continue with a warning"), ], - UnsupportedForeignItem(_) => { - vec![ + UnsupportedForeignItem { help, .. } => { + let mut helps = vec![ note!("this means the program tried to do something Miri does not support; it does not indicate a bug in the program"), - ] + ]; + if let Some(help) = help { + helps.push(note!("{help}")) + } + helps } StackedBorrowsUb { help, history, .. } => { labels.extend(help.clone()); diff --git a/src/eval.rs b/src/eval.rs index 3ba16971ec..c4206d1fd0 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -170,6 +170,9 @@ pub struct MiriConfig { pub short_fd_operations: bool, /// A list of crates that are considered user-relevant. pub user_relevant_crates: Vec, + /// Whether target features that Miri does not support should be removed from + /// `cfg(target_feature)`. + pub disable_unsupported_target_features: Option, } impl Default for MiriConfig { @@ -212,6 +215,7 @@ impl Default for MiriConfig { float_rounding_error: FloatRoundingErrorMode::Random, short_fd_operations: true, user_relevant_crates: vec![], + disable_unsupported_target_features: None, } } } diff --git a/src/intrinsics/mod.rs b/src/intrinsics/mod.rs index 9a6bdb9b48..2b6ca66f0b 100644 --- a/src/intrinsics/mod.rs +++ b/src/intrinsics/mod.rs @@ -6,10 +6,14 @@ mod math; mod simd; mod x86; +use std::mem; + #[rustfmt::skip] // prevent `use` reordering use rand::RngExt; use rustc_abi::{Endian, Size}; +use rustc_data_structures::unord::UnordSet; use rustc_middle::{mir, ty}; +use rustc_session::Session; use rustc_span::{Symbol, sym}; use rustc_target::spec::Arch; @@ -34,6 +38,39 @@ where ) } +fn should_remove_unsupported_target_features() -> bool { + // We cannot use `MiriConfig` to decide this as that is not available in dependencies. + std::env::var("MIRI_DISABLE_UNSUPPORTED_TARGET_FEATURES").is_ok_and(|s| s == "1") +} + +pub fn remove_unsupported_target_features(sess: &Session, features: &mut UnordSet) { + if !should_remove_unsupported_target_features() { + return; + } + + // While an allowlist may feel more natural here, we don't have a very good idea of what exactly + // we do support. So instead we list things that we know we don't support. + let is_unsupported = match sess.target.arch { + Arch::X86_64 | Arch::X86 => + |feature: Symbol| { + // AVX512 is only partially supported. + feature.as_str().starts_with("avx512") + }, + Arch::AArch64 => + |feature| { + // We don't fully support `neon` but we cannot remove that as it is ABI-required. + matches!(feature, sym::aes) + }, + // Fallback: we don't know so we just don't touch anything. + _ => |_feature| true, + }; + + // Apply the filter. This is currently annoying to do because there's no `retain` on `UnordSet`. + let mut features_vec = mem::take(features).into_sorted_stable_ord(); + features_vec.retain(|f| !is_unsupported(*f)); + *features = UnordSet::from_iter(features_vec); +} + impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn call_intrinsic( @@ -245,12 +282,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { EmulateItemResult::NeedsReturn }; - // The rest either implements the logic, or falls back to `lookup_exported_symbol`. + // The rest either implements the logic, or emits an error. These are not real symbols so we + // do not invoke `lookup_exported_symbol`. res.jump_to_next_block(this, &dest.clone().into(), ret, None, |this| { - throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(format!( - "can't call LLVM intrinsic `{link_name}` on architecture `{arch}`", - arch = this.tcx.sess.target.arch, - ))); + // Only show a hint on targets where we have decent support. + let help = if matches!(this.tcx.sess.target.arch, Arch::X86_64 | Arch::X86 | Arch::AArch64) { + Some(if should_remove_unsupported_target_features() { + format!("This likely means that Miri did not remove enough target features from `cfg(target_feature)`; \ + please file an issue at .") + } else { + format!("Try setting `MIRI_DISABLE_UNSUPPORTED_TARGET_FEATURES=1` (needs full rebuild via `cargo clean`) \ + to steer this code towards a fallback path that may work in Miri.") + }) + } else { None }; + throw_machine_stop!(TerminationInfo::UnsupportedForeignItem { + msg: format!( + "can't call LLVM intrinsic `{link_name}` on architecture `{arch}`", + arch = this.tcx.sess.target.arch, + ), + help + }); }) } } diff --git a/src/lib.rs b/src/lib.rs index 4cd7475f62..566d2f1bac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ // Configure clippy and other lints #![allow( clippy::collapsible_if, + clippy::collapsible_match, clippy::enum_variant_names, clippy::manual_map, clippy::neg_cmp_op_on_partial_ord, @@ -156,7 +157,7 @@ pub use crate::diagnostics::{ }; pub use crate::eval::{MiriConfig, MiriEntryFnType, create_ecx, entry_fn, eval_entry}; pub use crate::helpers::{EvalContextExt as _, ToU64 as _, ToUsize as _}; -pub use crate::intrinsics::EvalContextExt as _; +pub use crate::intrinsics::{EvalContextExt as _, remove_unsupported_target_features}; pub use crate::machine::{ AlignmentCheck, AllocExtra, BacktraceStyle, DynMachineCallback, FloatRoundingErrorMode, FrameExtra, IsolatedOp, MachineCallback, MemoryKind, MiriInterpCx, MiriInterpCxExt, diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index aebe0a4d2c..d222c267c6 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -79,10 +79,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(Some(body)); } - throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(format!( - "can't call foreign function `{link_name}` on OS `{os}`", - os = this.tcx.sess.target.os, - ))); + throw_machine_stop!(TerminationInfo::UnsupportedForeignItem { + msg: format!( + "can't call foreign function `{link_name}` on OS `{os}`", + os = this.tcx.sess.target.os, + ), + help: None + }); }) } diff --git a/tests/pass/no-unsupported-target-features.rs b/tests/pass/no-unsupported-target-features.rs new file mode 100644 index 0000000000..7d5615e39d --- /dev/null +++ b/tests/pass/no-unsupported-target-features.rs @@ -0,0 +1,9 @@ +//@only-target: aarch64 +//@rustc-env: MIRI_DISABLE_UNSUPPORTED_TARGET_FEATURES=1 + +use std::arch::is_aarch64_feature_detected; + +fn main() { + assert!(!cfg!(target_feature = "aes")); + assert!(!is_aarch64_feature_detected!("aes")); +}