diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs index 34effd199f152..246643d807470 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs @@ -70,6 +70,7 @@ use rustc_hir as hir; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::outlives::env::OutlivesEnvironment; +use rustc_infer::traits::ObligationCause; use rustc_infer::traits::specialization_graph::Node; use rustc_middle::ty::trait_def::TraitSpecializationKind; use rustc_middle::ty::{ @@ -210,13 +211,7 @@ fn get_impl_args( impl1_def_id.to_def_id(), impl1_args, impl2_node, - |_, span| { - traits::ObligationCause::new( - impl1_span, - impl1_def_id, - traits::ObligationCauseCode::WhereClause(impl2_node.def_id(), span), - ) - }, + &ObligationCause::misc(impl1_span, impl1_def_id), ); let errors = ocx.select_all_or_error(); diff --git a/compiler/rustc_trait_selection/src/traits/effects.rs b/compiler/rustc_trait_selection/src/traits/effects.rs index cb36f1a62dbfd..27d3ec160cab1 100644 --- a/compiler/rustc_trait_selection/src/traits/effects.rs +++ b/compiler/rustc_trait_selection/src/traits/effects.rs @@ -27,6 +27,11 @@ pub fn evaluate_host_effect_obligation<'tcx>( ); } + // Force ambiguity for infer self ty. + if obligation.predicate.self_ty().is_ty_var() { + return Err(EvaluationFailure::Ambiguous); + } + match evaluate_host_effect_from_bounds(selcx, obligation) { Ok(result) => return Ok(result), Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous), diff --git a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs index 5bf3dbcbc3273..a9cd705465e3a 100644 --- a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs @@ -15,24 +15,24 @@ use rustc_data_structures::fx::FxIndexSet; use rustc_errors::codes::*; use rustc_errors::{Diag, EmissionGuarantee}; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_infer::infer::DefineOpaqueTypes; use rustc_middle::bug; use rustc_middle::query::LocalCrate; use rustc_middle::ty::print::PrintTraitRefExt as _; -use rustc_middle::ty::{ - self, GenericArgsRef, ImplSubject, Ty, TyCtxt, TypeVisitableExt, TypingMode, -}; +use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, TypingMode}; use rustc_session::lint::builtin::{COHERENCE_LEAK_CHECK, ORDER_DEPENDENT_TRAIT_OBJECTS}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, sym}; +use rustc_type_ir::solve::NoSolution; use specialization_graph::GraphExt; use tracing::{debug, instrument}; -use super::{SelectionContext, util}; use crate::error_reporting::traits::to_pretty_impl_header; use crate::errors::NegativePositiveConflict; -use crate::infer::{InferCtxt, InferOk, TyCtxtInferExt}; +use crate::infer::{InferCtxt, TyCtxtInferExt}; use crate::traits::select::IntercrateAmbiguityCause; -use crate::traits::{FutureCompatOverlapErrorKind, ObligationCause, ObligationCtxt, coherence}; +use crate::traits::{ + FutureCompatOverlapErrorKind, ObligationCause, ObligationCtxt, coherence, + predicates_for_generics, +}; /// Information pertinent to an overlapping impl error. #[derive(Debug)] @@ -87,9 +87,14 @@ pub fn translate_args<'tcx>( source_args: GenericArgsRef<'tcx>, target_node: specialization_graph::Node, ) -> GenericArgsRef<'tcx> { - translate_args_with_cause(infcx, param_env, source_impl, source_args, target_node, |_, _| { - ObligationCause::dummy() - }) + translate_args_with_cause( + infcx, + param_env, + source_impl, + source_args, + target_node, + &ObligationCause::dummy(), + ) } /// Like [translate_args], but obligations from the parent implementation @@ -104,7 +109,7 @@ pub fn translate_args_with_cause<'tcx>( source_impl: DefId, source_args: GenericArgsRef<'tcx>, target_node: specialization_graph::Node, - cause: impl Fn(usize, Span) -> ObligationCause<'tcx>, + cause: &ObligationCause<'tcx>, ) -> GenericArgsRef<'tcx> { debug!( "translate_args({:?}, {:?}, {:?}, {:?})", @@ -123,7 +128,7 @@ pub fn translate_args_with_cause<'tcx>( } fulfill_implication(infcx, param_env, source_trait_ref, source_impl, target_impl, cause) - .unwrap_or_else(|()| { + .unwrap_or_else(|_| { bug!( "When translating generic parameters from {source_impl:?} to \ {target_impl:?}, the expected specialization failed to hold" @@ -137,6 +142,84 @@ pub fn translate_args_with_cause<'tcx>( source_args.rebase_onto(infcx.tcx, source_impl, target_args) } +/// Attempt to fulfill all obligations of `target_impl` after unification with +/// `source_trait_ref`. If successful, returns the generic parameters for *all* the +/// generics of `target_impl`, including both those needed to unify with +/// `source_trait_ref` and those whose identity is determined via a where +/// clause in the impl. +fn fulfill_implication<'tcx>( + infcx: &InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + source_trait_ref: ty::TraitRef<'tcx>, + source_impl: DefId, + target_impl: DefId, + cause: &ObligationCause<'tcx>, +) -> Result, NoSolution> { + debug!( + "fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)", + param_env, source_trait_ref, target_impl + ); + + let ocx = ObligationCtxt::new(infcx); + let source_trait_ref = ocx.normalize(cause, param_env, source_trait_ref); + + if !ocx.select_all_or_error().is_empty() { + infcx.dcx().span_delayed_bug( + infcx.tcx.def_span(source_impl), + format!("failed to fully normalize {source_trait_ref}"), + ); + return Err(NoSolution); + } + + let target_args = infcx.fresh_args_for_item(DUMMY_SP, target_impl); + let target_trait_ref = ocx.normalize( + cause, + param_env, + infcx + .tcx + .impl_trait_ref(target_impl) + .expect("expected source impl to be a trait impl") + .instantiate(infcx.tcx, target_args), + ); + + // do the impls unify? If not, no specialization. + ocx.eq(cause, param_env, source_trait_ref, target_trait_ref)?; + + // Now check that the source trait ref satisfies all the where clauses of the target impl. + // This is not just for correctness; we also need this to constrain any params that may + // only be referenced via projection predicates. + let predicates = ocx.normalize( + cause, + param_env, + infcx.tcx.predicates_of(target_impl).instantiate(infcx.tcx, target_args), + ); + let obligations = predicates_for_generics(|_, _| cause.clone(), param_env, predicates); + ocx.register_obligations(obligations); + + let errors = ocx.select_all_or_error(); + if !errors.is_empty() { + // no dice! + debug!( + "fulfill_implication: for impls on {:?} and {:?}, \ + could not fulfill: {:?} given {:?}", + source_trait_ref, + target_trait_ref, + errors, + param_env.caller_bounds() + ); + return Err(NoSolution); + } + + debug!( + "fulfill_implication: an impl for {:?} specializes {:?}", + source_trait_ref, target_trait_ref + ); + + // Now resolve the *generic parameters* we built for the target earlier, replacing + // the inference variables inside with whatever we got from fulfillment. + Ok(infcx.resolve_vars_if_possible(target_args)) +} + pub(super) fn specialization_enabled_in(tcx: TyCtxt<'_>, _: LocalCrate) -> bool { tcx.features().specialization() || tcx.features().min_specialization() } @@ -182,8 +265,9 @@ pub(super) fn specializes(tcx: TyCtxt<'_>, (impl1_def_id, impl2_def_id): (DefId, return false; } - // create a parameter environment corresponding to a (placeholder) instantiation of impl1 - let penv = tcx.param_env(impl1_def_id); + // create a parameter environment corresponding to an identity instantiation of impl1, + // i.e. the most generic instantiation of impl1. + let param_env = tcx.param_env(impl1_def_id); // Create an infcx, taking the predicates of impl1 as assumptions: let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); @@ -191,90 +275,15 @@ pub(super) fn specializes(tcx: TyCtxt<'_>, (impl1_def_id, impl2_def_id): (DefId, // Attempt to prove that impl2 applies, given all of the above. fulfill_implication( &infcx, - penv, + param_env, impl1_trait_header.trait_ref.instantiate_identity(), impl1_def_id, impl2_def_id, - |_, _| ObligationCause::dummy(), + &ObligationCause::dummy(), ) .is_ok() } -/// Attempt to fulfill all obligations of `target_impl` after unification with -/// `source_trait_ref`. If successful, returns the generic parameters for *all* the -/// generics of `target_impl`, including both those needed to unify with -/// `source_trait_ref` and those whose identity is determined via a where -/// clause in the impl. -fn fulfill_implication<'tcx>( - infcx: &InferCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - source_trait_ref: ty::TraitRef<'tcx>, - source_impl: DefId, - target_impl: DefId, - error_cause: impl Fn(usize, Span) -> ObligationCause<'tcx>, -) -> Result, ()> { - debug!( - "fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)", - param_env, source_trait_ref, target_impl - ); - - let ocx = ObligationCtxt::new(infcx); - let source_trait_ref = ocx.normalize(&ObligationCause::dummy(), param_env, source_trait_ref); - - if !ocx.select_all_or_error().is_empty() { - infcx.dcx().span_delayed_bug( - infcx.tcx.def_span(source_impl), - format!("failed to fully normalize {source_trait_ref}"), - ); - } - - let source_trait_ref = infcx.resolve_vars_if_possible(source_trait_ref); - let source_trait = ImplSubject::Trait(source_trait_ref); - - let selcx = SelectionContext::new(infcx); - let target_args = infcx.fresh_args_for_item(DUMMY_SP, target_impl); - let (target_trait, obligations) = - util::impl_subject_and_oblig(&selcx, param_env, target_impl, target_args, error_cause); - - // do the impls unify? If not, no specialization. - let Ok(InferOk { obligations: more_obligations, .. }) = infcx - .at(&ObligationCause::dummy(), param_env) - // Ok to use `Yes`, as all the generic params are already replaced by inference variables, - // which will match the opaque type no matter if it is defining or not. - // Any concrete type that would match the opaque would already be handled by coherence rules, - // and thus either be ok to match here and already have errored, or it won't match, in which - // case there is no issue anyway. - .eq(DefineOpaqueTypes::Yes, source_trait, target_trait) - else { - debug!("fulfill_implication: {:?} does not unify with {:?}", source_trait, target_trait); - return Err(()); - }; - - // attempt to prove all of the predicates for impl2 given those for impl1 - // (which are packed up in penv) - ocx.register_obligations(obligations.chain(more_obligations)); - - let errors = ocx.select_all_or_error(); - if !errors.is_empty() { - // no dice! - debug!( - "fulfill_implication: for impls on {:?} and {:?}, \ - could not fulfill: {:?} given {:?}", - source_trait, - target_trait, - errors, - param_env.caller_bounds() - ); - return Err(()); - } - - debug!("fulfill_implication: an impl for {:?} specializes {:?}", source_trait, target_trait); - - // Now resolve the *generic parameters* we built for the target earlier, replacing - // the inference variables inside with whatever we got from fulfillment. - Ok(infcx.resolve_vars_if_possible(target_args)) -} - /// Query provider for `specialization_graph_of`. pub(super) fn specialization_graph_provider( tcx: TyCtxt<'_>, diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs index b7a2f20b769d2..da1045b664afc 100644 --- a/compiler/rustc_trait_selection/src/traits/util.rs +++ b/compiler/rustc_trait_selection/src/traits/util.rs @@ -3,19 +3,16 @@ use std::collections::BTreeMap; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Diag; use rustc_hir::def_id::DefId; -use rustc_infer::infer::{InferCtxt, InferOk}; +use rustc_infer::infer::InferCtxt; pub use rustc_infer::traits::util::*; use rustc_middle::bug; use rustc_middle::ty::{ - self, GenericArgsRef, ImplSubject, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, - TypeVisitableExt, Upcast, + self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, Upcast, }; use rustc_span::Span; use smallvec::{SmallVec, smallvec}; use tracing::debug; -use super::{NormalizeExt, ObligationCause, PredicateObligation, SelectionContext}; - /////////////////////////////////////////////////////////////////////////// // `TraitAliasExpander` iterator /////////////////////////////////////////////////////////////////////////// @@ -166,34 +163,6 @@ impl<'tcx> Iterator for TraitAliasExpander<'tcx> { // Other /////////////////////////////////////////////////////////////////////////// -/// Instantiate all bound parameters of the impl subject with the given args, -/// returning the resulting subject and all obligations that arise. -/// The obligations are closed under normalization. -pub(crate) fn impl_subject_and_oblig<'a, 'tcx>( - selcx: &SelectionContext<'a, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - impl_def_id: DefId, - impl_args: GenericArgsRef<'tcx>, - cause: impl Fn(usize, Span) -> ObligationCause<'tcx>, -) -> (ImplSubject<'tcx>, impl Iterator>) { - let subject = selcx.tcx().impl_subject(impl_def_id); - let subject = subject.instantiate(selcx.tcx(), impl_args); - - let InferOk { value: subject, obligations: normalization_obligations1 } = - selcx.infcx.at(&ObligationCause::dummy(), param_env).normalize(subject); - - let predicates = selcx.tcx().predicates_of(impl_def_id); - let predicates = predicates.instantiate(selcx.tcx(), impl_args); - let InferOk { value: predicates, obligations: normalization_obligations2 } = - selcx.infcx.at(&ObligationCause::dummy(), param_env).normalize(predicates); - let impl_obligations = super::predicates_for_generics(cause, param_env, predicates); - - let impl_obligations = - impl_obligations.chain(normalization_obligations1).chain(normalization_obligations2); - - (subject, impl_obligations) -} - /// Casts a trait reference into a reference to one of its super /// traits; returns `None` if `target_trait_def_id` is not a /// supertrait. diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs index 4764d7f0b0fe0..71dcab3423c13 100644 --- a/library/core/src/array/mod.rs +++ b/library/core/src/array/mod.rs @@ -10,11 +10,13 @@ use crate::convert::Infallible; use crate::error::Error; use crate::fmt; use crate::hash::{self, Hash}; +use crate::intrinsics::transmute_unchecked; use crate::iter::{UncheckedIterator, repeat_n}; use crate::mem::{self, MaybeUninit}; use crate::ops::{ ChangeOutputType, ControlFlow, FromResidual, Index, IndexMut, NeverShortCircuit, Residual, Try, }; +use crate::ptr::{null, null_mut}; use crate::slice::{Iter, IterMut}; mod ascii; @@ -606,8 +608,20 @@ impl [T; N] { /// assert_eq!(strings.len(), 3); /// ``` #[stable(feature = "array_methods", since = "1.77.0")] - pub fn each_ref(&self) -> [&T; N] { - from_trusted_iterator(self.iter()) + #[rustc_const_unstable(feature = "const_array_each_ref", issue = "133289")] + pub const fn each_ref(&self) -> [&T; N] { + let mut buf = [null::(); N]; + + // FIXME(const-hack): We would like to simply use iterators for this (as in the original implementation), but this is not allowed in constant expressions. + let mut i = 0; + while i < N { + buf[i] = &raw const self[i]; + + i += 1; + } + + // SAFETY: `*const T` has the same layout as `&T`, and we've also initialised each pointer as a valid reference. + unsafe { transmute_unchecked(buf) } } /// Borrows each element mutably and returns an array of mutable references @@ -625,8 +639,20 @@ impl [T; N] { /// assert_eq!(floats, [0.0, 2.7, -1.0]); /// ``` #[stable(feature = "array_methods", since = "1.77.0")] - pub fn each_mut(&mut self) -> [&mut T; N] { - from_trusted_iterator(self.iter_mut()) + #[rustc_const_unstable(feature = "const_array_each_ref", issue = "133289")] + pub const fn each_mut(&mut self) -> [&mut T; N] { + let mut buf = [null_mut::(); N]; + + // FIXME(const-hack): We would like to simply use iterators for this (as in the original implementation), but this is not allowed in constant expressions. + let mut i = 0; + while i < N { + buf[i] = &raw mut self[i]; + + i += 1; + } + + // SAFETY: `*mut T` has the same layout as `&mut T`, and we've also initialised each pointer as a valid reference. + unsafe { transmute_unchecked(buf) } } /// Divides one array reference into two at an index. diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 5b94f036248cb..9c4e6f296fed5 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -658,6 +658,8 @@ pub mod arch { pub use std_detect::is_aarch64_feature_detected; #[unstable(feature = "stdarch_arm_feature_detection", issue = "111190")] pub use std_detect::is_arm_feature_detected; + #[unstable(feature = "is_loongarch_feature_detected", issue = "117425")] + pub use std_detect::is_loongarch_feature_detected; #[unstable(feature = "is_riscv_feature_detected", issue = "111192")] pub use std_detect::is_riscv_feature_detected; #[stable(feature = "simd_x86", since = "1.27.0")] diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 2d087c03b04b4..388b8a88a1a48 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -173,16 +173,17 @@ impl Drop for OwnedFd { #[inline] fn drop(&mut self) { unsafe { - // Note that errors are ignored when closing a file descriptor. The - // reason for this is that if an error occurs we don't actually know if - // the file descriptor was closed or not, and if we retried (for - // something like EINTR), we might close another valid file descriptor - // opened after we closed ours. - // However, this is usually justified, as some of the major Unices - // do make sure to always close the FD, even when `close()` is interrupted, - // and the scenario is rare to begin with. - // Helpful link to an epic discussion by POSIX workgroup: - // http://austingroupbugs.net/view.php?id=529 + // Note that errors are ignored when closing a file descriptor. According to POSIX 2024, + // we can and indeed should retry `close` on `EINTR` + // (https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/close.html), + // but it is not clear yet how well widely-used implementations are conforming with this + // mandate since older versions of POSIX left the state of the FD after an `EINTR` + // unspecified. Ignoring errors is "fine" because some of the major Unices (in + // particular, Linux) do make sure to always close the FD, even when `close()` is + // interrupted, and the scenario is rare to begin with. If we retried on a + // not-POSIX-compliant implementation, the consequences could be really bad since we may + // close the wrong FD. Helpful link to an epic discussion by POSIX workgroup that led to + // the latest POSIX wording: http://austingroupbugs.net/view.php?id=529 #[cfg(not(target_os = "hermit"))] { #[cfg(unix)] diff --git a/library/std/src/sys/random/arc4random.rs b/library/std/src/sys/random/arc4random.rs index ffabaafbee803..32467e9ebaa64 100644 --- a/library/std/src/sys/random/arc4random.rs +++ b/library/std/src/sys/random/arc4random.rs @@ -12,7 +12,6 @@ #[cfg(not(any( target_os = "haiku", target_os = "illumos", - target_os = "rtems", target_os = "solaris", target_os = "vita", )))] @@ -22,7 +21,6 @@ use libc::arc4random_buf; #[cfg(any( target_os = "haiku", // See https://git.haiku-os.org/haiku/tree/headers/compatibility/bsd/stdlib.h target_os = "illumos", // See https://www.illumos.org/man/3C/arc4random - target_os = "rtems", // See https://docs.rtems.org/branches/master/bsp-howto/getentropy.html target_os = "solaris", // See https://docs.oracle.com/cd/E88353_01/html/E37843/arc4random-3c.html target_os = "vita", // See https://github.com/vitasdk/newlib/blob/b89e5bc183b516945f9ee07eef483ecb916e45ff/newlib/libc/include/stdlib.h#L74 ))] diff --git a/library/std/src/thread/scoped.rs b/library/std/src/thread/scoped.rs index b2305b1eda7e1..a14446b35155a 100644 --- a/library/std/src/thread/scoped.rs +++ b/library/std/src/thread/scoped.rs @@ -176,7 +176,7 @@ impl<'scope, 'env> Scope<'scope, 'env> { /// thread. If the spawned thread panics, [`join`] will return an [`Err`] containing /// the panic payload. /// - /// If the join handle is dropped, the spawned thread will implicitly joined at the + /// If the join handle is dropped, the spawned thread will be implicitly joined at the /// end of the scope. In that case, if the spawned thread panics, [`scope`] will /// panic after all threads are joined. /// diff --git a/src/tools/miri/.cargo/config.toml b/src/tools/miri/.cargo/config.toml deleted file mode 100644 index 42e7c2c48189a..0000000000000 --- a/src/tools/miri/.cargo/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -[unstable] -profile-rustflags = true - -# Add back the containing directory of the packages we have to refer to using --manifest-path. -# Per-package profiles avoid adding this to build dependencies. -[profile.dev.package."cargo-miri"] -rustflags = ["--remap-path-prefix", "=cargo-miri"] -[profile.dev.package."miri-script"] -rustflags = ["--remap-path-prefix", "=miri-script"] diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index a73fefaaf3499..9a683ae68fd71 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -548,6 +548,7 @@ Definite bugs found: * [Incorrect offset computation for highly-aligned types in `portable-atomic-util`](https://github.com/taiki-e/portable-atomic/pull/138) * [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084)) * [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281) +* [A bug in the new `RwLock::downgrade` implementation](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test) (caught by Miri before it landed in the Rust repo) Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment): diff --git a/src/tools/miri/miri b/src/tools/miri/miri index 5d07ad9e249d6..ac1a7211c4ef8 100755 --- a/src/tools/miri/miri +++ b/src/tools/miri/miri @@ -1,13 +1,15 @@ #!/usr/bin/env bash set -e # We want to call the binary directly, so we need to know where it ends up. -MIRI_SCRIPT_TARGET_DIR="$(dirname "$0")"/miri-script/target +ROOT_DIR="$(dirname "$0")" +MIRI_SCRIPT_TARGET_DIR="$ROOT_DIR"/miri-script/target # If stdout is not a terminal and we are not on CI, assume that we are being invoked by RA, and use JSON output. if ! [ -t 1 ] && [ -z "$CI" ]; then MESSAGE_FORMAT="--message-format=json" fi -# We need a nightly toolchain, for the `profile-rustflags` cargo feature. -cargo +nightly build $CARGO_EXTRA_FLAGS --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml \ +# We need a nightly toolchain, for `-Zroot-dir`. +cargo +nightly build $CARGO_EXTRA_FLAGS --manifest-path "$ROOT_DIR"/miri-script/Cargo.toml \ + -Zroot-dir="$ROOT_DIR" \ -q --target-dir "$MIRI_SCRIPT_TARGET_DIR" $MESSAGE_FORMAT || \ ( echo "Failed to build miri-script. Is the 'nightly' toolchain installed?"; exit 1 ) # Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs index e6e85747d4d5f..cf6529d8373ed 100644 --- a/src/tools/miri/miri-script/src/util.rs +++ b/src/tools/miri/miri-script/src/util.rs @@ -105,7 +105,7 @@ impl MiriEnv { // Get extra flags for cargo. let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default(); - let cargo_extra_flags = flagsplit(&cargo_extra_flags); + let mut cargo_extra_flags = flagsplit(&cargo_extra_flags); if cargo_extra_flags.iter().any(|a| a == "--release" || a.starts_with("--profile")) { // This makes binaries end up in different paths, let's not do that. eprintln!( @@ -113,6 +113,8 @@ impl MiriEnv { ); std::process::exit(1); } + // Also set `-Zroot-dir` for cargo, to print diagnostics relative to the miri dir. + cargo_extra_flags.push(format!("-Zroot-dir={}", miri_dir.display())); Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags, libdir }) } diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index bec28af6257fa..effed0cd180d1 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -668959740f97e7a22ae340742886d330ab63950f +2d0ea7956c45de6e421fd579e2ded27be405dec6 diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index 02e8261a6ed90..ef4034cc0c11e 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::collections::hash_map::Entry; +use std::default::Default; use std::ops::Not; use std::rc::Rc; use std::time::Duration; @@ -46,8 +47,6 @@ macro_rules! declare_id { } pub(super) use declare_id; -declare_id!(MutexId); - /// The mutex state. #[derive(Default, Debug)] struct Mutex { @@ -61,6 +60,21 @@ struct Mutex { clock: VClock, } +#[derive(Default, Clone, Debug)] +pub struct MutexRef(Rc>); + +impl MutexRef { + fn new() -> Self { + MutexRef(Rc::new(RefCell::new(Mutex::default()))) + } +} + +impl VisitProvenance for MutexRef { + fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { + // Mutex contains no provenance. + } +} + declare_id!(RwLockId); /// The read-write lock state. @@ -144,7 +158,6 @@ struct FutexWaiter { /// The state of all synchronization objects. #[derive(Default, Debug)] pub struct SynchronizationObjects { - mutexes: IndexVec, rwlocks: IndexVec, condvars: IndexVec, pub(super) init_onces: IndexVec, @@ -155,17 +168,17 @@ impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { fn condvar_reacquire_mutex( &mut self, - mutex: MutexId, + mutex_ref: &MutexRef, retval: Scalar, dest: MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - if this.mutex_is_locked(mutex) { - assert_ne!(this.mutex_get_owner(mutex), this.active_thread()); - this.mutex_enqueue_and_block(mutex, Some((retval, dest))); + if this.mutex_is_locked(mutex_ref) { + assert_ne!(this.mutex_get_owner(mutex_ref), this.active_thread()); + this.mutex_enqueue_and_block(mutex_ref, Some((retval, dest))); } else { // We can have it right now! - this.mutex_lock(mutex); + this.mutex_lock(mutex_ref); // Don't forget to write the return value. this.write_scalar(retval, &dest)?; } @@ -174,10 +187,9 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } impl SynchronizationObjects { - pub fn mutex_create(&mut self) -> MutexId { - self.mutexes.push(Default::default()) + pub fn mutex_create(&mut self) -> MutexRef { + MutexRef::new() } - pub fn rwlock_create(&mut self) -> RwLockId { self.rwlocks.push(Default::default()) } @@ -209,12 +221,16 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Helper for lazily initialized `alloc_extra.sync` data: /// this forces an immediate init. - fn lazy_sync_init( - &mut self, + /// Return a reference to the data in the machine state. + fn lazy_sync_init<'a, T: 'static>( + &'a mut self, primitive: &MPlaceTy<'tcx>, init_offset: Size, data: T, - ) -> InterpResult<'tcx> { + ) -> InterpResult<'tcx, &'a T> + where + 'tcx: 'a, + { let this = self.eval_context_mut(); let (alloc, offset, _) = this.ptr_get_alloc_id(primitive.ptr(), 0)?; @@ -227,7 +243,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &init_field, AtomicWriteOrd::Relaxed, )?; - interp_ok(()) + interp_ok(this.get_alloc_extra(alloc)?.get_sync::(offset).unwrap()) } /// Helper for lazily initialized `alloc_extra.sync` data: @@ -235,13 +251,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// - If yes, fetches the data from `alloc_extra.sync`, or calls `missing_data` if that fails /// and stores that in `alloc_extra.sync`. /// - Otherwise, calls `new_data` to initialize the primitive. - fn lazy_sync_get_data( - &mut self, + /// + /// Return a reference to the data in the machine state. + fn lazy_sync_get_data<'a, T: 'static>( + &'a mut self, primitive: &MPlaceTy<'tcx>, init_offset: Size, missing_data: impl FnOnce() -> InterpResult<'tcx, T>, new_data: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, T>, - ) -> InterpResult<'tcx, T> { + ) -> InterpResult<'tcx, &'a T> + where + 'tcx: 'a, + { let this = self.eval_context_mut(); // Check if this is already initialized. Needs to be atomic because we can race with another @@ -265,17 +286,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // or else it has been moved illegally. let (alloc, offset, _) = this.ptr_get_alloc_id(primitive.ptr(), 0)?; let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?; - if let Some(data) = alloc_extra.get_sync::(offset) { - interp_ok(*data) - } else { + // Due to borrow checker reasons, we have to do the lookup twice. + if alloc_extra.get_sync::(offset).is_none() { let data = missing_data()?; alloc_extra.sync.insert(offset, Box::new(data)); - interp_ok(data) } + interp_ok(alloc_extra.get_sync::(offset).unwrap()) } else { let data = new_data(this)?; - this.lazy_sync_init(primitive, init_offset, data)?; - interp_ok(data) + this.lazy_sync_init(primitive, init_offset, data) } } @@ -311,23 +330,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { #[inline] /// Get the id of the thread that currently owns this lock. - fn mutex_get_owner(&mut self, id: MutexId) -> ThreadId { - let this = self.eval_context_ref(); - this.machine.sync.mutexes[id].owner.unwrap() + fn mutex_get_owner(&self, mutex_ref: &MutexRef) -> ThreadId { + mutex_ref.0.borrow().owner.unwrap() } #[inline] /// Check if locked. - fn mutex_is_locked(&self, id: MutexId) -> bool { - let this = self.eval_context_ref(); - this.machine.sync.mutexes[id].owner.is_some() + fn mutex_is_locked(&self, mutex_ref: &MutexRef) -> bool { + mutex_ref.0.borrow().owner.is_some() } /// Lock by setting the mutex owner and increasing the lock count. - fn mutex_lock(&mut self, id: MutexId) { + fn mutex_lock(&mut self, mutex_ref: &MutexRef) { let this = self.eval_context_mut(); let thread = this.active_thread(); - let mutex = &mut this.machine.sync.mutexes[id]; + let mut mutex = mutex_ref.0.borrow_mut(); if let Some(current_owner) = mutex.owner { assert_eq!(thread, current_owner, "mutex already locked by another thread"); assert!( @@ -347,9 +364,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// count. If the lock count reaches 0, release the lock and potentially /// give to a new owner. If the lock was not locked by the current thread, /// return `None`. - fn mutex_unlock(&mut self, id: MutexId) -> InterpResult<'tcx, Option> { + fn mutex_unlock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); - let mutex = &mut this.machine.sync.mutexes[id]; + let mut mutex = mutex_ref.0.borrow_mut(); interp_ok(if let Some(current_owner) = mutex.owner { // Mutex is locked. if current_owner != this.machine.threads.active_thread() { @@ -367,8 +384,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { mutex.clock.clone_from(clock) }); } - if let Some(thread) = this.machine.sync.mutexes[id].queue.pop_front() { - this.unblock_thread(thread, BlockReason::Mutex(id))?; + let thread_id = mutex.queue.pop_front(); + // We need to drop our mutex borrow before unblock_thread + // because it will be borrowed again in the unblock callback. + drop(mutex); + if thread_id.is_some() { + this.unblock_thread(thread_id.unwrap(), BlockReason::Mutex)?; } } Some(old_lock_count) @@ -385,24 +406,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { #[inline] fn mutex_enqueue_and_block( &mut self, - id: MutexId, + mutex_ref: &MutexRef, retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>, ) { let this = self.eval_context_mut(); - assert!(this.mutex_is_locked(id), "queing on unlocked mutex"); + assert!(this.mutex_is_locked(mutex_ref), "queuing on unlocked mutex"); let thread = this.active_thread(); - this.machine.sync.mutexes[id].queue.push_back(thread); + mutex_ref.0.borrow_mut().queue.push_back(thread); + let mutex_ref = mutex_ref.clone(); this.block_thread( - BlockReason::Mutex(id), + BlockReason::Mutex, None, callback!( @capture<'tcx> { - id: MutexId, + mutex_ref: MutexRef, retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>, } @unblock = |this| { - assert!(!this.mutex_is_locked(id)); - this.mutex_lock(id); + assert!(!this.mutex_is_locked(&mutex_ref)); + this.mutex_lock(&mutex_ref); if let Some((retval, dest)) = retval_dest { this.write_scalar(retval, &dest)?; @@ -623,14 +645,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn condvar_wait( &mut self, condvar: CondvarId, - mutex: MutexId, + mutex_ref: MutexRef, timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>, retval_succ: Scalar, retval_timeout: Scalar, dest: MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - if let Some(old_locked_count) = this.mutex_unlock(mutex)? { + if let Some(old_locked_count) = this.mutex_unlock(&mutex_ref)? { if old_locked_count != 1 { throw_unsup_format!( "awaiting a condvar on a mutex acquired multiple times is not supported" @@ -650,7 +672,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { callback!( @capture<'tcx> { condvar: CondvarId, - mutex: MutexId, + mutex_ref: MutexRef, retval_succ: Scalar, retval_timeout: Scalar, dest: MPlaceTy<'tcx>, @@ -665,7 +687,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Try to acquire the mutex. // The timeout only applies to the first wait (until the signal), not for mutex acquisition. - this.condvar_reacquire_mutex(mutex, retval_succ, dest) + this.condvar_reacquire_mutex(&mutex_ref, retval_succ, dest) } @timeout = |this| { // We have to remove the waiter from the queue again. @@ -673,7 +695,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let waiters = &mut this.machine.sync.condvars[condvar].waiters; waiters.retain(|waiter| *waiter != thread); // Now get back the lock. - this.condvar_reacquire_mutex(mutex, retval_timeout, dest) + this.condvar_reacquire_mutex(&mutex_ref, retval_timeout, dest) } ), ); diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index e6a3ae897c23b..59e2fdd428504 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -113,6 +113,11 @@ impl ThreadId { self.0 } + /// Create a new thread id from a `u32` without checking if this thread exists. + pub fn new_unchecked(id: u32) -> Self { + Self(id) + } + pub const MAIN_THREAD: ThreadId = ThreadId(0); } @@ -141,7 +146,7 @@ pub enum BlockReason { /// Waiting for time to pass. Sleep, /// Blocked on a mutex. - Mutex(MutexId), + Mutex, /// Blocked on a condition variable. Condvar(CondvarId), /// Blocked on a reader-writer lock. @@ -152,6 +157,8 @@ pub enum BlockReason { InitOnce(InitOnceId), /// Blocked on epoll. Epoll, + /// Blocked on eventfd. + Eventfd, } /// The state of a thread. diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 63591474120db..41b7be37c37d3 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -195,7 +195,7 @@ pub fn prune_stacktrace<'tcx>( // This len check ensures that we don't somehow remove every frame, as doing so breaks // the primary error message. while stacktrace.len() > 1 - && stacktrace.last().map_or(false, |frame| !machine.is_local(frame)) + && stacktrace.last().is_some_and(|frame| !machine.is_local(frame)) { stacktrace.pop(); } diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 85591a0b37022..1df1d08802a82 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -270,12 +270,8 @@ pub fn create_ecx<'tcx>( ) -> InterpResult<'tcx, InterpCx<'tcx, MiriMachine<'tcx>>> { let typing_env = ty::TypingEnv::fully_monomorphized(); let layout_cx = LayoutCx::new(tcx, typing_env); - let mut ecx = InterpCx::new( - tcx, - rustc_span::DUMMY_SP, - typing_env, - MiriMachine::new(config, layout_cx) - ); + let mut ecx = + InterpCx::new(tcx, rustc_span::DUMMY_SP, typing_env, MiriMachine::new(config, layout_cx)); // Some parts of initialization require a full `InterpCx`. MiriMachine::late_init(&mut ecx, config, { diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 4b34f1686a01a..b57ce4e070c38 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -116,8 +116,7 @@ pub fn resolve_path<'tcx>( /// Gets the layout of a type at a path. #[track_caller] pub fn path_ty_layout<'tcx>(cx: &impl LayoutOf<'tcx>, path: &[&str]) -> TyAndLayout<'tcx> { - let ty = resolve_path(cx.tcx(), path, Namespace::TypeNS) - .ty(cx.tcx(), cx.typing_env()); + let ty = resolve_path(cx.tcx(), path, Namespace::TypeNS).ty(cx.tcx(), cx.typing_env()); cx.layout_of(ty).to_result().ok().unwrap() } @@ -1009,7 +1008,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_ref(); fn float_to_int_inner<'tcx, F: rustc_apfloat::Float>( - this: &MiriInterpCx<'tcx>, + ecx: &MiriInterpCx<'tcx>, src: F, cast_to: TyAndLayout<'tcx>, round: rustc_apfloat::Round, @@ -1029,7 +1028,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Nothing else _ => span_bug!( - this.cur_span(), + ecx.cur_span(), "attempted float-to-int conversion with non-int output type {}", cast_to.ty, ), diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 272dca1594ed9..9eebbc5d3631e 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -218,20 +218,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let f_host = f.to_host(); + // Using host floats except for sqrt (but it's fine, these operations do not have + // guaranteed precision). let res = match intrinsic_name { - "sinf32" => f_host.sin(), - "cosf32" => f_host.cos(), - "sqrtf32" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats - "expf32" => f_host.exp(), - "exp2f32" => f_host.exp2(), - "logf32" => f_host.ln(), - "log10f32" => f_host.log10(), - "log2f32" => f_host.log2(), + "sinf32" => f.to_host().sin().to_soft(), + "cosf32" => f.to_host().cos().to_soft(), + "sqrtf32" => math::sqrt(f), + "expf32" => f.to_host().exp().to_soft(), + "exp2f32" => f.to_host().exp2().to_soft(), + "logf32" => f.to_host().ln().to_soft(), + "log10f32" => f.to_host().log10().to_soft(), + "log2f32" => f.to_host().log2().to_soft(), _ => bug!(), }; - let res = res.to_soft(); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -247,20 +246,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; - // Using host floats (but it's fine, these operations do not have guaranteed precision). - let f_host = f.to_host(); + // Using host floats except for sqrt (but it's fine, these operations do not have + // guaranteed precision). let res = match intrinsic_name { - "sinf64" => f_host.sin(), - "cosf64" => f_host.cos(), - "sqrtf64" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats - "expf64" => f_host.exp(), - "exp2f64" => f_host.exp2(), - "logf64" => f_host.ln(), - "log10f64" => f_host.log10(), - "log2f64" => f_host.log2(), + "sinf64" => f.to_host().sin().to_soft(), + "cosf64" => f.to_host().cos().to_soft(), + "sqrtf64" => math::sqrt(f), + "expf64" => f.to_host().exp().to_soft(), + "exp2f64" => f.to_host().exp2().to_soft(), + "logf64" => f.to_host().ln().to_soft(), + "log10f64" => f.to_host().log10().to_soft(), + "log2f64" => f.to_host().log2().to_soft(), _ => bug!(), }; - let res = res.to_soft(); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index d5c417e723142..075b6f35e0ee0 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -104,42 +104,39 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let ty::Float(float_ty) = op.layout.ty.kind() else { span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) }; - // Using host floats (but it's fine, these operations do not have guaranteed precision). + // Using host floats except for sqrt (but it's fine, these operations do not + // have guaranteed precision). match float_ty { FloatTy::F16 => unimplemented!("f16_f128"), FloatTy::F32 => { let f = op.to_scalar().to_f32()?; - let f_host = f.to_host(); let res = match host_op { - "fsqrt" => f_host.sqrt(), // FIXME Using host floats, this should use full-precision soft-floats - "fsin" => f_host.sin(), - "fcos" => f_host.cos(), - "fexp" => f_host.exp(), - "fexp2" => f_host.exp2(), - "flog" => f_host.ln(), - "flog2" => f_host.log2(), - "flog10" => f_host.log10(), + "fsqrt" => math::sqrt(f), + "fsin" => f.to_host().sin().to_soft(), + "fcos" => f.to_host().cos().to_soft(), + "fexp" => f.to_host().exp().to_soft(), + "fexp2" => f.to_host().exp2().to_soft(), + "flog" => f.to_host().ln().to_soft(), + "flog2" => f.to_host().log2().to_soft(), + "flog10" => f.to_host().log10().to_soft(), _ => bug!(), }; - let res = res.to_soft(); let res = this.adjust_nan(res, &[f]); Scalar::from(res) } FloatTy::F64 => { let f = op.to_scalar().to_f64()?; - let f_host = f.to_host(); let res = match host_op { - "fsqrt" => f_host.sqrt(), - "fsin" => f_host.sin(), - "fcos" => f_host.cos(), - "fexp" => f_host.exp(), - "fexp2" => f_host.exp2(), - "flog" => f_host.ln(), - "flog2" => f_host.log2(), - "flog10" => f_host.log10(), + "fsqrt" => math::sqrt(f), + "fsin" => f.to_host().sin().to_soft(), + "fcos" => f.to_host().cos().to_soft(), + "fexp" => f.to_host().exp().to_soft(), + "fexp2" => f.to_host().exp2().to_soft(), + "flog" => f.to_host().ln().to_soft(), + "flog2" => f.to_host().log2().to_soft(), + "flog10" => f.to_host().log10().to_soft(), _ => bug!(), }; - let res = res.to_soft(); let res = this.adjust_nan(res, &[f]); Scalar::from(res) } diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index f903ccbc25a74..85c896563dabf 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -83,6 +83,7 @@ mod eval; mod helpers; mod intrinsics; mod machine; +mod math; mod mono_hash_map; mod operator; mod provenance_gc; @@ -122,7 +123,7 @@ pub use crate::concurrency::data_race::{ }; pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceId}; pub use crate::concurrency::sync::{ - CondvarId, EvalContextExt as _, MutexId, RwLockId, SynchronizationObjects, + CondvarId, EvalContextExt as _, MutexRef, RwLockId, SynchronizationObjects, }; pub use crate::concurrency::thread::{ BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, TimeoutAnchor, diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 7b03aed476383..814dc6d2b01f7 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -732,20 +732,20 @@ impl<'tcx> MiriMachine<'tcx> { } pub(crate) fn late_init( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, config: &MiriConfig, on_main_stack_empty: StackEmptyCallback<'tcx>, ) -> InterpResult<'tcx> { - EnvVars::init(this, config)?; - MiriMachine::init_extern_statics(this)?; - ThreadManager::init(this, on_main_stack_empty); + EnvVars::init(ecx, config)?; + MiriMachine::init_extern_statics(ecx)?; + ThreadManager::init(ecx, on_main_stack_empty); interp_ok(()) } - pub(crate) fn add_extern_static(this: &mut MiriInterpCx<'tcx>, name: &str, ptr: Pointer) { + pub(crate) fn add_extern_static(ecx: &mut MiriInterpCx<'tcx>, name: &str, ptr: Pointer) { // This got just allocated, so there definitely is a pointer here. let ptr = ptr.into_pointer_or_addr().unwrap(); - this.machine.extern_statics.try_insert(Symbol::intern(name), ptr).unwrap(); + ecx.machine.extern_statics.try_insert(Symbol::intern(name), ptr).unwrap(); } pub(crate) fn communicate(&self) -> bool { diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs new file mode 100644 index 0000000000000..ed3d2d55678e2 --- /dev/null +++ b/src/tools/miri/src/math.rs @@ -0,0 +1,164 @@ +use rand::Rng as _; +use rand::distributions::Distribution as _; +use rustc_apfloat::Float as _; +use rustc_apfloat::ieee::IeeeFloat; + +/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). +pub(crate) fn apply_random_float_error( + ecx: &mut crate::MiriInterpCx<'_>, + val: F, + err_scale: i32, +) -> F { + let rng = ecx.machine.rng.get_mut(); + // Generate a random integer in the range [0, 2^PREC). + let dist = rand::distributions::Uniform::new(0, 1 << F::PRECISION); + let err = F::from_u128(dist.sample(rng)) + .value + .scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap())); + // give it a random sign + let err = if rng.gen::() { -err } else { err }; + // multiple the value with (1+err) + (val * (F::from_u128(1).value + err).value).value +} + +pub(crate) fn sqrt(x: IeeeFloat) -> IeeeFloat { + match x.category() { + // preserve zero sign + rustc_apfloat::Category::Zero => x, + // propagate NaN + rustc_apfloat::Category::NaN => x, + // sqrt of negative number is NaN + _ if x.is_negative() => IeeeFloat::NAN, + // sqrt(∞) = ∞ + rustc_apfloat::Category::Infinity => IeeeFloat::INFINITY, + rustc_apfloat::Category::Normal => { + // Floating point precision, excluding the integer bit + let prec = i32::try_from(S::PRECISION).unwrap() - 1; + + // x = 2^(exp - prec) * mant + // where mant is an integer with prec+1 bits + // mant is a u128, which should be large enough for the largest prec (112 for f128) + let mut exp = x.ilogb(); + let mut mant = x.scalbn(prec - exp).to_u128(128).value; + + if exp % 2 != 0 { + // Make exponent even, so it can be divided by 2 + exp -= 1; + mant <<= 1; + } + + // Bit-by-bit (base-2 digit-by-digit) sqrt of mant. + // mant is treated here as a fixed point number with prec fractional bits. + // mant will be shifted left by one bit to have an extra fractional bit, which + // will be used to determine the rounding direction. + + // res is the truncated sqrt of mant, where one bit is added at each iteration. + let mut res = 0u128; + // rem is the remainder with the current res + // rem_i = 2^i * ((mant<<1) - res_i^2) + // starting with res = 0, rem = mant<<1 + let mut rem = mant << 1; + // s_i = 2*res_i + let mut s = 0u128; + // d is used to iterate over bits, from high to low (d_i = 2^(-i)) + let mut d = 1u128 << (prec + 1); + + // For iteration j=i+1, we need to find largest b_j = 0 or 1 such that + // (res_i + b_j * 2^(-j))^2 <= mant<<1 + // Expanding (a + b)^2 = a^2 + b^2 + 2*a*b: + // res_i^2 + (b_j * 2^(-j))^2 + 2 * res_i * b_j * 2^(-j) <= mant<<1 + // And rearranging the terms: + // b_j^2 * 2^(-j) + 2 * res_i * b_j <= 2^j * (mant<<1 - res_i^2) + // b_j^2 * 2^(-j) + 2 * res_i * b_j <= rem_i + + while d != 0 { + // Probe b_j^2 * 2^(-j) + 2 * res_i * b_j <= rem_i with b_j = 1: + // t = 2*res_i + 2^(-j) + let t = s + d; + if rem >= t { + // b_j should be 1, so make res_j = res_i + 2^(-j) and adjust rem + res += d; + s += d + d; + rem -= t; + } + // Adjust rem for next iteration + rem <<= 1; + // Shift iterator + d >>= 1; + } + + // Remove extra fractional bit from result, rounding to nearest. + // If the last bit is 0, then the nearest neighbor is definitely the lower one. + // If the last bit is 1, it sounds like this may either be a tie (if there's + // infinitely many 0s after this 1), or the nearest neighbor is the upper one. + // However, since square roots are either exact or irrational, and an exact root + // would lead to the last "extra" bit being 0, we can exclude a tie in this case. + // We therefore always round up if the last bit is 1. When the last bit is 0, + // adding 1 will not do anything since the shift will discard it. + res = (res + 1) >> 1; + + // Build resulting value with res as mantissa and exp/2 as exponent + IeeeFloat::from_u128(res).value.scalbn(exp / 2 - prec) + } + } +} + +#[cfg(test)] +mod tests { + use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, QuadS, SingleS}; + + use super::sqrt; + + #[test] + fn test_sqrt() { + #[track_caller] + fn test(x: &str, expected: &str) { + let x: IeeeFloat = x.parse().unwrap(); + let expected: IeeeFloat = expected.parse().unwrap(); + let result = sqrt(x); + assert_eq!(result, expected); + } + + fn exact_tests() { + test::("0", "0"); + test::("1", "1"); + test::("1.5625", "1.25"); + test::("2.25", "1.5"); + test::("4", "2"); + test::("5.0625", "2.25"); + test::("9", "3"); + test::("16", "4"); + test::("25", "5"); + test::("36", "6"); + test::("49", "7"); + test::("64", "8"); + test::("81", "9"); + test::("100", "10"); + + test::("0.5625", "0.75"); + test::("0.25", "0.5"); + test::("0.0625", "0.25"); + test::("0.00390625", "0.0625"); + } + + exact_tests::(); + exact_tests::(); + exact_tests::(); + exact_tests::(); + + test::("2", "1.4142135"); + test::("2", "1.4142135623730951"); + + test::("1.1", "1.0488088"); + test::("1.1", "1.0488088481701516"); + + test::("2.2", "1.4832398"); + test::("2.2", "1.4832396974191326"); + + test::("1.22101e-40", "1.10499205e-20"); + test::("1.22101e-310", "1.1049932126488395e-155"); + + test::("3.4028235e38", "1.8446743e19"); + test::("1.7976931348623157e308", "1.3407807929942596e154"); + } +} diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs index 6042a9eb2eb52..b3d715db9cd63 100644 --- a/src/tools/miri/src/provenance_gc.rs +++ b/src/tools/miri/src/provenance_gc.rs @@ -195,10 +195,10 @@ impl LiveAllocs<'_, '_> { } } -fn remove_unreachable_tags<'tcx>(this: &mut MiriInterpCx<'tcx>, tags: FxHashSet) { +fn remove_unreachable_tags<'tcx>(ecx: &mut MiriInterpCx<'tcx>, tags: FxHashSet) { // Avoid iterating all allocations if there's no borrow tracker anyway. - if this.machine.borrow_tracker.is_some() { - this.memory.alloc_map().iter(|it| { + if ecx.machine.borrow_tracker.is_some() { + ecx.memory.alloc_map().iter(|it| { for (_id, (_kind, alloc)) in it { alloc.extra.borrow_tracker.as_ref().unwrap().remove_unreachable_tags(&tags); } @@ -206,16 +206,16 @@ fn remove_unreachable_tags<'tcx>(this: &mut MiriInterpCx<'tcx>, tags: FxHashSet< } } -fn remove_unreachable_allocs<'tcx>(this: &mut MiriInterpCx<'tcx>, allocs: FxHashSet) { - let allocs = LiveAllocs { ecx: this, collected: allocs }; - this.machine.allocation_spans.borrow_mut().retain(|id, _| allocs.is_live(*id)); - this.machine.symbolic_alignment.borrow_mut().retain(|id, _| allocs.is_live(*id)); - this.machine.alloc_addresses.borrow_mut().remove_unreachable_allocs(&allocs); - if let Some(borrow_tracker) = &this.machine.borrow_tracker { +fn remove_unreachable_allocs<'tcx>(ecx: &mut MiriInterpCx<'tcx>, allocs: FxHashSet) { + let allocs = LiveAllocs { ecx, collected: allocs }; + ecx.machine.allocation_spans.borrow_mut().retain(|id, _| allocs.is_live(*id)); + ecx.machine.symbolic_alignment.borrow_mut().retain(|id, _| allocs.is_live(*id)); + ecx.machine.alloc_addresses.borrow_mut().remove_unreachable_allocs(&allocs); + if let Some(borrow_tracker) = &ecx.machine.borrow_tracker { borrow_tracker.borrow_mut().remove_unreachable_allocs(&allocs); } // Clean up core (non-Miri-specific) state. - this.remove_unreachable_allocs(&allocs.collected); + ecx.remove_unreachable_allocs(&allocs.collected); } impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index 5559ea2750b3b..f0aebfe169378 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -4,13 +4,13 @@ use crate::*; impl<'tcx> MiriMachine<'tcx> { fn alloc_extern_static( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, name: &str, val: ImmTy<'tcx>, ) -> InterpResult<'tcx> { - let place = this.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?; - this.write_immediate(*val, &place)?; - Self::add_extern_static(this, name, place.ptr()); + let place = ecx.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?; + ecx.write_immediate(*val, &place)?; + Self::add_extern_static(ecx, name, place.ptr()); interp_ok(()) } @@ -18,72 +18,69 @@ impl<'tcx> MiriMachine<'tcx> { /// Most of them are for weak symbols, which we all set to null (indicating that the /// symbol is not supported, and triggering fallback code which ends up calling /// some other shim that we do support). - fn null_ptr_extern_statics( - this: &mut MiriInterpCx<'tcx>, - names: &[&str], - ) -> InterpResult<'tcx> { + fn null_ptr_extern_statics(ecx: &mut MiriInterpCx<'tcx>, names: &[&str]) -> InterpResult<'tcx> { for name in names { - let val = ImmTy::from_int(0, this.machine.layouts.usize); - Self::alloc_extern_static(this, name, val)?; + let val = ImmTy::from_int(0, ecx.machine.layouts.usize); + Self::alloc_extern_static(ecx, name, val)?; } interp_ok(()) } /// Extern statics that are initialized with function pointers to the symbols of the same name. fn weak_symbol_extern_statics( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, names: &[&str], ) -> InterpResult<'tcx> { for name in names { - assert!(this.is_dyn_sym(name), "{name} is not a dynamic symbol"); - let layout = this.machine.layouts.const_raw_ptr; - let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name))); - let val = ImmTy::from_scalar(Scalar::from_pointer(ptr, this), layout); - Self::alloc_extern_static(this, name, val)?; + assert!(ecx.is_dyn_sym(name), "{name} is not a dynamic symbol"); + let layout = ecx.machine.layouts.const_raw_ptr; + let ptr = ecx.fn_ptr(FnVal::Other(DynSym::from_str(name))); + let val = ImmTy::from_scalar(Scalar::from_pointer(ptr, ecx), layout); + Self::alloc_extern_static(ecx, name, val)?; } interp_ok(()) } /// Sets up the "extern statics" for this machine. - pub fn init_extern_statics(this: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx> { + pub fn init_extern_statics(ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx> { // "__rust_no_alloc_shim_is_unstable" - let val = ImmTy::from_int(0, this.machine.layouts.u8); // always 0, value does not matter - Self::alloc_extern_static(this, "__rust_no_alloc_shim_is_unstable", val)?; + let val = ImmTy::from_int(0, ecx.machine.layouts.u8); // always 0, value does not matter + Self::alloc_extern_static(ecx, "__rust_no_alloc_shim_is_unstable", val)?; // "__rust_alloc_error_handler_should_panic" - let val = this.tcx.sess.opts.unstable_opts.oom.should_panic(); - let val = ImmTy::from_int(val, this.machine.layouts.u8); - Self::alloc_extern_static(this, "__rust_alloc_error_handler_should_panic", val)?; + let val = ecx.tcx.sess.opts.unstable_opts.oom.should_panic(); + let val = ImmTy::from_int(val, ecx.machine.layouts.u8); + Self::alloc_extern_static(ecx, "__rust_alloc_error_handler_should_panic", val)?; - if this.target_os_is_unix() { + if ecx.target_os_is_unix() { // "environ" is mandated by POSIX. - let environ = this.machine.env_vars.unix().environ(); - Self::add_extern_static(this, "environ", environ); + let environ = ecx.machine.env_vars.unix().environ(); + Self::add_extern_static(ecx, "environ", environ); } - match this.tcx.sess.target.os.as_ref() { + match ecx.tcx.sess.target.os.as_ref() { "linux" => { - Self::null_ptr_extern_statics(this, &[ + Self::null_ptr_extern_statics(ecx, &[ "__cxa_thread_atexit_impl", "__clock_gettime64", ])?; - Self::weak_symbol_extern_statics(this, &["getrandom", "statx"])?; + Self::weak_symbol_extern_statics(ecx, &["getrandom", "statx"])?; } "freebsd" => { - Self::null_ptr_extern_statics(this, &["__cxa_thread_atexit_impl"])?; + Self::null_ptr_extern_statics(ecx, &["__cxa_thread_atexit_impl"])?; } "android" => { - Self::null_ptr_extern_statics(this, &["bsd_signal"])?; - Self::weak_symbol_extern_statics(this, &["signal", "getrandom"])?; + Self::null_ptr_extern_statics(ecx, &["bsd_signal"])?; + Self::weak_symbol_extern_statics(ecx, &["signal", "getrandom"])?; } "windows" => { // "_tls_used" // This is some obscure hack that is part of the Windows TLS story. It's a `u8`. - let val = ImmTy::from_int(0, this.machine.layouts.u8); - Self::alloc_extern_static(this, "_tls_used", val)?; + let val = ImmTy::from_int(0, ecx.machine.layouts.u8); + Self::alloc_extern_static(ecx, "_tls_used", val)?; } "illumos" | "solaris" => { - Self::weak_symbol_extern_statics(this, &["pthread_setname_np"])?; + Self::weak_symbol_extern_statics(ecx, &["pthread_setname_np"])?; } _ => {} // No "extern statics" supported on this target } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index b74491a2f8e62..4dc857ef30b49 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -496,14 +496,14 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Rust allocation "__rust_alloc" | "miri_alloc" => { - let default = |this: &mut MiriInterpCx<'tcx>| { + let default = |ecx: &mut MiriInterpCx<'tcx>| { // Only call `check_shim` when `#[global_allocator]` isn't used. When that // macro is used, we act like no shim exists, so that the exported function can run. - let [size, align] = this.check_shim(abi, ExternAbi::Rust, link_name, args)?; - let size = this.read_target_usize(size)?; - let align = this.read_target_usize(align)?; + let [size, align] = ecx.check_shim(abi, ExternAbi::Rust, link_name, args)?; + let size = ecx.read_target_usize(size)?; + let align = ecx.read_target_usize(align)?; - this.check_rustc_alloc_request(size, align)?; + ecx.check_rustc_alloc_request(size, align)?; let memory_kind = match link_name.as_str() { "__rust_alloc" => MiriMemoryKind::Rust, @@ -511,13 +511,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => unreachable!(), }; - let ptr = this.allocate_ptr( + let ptr = ecx.allocate_ptr( Size::from_bytes(size), Align::from_bytes(align).unwrap(), memory_kind.into(), )?; - this.write_pointer(ptr, dest) + ecx.write_pointer(ptr, dest) }; match link_name.as_str() { @@ -555,14 +555,14 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { }); } "__rust_dealloc" | "miri_dealloc" => { - let default = |this: &mut MiriInterpCx<'tcx>| { + let default = |ecx: &mut MiriInterpCx<'tcx>| { // See the comment for `__rust_alloc` why `check_shim` is only called in the // default case. let [ptr, old_size, align] = - this.check_shim(abi, ExternAbi::Rust, link_name, args)?; - let ptr = this.read_pointer(ptr)?; - let old_size = this.read_target_usize(old_size)?; - let align = this.read_target_usize(align)?; + ecx.check_shim(abi, ExternAbi::Rust, link_name, args)?; + let ptr = ecx.read_pointer(ptr)?; + let old_size = ecx.read_target_usize(old_size)?; + let align = ecx.read_target_usize(align)?; let memory_kind = match link_name.as_str() { "__rust_dealloc" => MiriMemoryKind::Rust, @@ -571,7 +571,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { }; // No need to check old_size/align; we anyway check that they match the allocation. - this.deallocate_ptr( + ecx.deallocate_ptr( ptr, Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())), memory_kind.into(), diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs index 093b7405ccda6..f8a0b3a85a2f2 100644 --- a/src/tools/miri/src/shims/unix/android/thread.rs +++ b/src/tools/miri/src/shims/unix/android/thread.rs @@ -8,7 +8,7 @@ use crate::*; const TASK_COMM_LEN: usize = 16; pub fn prctl<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, link_name: Symbol, abi: ExternAbi, args: &[OpTy<'tcx>], @@ -16,41 +16,41 @@ pub fn prctl<'tcx>( ) -> InterpResult<'tcx> { // We do not use `check_shim` here because `prctl` is variadic. The argument // count is checked bellow. - this.check_abi_and_shim_symbol_clash(abi, ExternAbi::C { unwind: false }, link_name)?; + ecx.check_abi_and_shim_symbol_clash(abi, ExternAbi::C { unwind: false }, link_name)?; // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch. let pr_set_name = 15; let pr_get_name = 16; let [op] = check_min_arg_count("prctl", args)?; - let res = match this.read_scalar(op)?.to_i32()? { + let res = match ecx.read_scalar(op)?.to_i32()? { op if op == pr_set_name => { let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?; - let name = this.read_scalar(name)?; - let thread = this.pthread_self()?; + let name = ecx.read_scalar(name)?; + let thread = ecx.pthread_self()?; // The Linux kernel silently truncates long names. // https://www.man7.org/linux/man-pages/man2/PR_SET_NAME.2const.html let res = - this.pthread_setname_np(thread, name, TASK_COMM_LEN, /* truncate */ true)?; + ecx.pthread_setname_np(thread, name, TASK_COMM_LEN, /* truncate */ true)?; assert_eq!(res, ThreadNameResult::Ok); Scalar::from_u32(0) } op if op == pr_get_name => { let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?; - let name = this.read_scalar(name)?; - let thread = this.pthread_self()?; - let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, this); - this.check_ptr_access( - name.to_pointer(this)?, + let name = ecx.read_scalar(name)?; + let thread = ecx.pthread_self()?; + let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, ecx); + ecx.check_ptr_access( + name.to_pointer(ecx)?, Size::from_bytes(TASK_COMM_LEN), CheckInAllocMsg::MemoryAccessTest, )?; - let res = this.pthread_getname_np(thread, name, len, /* truncate*/ false)?; + let res = ecx.pthread_getname_np(thread, name, len, /* truncate*/ false)?; assert_eq!(res, ThreadNameResult::Ok); Scalar::from_u32(0) } op => throw_unsup_format!("Miri does not support `prctl` syscall with op={}", op), }; - this.write_scalar(res, dest)?; + ecx.write_scalar(res, dest)?; interp_ok(()) } diff --git a/src/tools/miri/src/shims/unix/linux/eventfd.rs b/src/tools/miri/src/shims/unix/linux/eventfd.rs index 35bc933885cd2..63b7d37b13e1f 100644 --- a/src/tools/miri/src/shims/unix/linux/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux/eventfd.rs @@ -4,7 +4,7 @@ use std::io; use std::io::ErrorKind; use crate::concurrency::VClock; -use crate::shims::unix::fd::FileDescriptionRef; +use crate::shims::unix::fd::{FileDescriptionRef, WeakFileDescriptionRef}; use crate::shims::unix::linux::epoll::{EpollReadyEvents, EvalContextExt as _}; use crate::shims::unix::*; use crate::*; @@ -26,6 +26,10 @@ struct Event { counter: Cell, is_nonblock: bool, clock: RefCell, + /// A list of thread ids blocked on eventfd::read. + blocked_read_tid: RefCell>, + /// A list of thread ids blocked on eventfd::write. + blocked_write_tid: RefCell>, } impl FileDescription for Event { @@ -72,31 +76,8 @@ impl FileDescription for Event { // eventfd read at the size of u64. let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty); - // Block when counter == 0. - let counter = self.counter.get(); - if counter == 0 { - if self.is_nonblock { - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); - } - - throw_unsup_format!("eventfd: blocking is unsupported"); - } else { - // Synchronize with all prior `write` calls to this FD. - ecx.acquire_clock(&self.clock.borrow()); - - // Give old counter value to userspace, and set counter value to 0. - ecx.write_int(counter, &buf_place)?; - self.counter.set(0); - - // When any of the event happened, we check and update the status of all supported event - // types for current file description. - ecx.check_and_update_readiness(self_ref)?; - - // Tell userspace how many bytes we wrote. - ecx.write_int(buf_place.layout.size.bytes(), dest)?; - } - - interp_ok(()) + let weak_eventfd = self_ref.downgrade(); + eventfd_read(buf_place, dest, weak_eventfd, ecx) } /// A write call adds the 8-byte integer value supplied in @@ -127,7 +108,7 @@ impl FileDescription for Event { return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest); } - // Read the user supplied value from the pointer. + // Read the user-supplied value from the pointer. let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty); let num = ecx.read_scalar(&buf_place)?.to_u64()?; @@ -137,27 +118,8 @@ impl FileDescription for Event { } // If the addition does not let the counter to exceed the maximum value, update the counter. // Else, block. - match self.counter.get().checked_add(num) { - Some(new_count @ 0..=MAX_COUNTER) => { - // Future `read` calls will synchronize with this write, so update the FD clock. - ecx.release_clock(|clock| { - self.clock.borrow_mut().join(clock); - }); - self.counter.set(new_count); - } - None | Some(u64::MAX) => - if self.is_nonblock { - return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); - } else { - throw_unsup_format!("eventfd: blocking is unsupported"); - }, - }; - // When any of the event happened, we check and update the status of all supported event - // types for current file description. - ecx.check_and_update_readiness(self_ref)?; - - // Return how many bytes we read. - ecx.write_int(buf_place.layout.size.bytes(), dest) + let weak_eventfd = self_ref.downgrade(); + eventfd_write(num, buf_place, dest, weak_eventfd, ecx) } } @@ -217,8 +179,151 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { counter: Cell::new(val.into()), is_nonblock, clock: RefCell::new(VClock::default()), + blocked_read_tid: RefCell::new(Vec::new()), + blocked_write_tid: RefCell::new(Vec::new()), }); interp_ok(Scalar::from_i32(fd_value)) } } + +/// Block thread if the value addition will exceed u64::MAX -1, +/// else just add the user-supplied value to current counter. +fn eventfd_write<'tcx>( + num: u64, + buf_place: MPlaceTy<'tcx>, + dest: &MPlaceTy<'tcx>, + weak_eventfd: WeakFileDescriptionRef, + ecx: &mut MiriInterpCx<'tcx>, +) -> InterpResult<'tcx> { + let Some(eventfd_ref) = weak_eventfd.upgrade() else { + throw_unsup_format!("eventfd FD got closed while blocking.") + }; + + // Since we pass the weak file description ref, it is guaranteed to be + // an eventfd file description. + let eventfd = eventfd_ref.downcast::().unwrap(); + + match eventfd.counter.get().checked_add(num) { + Some(new_count @ 0..=MAX_COUNTER) => { + // Future `read` calls will synchronize with this write, so update the FD clock. + ecx.release_clock(|clock| { + eventfd.clock.borrow_mut().join(clock); + }); + + // When this function is called, the addition is guaranteed to not exceed u64::MAX - 1. + eventfd.counter.set(new_count); + + // When any of the event happened, we check and update the status of all supported event + // types for current file description. + ecx.check_and_update_readiness(&eventfd_ref)?; + + // Unblock *all* threads previously blocked on `read`. + // We need to take out the blocked thread ids and unblock them together, + // because `unblock_threads` may block them again and end up re-adding the + // thread to the blocked list. + let waiting_threads = std::mem::take(&mut *eventfd.blocked_read_tid.borrow_mut()); + // FIXME: We can randomize the order of unblocking. + for thread_id in waiting_threads { + ecx.unblock_thread(thread_id, BlockReason::Eventfd)?; + } + + // Return how many bytes we wrote. + return ecx.write_int(buf_place.layout.size.bytes(), dest); + } + None | Some(u64::MAX) => { + if eventfd.is_nonblock { + return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + } + + let dest = dest.clone(); + + eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread()); + + ecx.block_thread( + BlockReason::Eventfd, + None, + callback!( + @capture<'tcx> { + num: u64, + buf_place: MPlaceTy<'tcx>, + dest: MPlaceTy<'tcx>, + weak_eventfd: WeakFileDescriptionRef, + } + @unblock = |this| { + eventfd_write(num, buf_place, &dest, weak_eventfd, this) + } + ), + ); + } + }; + interp_ok(()) +} + +/// Block thread if the current counter is 0, +/// else just return the current counter value to the caller and set the counter to 0. +fn eventfd_read<'tcx>( + buf_place: MPlaceTy<'tcx>, + dest: &MPlaceTy<'tcx>, + weak_eventfd: WeakFileDescriptionRef, + ecx: &mut MiriInterpCx<'tcx>, +) -> InterpResult<'tcx> { + let Some(eventfd_ref) = weak_eventfd.upgrade() else { + throw_unsup_format!("eventfd FD got closed while blocking.") + }; + + // Since we pass the weak file description ref to the callback function, it is guaranteed to be + // an eventfd file description. + let eventfd = eventfd_ref.downcast::().unwrap(); + + // Block when counter == 0. + let counter = eventfd.counter.replace(0); + + if counter == 0 { + if eventfd.is_nonblock { + return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest); + } + let dest = dest.clone(); + + eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread()); + + ecx.block_thread( + BlockReason::Eventfd, + None, + callback!( + @capture<'tcx> { + buf_place: MPlaceTy<'tcx>, + dest: MPlaceTy<'tcx>, + weak_eventfd: WeakFileDescriptionRef, + } + @unblock = |this| { + eventfd_read(buf_place, &dest, weak_eventfd, this) + } + ), + ); + } else { + // Synchronize with all prior `write` calls to this FD. + ecx.acquire_clock(&eventfd.clock.borrow()); + + // Give old counter value to userspace, and set counter value to 0. + ecx.write_int(counter, &buf_place)?; + + // When any of the events happened, we check and update the status of all supported event + // types for current file description. + ecx.check_and_update_readiness(&eventfd_ref)?; + + // Unblock *all* threads previously blocked on `write`. + // We need to take out the blocked thread ids and unblock them together, + // because `unblock_threads` may block them again and end up re-adding the + // thread to the blocked list. + let waiting_threads = std::mem::take(&mut *eventfd.blocked_write_tid.borrow_mut()); + // FIXME: We can randomize the order of unblocking. + for thread_id in waiting_threads { + ecx.unblock_thread(thread_id, BlockReason::Eventfd)?; + } + + // Tell userspace how many bytes we read. + return ecx.write_int(buf_place.layout.size.bytes(), dest); + } + interp_ok(()) +} diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs index 01b011d35046e..51124fb2a003b 100644 --- a/src/tools/miri/src/shims/unix/linux/sync.rs +++ b/src/tools/miri/src/shims/unix/linux/sync.rs @@ -9,7 +9,7 @@ struct LinuxFutex { /// Implementation of the SYS_futex syscall. /// `args` is the arguments *including* the syscall number. pub fn futex<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { @@ -26,19 +26,19 @@ pub fn futex<'tcx>( // The first three arguments (after the syscall number itself) are the same to all futex operations: // (int *addr, int op, int val). // We checked above that these definitely exist. - let addr = this.read_pointer(addr)?; - let op = this.read_scalar(op)?.to_i32()?; - let val = this.read_scalar(val)?.to_i32()?; + let addr = ecx.read_pointer(addr)?; + let op = ecx.read_scalar(op)?.to_i32()?; + let val = ecx.read_scalar(val)?.to_i32()?; // This is a vararg function so we have to bring our own type for this pointer. - let addr = this.ptr_to_mplace(addr, this.machine.layouts.i32); + let addr = ecx.ptr_to_mplace(addr, ecx.machine.layouts.i32); - let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG"); - let futex_wait = this.eval_libc_i32("FUTEX_WAIT"); - let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET"); - let futex_wake = this.eval_libc_i32("FUTEX_WAKE"); - let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET"); - let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME"); + let futex_private = ecx.eval_libc_i32("FUTEX_PRIVATE_FLAG"); + let futex_wait = ecx.eval_libc_i32("FUTEX_WAIT"); + let futex_wait_bitset = ecx.eval_libc_i32("FUTEX_WAIT_BITSET"); + let futex_wake = ecx.eval_libc_i32("FUTEX_WAKE"); + let futex_wake_bitset = ecx.eval_libc_i32("FUTEX_WAKE_BITSET"); + let futex_realtime = ecx.eval_libc_i32("FUTEX_CLOCK_REALTIME"); // FUTEX_PRIVATE enables an optimization that stops it from working across processes. // Miri doesn't support that anyway, so we ignore that flag. @@ -57,9 +57,9 @@ pub fn futex<'tcx>( let (timeout, bitset) = if wait_bitset { let [_, _, _, _, timeout, uaddr2, bitset] = check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT_BITSET, ...)`", args)?; - let _timeout = this.read_pointer(timeout)?; - let _uaddr2 = this.read_pointer(uaddr2)?; - (timeout, this.read_scalar(bitset)?.to_u32()?) + let _timeout = ecx.read_pointer(timeout)?; + let _uaddr2 = ecx.read_pointer(uaddr2)?; + (timeout, ecx.read_scalar(bitset)?.to_u32()?) } else { let [_, _, _, _, timeout] = check_min_arg_count("`syscall(SYS_futex, FUTEX_WAIT, ...)`", args)?; @@ -67,21 +67,21 @@ pub fn futex<'tcx>( }; if bitset == 0 { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return ecx.set_last_error_and_return(LibcError("EINVAL"), dest); } - let timeout = this.deref_pointer_as(timeout, this.libc_ty_layout("timespec"))?; - let timeout = if this.ptr_is_null(timeout.ptr())? { + let timeout = ecx.deref_pointer_as(timeout, ecx.libc_ty_layout("timespec"))?; + let timeout = if ecx.ptr_is_null(timeout.ptr())? { None } else { - let duration = match this.read_timespec(&timeout)? { + let duration = match ecx.read_timespec(&timeout)? { Some(duration) => duration, None => { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return ecx.set_last_error_and_return(LibcError("EINVAL"), dest); } }; let timeout_clock = if op & futex_realtime == futex_realtime { - this.check_no_isolation( + ecx.check_no_isolation( "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`", )?; TimeoutClock::RealTime @@ -139,36 +139,36 @@ pub fn futex<'tcx>( // // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to // do anything special to guarantee fence-load-comparison atomicity. - this.atomic_fence(AtomicFenceOrd::SeqCst)?; + ecx.atomic_fence(AtomicFenceOrd::SeqCst)?; // Read an `i32` through the pointer, regardless of any wrapper types. // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`. // We do an acquire read -- it only seems reasonable that if we observe a value here, we // actually establish an ordering with that value. - let futex_val = this.read_scalar_atomic(&addr, AtomicReadOrd::Acquire)?.to_i32()?; + let futex_val = ecx.read_scalar_atomic(&addr, AtomicReadOrd::Acquire)?.to_i32()?; if val == futex_val { // The value still matches, so we block the thread and make it wait for FUTEX_WAKE. // This cannot fail since we already did an atomic acquire read on that pointer. // Acquire reads are only allowed on mutable memory. - let futex_ref = this + let futex_ref = ecx .get_sync_or_init(addr.ptr(), |_| LinuxFutex { futex: Default::default() }) .unwrap() .futex .clone(); - this.futex_wait( + ecx.futex_wait( futex_ref, bitset, timeout, - Scalar::from_target_isize(0, this), // retval_succ - Scalar::from_target_isize(-1, this), // retval_timeout + Scalar::from_target_isize(0, ecx), // retval_succ + Scalar::from_target_isize(-1, ecx), // retval_timeout dest.clone(), LibcError("ETIMEDOUT"), // errno_timeout ); } else { // The futex value doesn't match the expected value, so we return failure // right away without sleeping: -1 and errno set to EAGAIN. - return this.set_last_error_and_return(LibcError("EAGAIN"), dest); + return ecx.set_last_error_and_return(LibcError("EAGAIN"), dest); } } // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val) @@ -179,42 +179,42 @@ pub fn futex<'tcx>( // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up. op if op == futex_wake || op == futex_wake_bitset => { let Some(futex_ref) = - this.get_sync_or_init(addr.ptr(), |_| LinuxFutex { futex: Default::default() }) + ecx.get_sync_or_init(addr.ptr(), |_| LinuxFutex { futex: Default::default() }) else { // No AllocId, or no live allocation at that AllocId. // Return an error code. (That seems nicer than silently doing something non-intuitive.) // This means that if an address gets reused by a new allocation, // we'll use an independent futex queue for this... that seems acceptable. - return this.set_last_error_and_return(LibcError("EFAULT"), dest); + return ecx.set_last_error_and_return(LibcError("EFAULT"), dest); }; let futex_ref = futex_ref.futex.clone(); let bitset = if op == futex_wake_bitset { let [_, _, _, _, timeout, uaddr2, bitset] = check_min_arg_count("`syscall(SYS_futex, FUTEX_WAKE_BITSET, ...)`", args)?; - let _timeout = this.read_pointer(timeout)?; - let _uaddr2 = this.read_pointer(uaddr2)?; - this.read_scalar(bitset)?.to_u32()? + let _timeout = ecx.read_pointer(timeout)?; + let _uaddr2 = ecx.read_pointer(uaddr2)?; + ecx.read_scalar(bitset)?.to_u32()? } else { u32::MAX }; if bitset == 0 { - return this.set_last_error_and_return(LibcError("EINVAL"), dest); + return ecx.set_last_error_and_return(LibcError("EINVAL"), dest); } // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait // will see the latest value on addr which could be changed by our caller // before doing the syscall. - this.atomic_fence(AtomicFenceOrd::SeqCst)?; + ecx.atomic_fence(AtomicFenceOrd::SeqCst)?; let mut n = 0; #[expect(clippy::arithmetic_side_effects)] for _ in 0..val { - if this.futex_wake(&futex_ref, bitset)? { + if ecx.futex_wake(&futex_ref, bitset)? { n += 1; } else { break; } } - this.write_scalar(Scalar::from_target_isize(n, this), dest)?; + ecx.write_scalar(Scalar::from_target_isize(n, ecx), dest)?; } op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op), } diff --git a/src/tools/miri/src/shims/unix/linux/syscall.rs b/src/tools/miri/src/shims/unix/linux/syscall.rs index 0d7032adab43e..9f6935f096be4 100644 --- a/src/tools/miri/src/shims/unix/linux/syscall.rs +++ b/src/tools/miri/src/shims/unix/linux/syscall.rs @@ -7,7 +7,7 @@ use crate::shims::unix::linux::sync::futex; use crate::*; pub fn syscall<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, link_name: Symbol, abi: ExternAbi, args: &[OpTy<'tcx>], @@ -15,18 +15,18 @@ pub fn syscall<'tcx>( ) -> InterpResult<'tcx> { // We do not use `check_shim` here because `syscall` is variadic. The argument // count is checked bellow. - this.check_abi_and_shim_symbol_clash(abi, ExternAbi::C { unwind: false }, link_name)?; + ecx.check_abi_and_shim_symbol_clash(abi, ExternAbi::C { unwind: false }, link_name)?; // The syscall variadic function is legal to call with more arguments than needed, // extra arguments are simply ignored. The important check is that when we use an // argument, we have to also check all arguments *before* it to ensure that they // have the right type. - let sys_getrandom = this.eval_libc("SYS_getrandom").to_target_usize(this)?; - let sys_futex = this.eval_libc("SYS_futex").to_target_usize(this)?; - let sys_eventfd2 = this.eval_libc("SYS_eventfd2").to_target_usize(this)?; + let sys_getrandom = ecx.eval_libc("SYS_getrandom").to_target_usize(ecx)?; + let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?; + let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?; let [op] = check_min_arg_count("syscall", args)?; - match this.read_target_usize(op)? { + match ecx.read_target_usize(op)? { // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)` // is called if a `HashMap` is created the regular way (e.g. HashMap). num if num == sys_getrandom => { @@ -34,25 +34,25 @@ pub fn syscall<'tcx>( // The first argument is the syscall id, so skip over it. let [_, ptr, len, flags] = check_min_arg_count("syscall(SYS_getrandom, ...)", args)?; - let ptr = this.read_pointer(ptr)?; - let len = this.read_target_usize(len)?; + let ptr = ecx.read_pointer(ptr)?; + let len = ecx.read_target_usize(len)?; // The only supported flags are GRND_RANDOM and GRND_NONBLOCK, // neither of which have any effect on our current PRNG. // See for a discussion of argument sizes. - let _flags = this.read_scalar(flags)?.to_i32()?; + let _flags = ecx.read_scalar(flags)?.to_i32()?; - this.gen_random(ptr, len)?; - this.write_scalar(Scalar::from_target_usize(len, this), dest)?; + ecx.gen_random(ptr, len)?; + ecx.write_scalar(Scalar::from_target_usize(len, ecx), dest)?; } // `futex` is used by some synchronization primitives. num if num == sys_futex => { - futex(this, args, dest)?; + futex(ecx, args, dest)?; } num if num == sys_eventfd2 => { let [_, initval, flags] = check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?; - let result = this.eventfd(initval, flags)?; - this.write_int(result.to_i32()?, dest)?; + let result = ecx.eventfd(initval, flags)?; + ecx.write_int(result.to_i32()?, dest)?; } num => { throw_unsup_format!("syscall: unsupported syscall number {num}"); diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs index ffc8c2227380d..f66a57ae7061c 100644 --- a/src/tools/miri/src/shims/unix/macos/sync.rs +++ b/src/tools/miri/src/shims/unix/macos/sync.rs @@ -14,18 +14,21 @@ use rustc_abi::Size; use crate::*; -#[derive(Copy, Clone)] +#[derive(Clone)] enum MacOsUnfairLock { Poisoned, - Active { id: MutexId }, + Active { mutex_ref: MutexRef }, } impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { - fn os_unfair_lock_get_data( - &mut self, + fn os_unfair_lock_get_data<'a>( + &'a mut self, lock_ptr: &OpTy<'tcx>, - ) -> InterpResult<'tcx, MacOsUnfairLock> { + ) -> InterpResult<'tcx, &'a MacOsUnfairLock> + where + 'tcx: 'a, + { let this = self.eval_context_mut(); let lock = this.deref_pointer(lock_ptr)?; this.lazy_sync_get_data( @@ -42,8 +45,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(MacOsUnfairLock::Poisoned) }, |ecx| { - let id = ecx.machine.sync.mutex_create(); - interp_ok(MacOsUnfairLock::Active { id }) + let mutex_ref = ecx.machine.sync.mutex_create(); + interp_ok(MacOsUnfairLock::Active { mutex_ref }) }, ) } @@ -54,7 +57,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let MacOsUnfairLock::Active { id } = this.os_unfair_lock_get_data(lock_op)? else { + let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else { // Trying to get a poisoned lock. Just block forever... this.block_thread( BlockReason::Sleep, @@ -68,18 +71,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); return interp_ok(()); }; + let mutex_ref = mutex_ref.clone(); - if this.mutex_is_locked(id) { - if this.mutex_get_owner(id) == this.active_thread() { + if this.mutex_is_locked(&mutex_ref) { + if this.mutex_get_owner(&mutex_ref) == this.active_thread() { // Matching the current macOS implementation: abort on reentrant locking. throw_machine_stop!(TerminationInfo::Abort( "attempted to lock an os_unfair_lock that is already locked by the current thread".to_owned() )); } - this.mutex_enqueue_and_block(id, None); + this.mutex_enqueue_and_block(&mutex_ref, None); } else { - this.mutex_lock(id); + this.mutex_lock(&mutex_ref); } interp_ok(()) @@ -92,18 +96,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let MacOsUnfairLock::Active { id } = this.os_unfair_lock_get_data(lock_op)? else { + let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else { // Trying to get a poisoned lock. That never works. this.write_scalar(Scalar::from_bool(false), dest)?; return interp_ok(()); }; + let mutex_ref = mutex_ref.clone(); - if this.mutex_is_locked(id) { + if this.mutex_is_locked(&mutex_ref) { // Contrary to the blocking lock function, this does not check for // reentrancy. this.write_scalar(Scalar::from_bool(false), dest)?; } else { - this.mutex_lock(id); + this.mutex_lock(&mutex_ref); this.write_scalar(Scalar::from_bool(true), dest)?; } @@ -113,15 +118,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn os_unfair_lock_unlock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let MacOsUnfairLock::Active { id } = this.os_unfair_lock_get_data(lock_op)? else { + let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else { // The lock is poisoned, who knows who owns it... we'll pretend: someone else. throw_machine_stop!(TerminationInfo::Abort( "attempted to unlock an os_unfair_lock not owned by the current thread".to_owned() )); }; + let mutex_ref = mutex_ref.clone(); // Now, unlock. - if this.mutex_unlock(id)?.is_none() { + if this.mutex_unlock(&mutex_ref)?.is_none() { // Matching the current macOS implementation: abort. throw_machine_stop!(TerminationInfo::Abort( "attempted to unlock an os_unfair_lock not owned by the current thread".to_owned() @@ -130,7 +136,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If the lock is not locked by anyone now, it went quer. // Reset to zero so that it can be moved and initialized again for the next phase. - if !this.mutex_is_locked(id) { + if !this.mutex_is_locked(&mutex_ref) { let lock_place = this.deref_pointer_as(lock_op, this.machine.layouts.u32)?; this.write_scalar_atomic(Scalar::from_u32(0), &lock_place, AtomicWriteOrd::Relaxed)?; } @@ -141,13 +147,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn os_unfair_lock_assert_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let MacOsUnfairLock::Active { id } = this.os_unfair_lock_get_data(lock_op)? else { + let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else { // The lock is poisoned, who knows who owns it... we'll pretend: someone else. throw_machine_stop!(TerminationInfo::Abort( "called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned() )); }; - if !this.mutex_is_locked(id) || this.mutex_get_owner(id) != this.active_thread() { + let mutex_ref = mutex_ref.clone(); + + if !this.mutex_is_locked(&mutex_ref) + || this.mutex_get_owner(&mutex_ref) != this.active_thread() + { throw_machine_stop!(TerminationInfo::Abort( "called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned() )); @@ -161,11 +171,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn os_unfair_lock_assert_not_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let MacOsUnfairLock::Active { id } = this.os_unfair_lock_get_data(lock_op)? else { + let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else { // The lock is poisoned, who knows who owns it... we'll pretend: someone else. return interp_ok(()); }; - if this.mutex_is_locked(id) && this.mutex_get_owner(id) == this.active_thread() { + let mutex_ref = mutex_ref.clone(); + + if this.mutex_is_locked(&mutex_ref) + && this.mutex_get_owner(&mutex_ref) == this.active_thread() + { throw_machine_stop!(TerminationInfo::Abort( "called os_unfair_lock_assert_not_owner on an os_unfair_lock owned by the current thread".to_owned() )); @@ -173,7 +187,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // If the lock is not locked by anyone now, it went quer. // Reset to zero so that it can be moved and initialized again for the next phase. - if !this.mutex_is_locked(id) { + if !this.mutex_is_locked(&mutex_ref) { let lock_place = this.deref_pointer_as(lock_op, this.machine.layouts.u32)?; this.write_scalar_atomic(Scalar::from_u32(0), &lock_place, AtomicWriteOrd::Relaxed)?; } diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs index 850626d89ac15..416cf020dcc99 100644 --- a/src/tools/miri/src/shims/unix/sync.rs +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -116,9 +116,9 @@ enum MutexKind { ErrorCheck, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct PthreadMutex { - id: MutexId, + mutex_ref: MutexRef, kind: MutexKind, } @@ -175,19 +175,20 @@ fn mutex_create<'tcx>( ) -> InterpResult<'tcx, PthreadMutex> { let mutex = ecx.deref_pointer(mutex_ptr)?; let id = ecx.machine.sync.mutex_create(); - let data = PthreadMutex { id, kind }; - ecx.lazy_sync_init(&mutex, mutex_init_offset(ecx)?, data)?; + let data = PthreadMutex { mutex_ref: id, kind }; + ecx.lazy_sync_init(&mutex, mutex_init_offset(ecx)?, data.clone())?; interp_ok(data) } -/// Returns the `MutexId` of the mutex stored at `mutex_op`. -/// -/// `mutex_get_id` will also check if the mutex has been moved since its first use and -/// return an error if it has. +/// Returns the mutex data stored at the address that `mutex_ptr` points to. +/// Will raise an error if the mutex has been moved since its first use. fn mutex_get_data<'tcx, 'a>( ecx: &'a mut MiriInterpCx<'tcx>, mutex_ptr: &OpTy<'tcx>, -) -> InterpResult<'tcx, PthreadMutex> { +) -> InterpResult<'tcx, &'a PthreadMutex> +where + 'tcx: 'a, +{ let mutex = ecx.deref_pointer(mutex_ptr)?; ecx.lazy_sync_get_data( &mutex, @@ -196,7 +197,7 @@ fn mutex_get_data<'tcx, 'a>( |ecx| { let kind = mutex_kind_from_static_initializer(ecx, &mutex)?; let id = ecx.machine.sync.mutex_create(); - interp_ok(PthreadMutex { id, kind }) + interp_ok(PthreadMutex { mutex_ref: id, kind }) }, ) } @@ -261,10 +262,13 @@ fn rwlock_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size interp_ok(offset) } -fn rwlock_get_data<'tcx>( - ecx: &mut MiriInterpCx<'tcx>, +fn rwlock_get_data<'tcx, 'a>( + ecx: &'a mut MiriInterpCx<'tcx>, rwlock_ptr: &OpTy<'tcx>, -) -> InterpResult<'tcx, PthreadRwLock> { +) -> InterpResult<'tcx, &'a PthreadRwLock> +where + 'tcx: 'a, +{ let rwlock = ecx.deref_pointer(rwlock_ptr)?; ecx.lazy_sync_get_data( &rwlock, @@ -391,10 +395,13 @@ fn cond_create<'tcx>( interp_ok(data) } -fn cond_get_data<'tcx>( - ecx: &mut MiriInterpCx<'tcx>, +fn cond_get_data<'tcx, 'a>( + ecx: &'a mut MiriInterpCx<'tcx>, cond_ptr: &OpTy<'tcx>, -) -> InterpResult<'tcx, PthreadCondvar> { +) -> InterpResult<'tcx, &'a PthreadCondvar> +where + 'tcx: 'a, +{ let cond = ecx.deref_pointer(cond_ptr)?; ecx.lazy_sync_get_data( &cond, @@ -500,12 +507,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let mutex = mutex_get_data(this, mutex_op)?; + let mutex = mutex_get_data(this, mutex_op)?.clone(); - let ret = if this.mutex_is_locked(mutex.id) { - let owner_thread = this.mutex_get_owner(mutex.id); + let ret = if this.mutex_is_locked(&mutex.mutex_ref) { + let owner_thread = this.mutex_get_owner(&mutex.mutex_ref); if owner_thread != this.active_thread() { - this.mutex_enqueue_and_block(mutex.id, Some((Scalar::from_i32(0), dest.clone()))); + this.mutex_enqueue_and_block( + &mutex.mutex_ref, + Some((Scalar::from_i32(0), dest.clone())), + ); return interp_ok(()); } else { // Trying to acquire the same mutex again. @@ -517,14 +527,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { MutexKind::Normal => throw_machine_stop!(TerminationInfo::Deadlock), MutexKind::ErrorCheck => this.eval_libc_i32("EDEADLK"), MutexKind::Recursive => { - this.mutex_lock(mutex.id); + this.mutex_lock(&mutex.mutex_ref); 0 } } } } else { // The mutex is unlocked. Let's lock it. - this.mutex_lock(mutex.id); + this.mutex_lock(&mutex.mutex_ref); 0 }; this.write_scalar(Scalar::from_i32(ret), dest)?; @@ -534,10 +544,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn pthread_mutex_trylock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let mutex = mutex_get_data(this, mutex_op)?; + let mutex = mutex_get_data(this, mutex_op)?.clone(); - interp_ok(Scalar::from_i32(if this.mutex_is_locked(mutex.id) { - let owner_thread = this.mutex_get_owner(mutex.id); + interp_ok(Scalar::from_i32(if this.mutex_is_locked(&mutex.mutex_ref) { + let owner_thread = this.mutex_get_owner(&mutex.mutex_ref); if owner_thread != this.active_thread() { this.eval_libc_i32("EBUSY") } else { @@ -545,14 +555,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { MutexKind::Default | MutexKind::Normal | MutexKind::ErrorCheck => this.eval_libc_i32("EBUSY"), MutexKind::Recursive => { - this.mutex_lock(mutex.id); + this.mutex_lock(&mutex.mutex_ref); 0 } } } } else { // The mutex is unlocked. Let's lock it. - this.mutex_lock(mutex.id); + this.mutex_lock(&mutex.mutex_ref); 0 })) } @@ -560,9 +570,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn pthread_mutex_unlock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let mutex = mutex_get_data(this, mutex_op)?; + let mutex = mutex_get_data(this, mutex_op)?.clone(); - if let Some(_old_locked_count) = this.mutex_unlock(mutex.id)? { + if let Some(_old_locked_count) = this.mutex_unlock(&mutex.mutex_ref)? { // The mutex was locked by the current thread. interp_ok(Scalar::from_i32(0)) } else { @@ -588,10 +598,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // Reading the field also has the side-effect that we detect double-`destroy` - // since we make the field unint below. - let mutex = mutex_get_data(this, mutex_op)?; + // since we make the field uninit below. + let mutex = mutex_get_data(this, mutex_op)?.clone(); - if this.mutex_is_locked(mutex.id) { + if this.mutex_is_locked(&mutex.mutex_ref) { throw_ub_format!("destroyed a locked mutex"); } @@ -696,7 +706,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // Reading the field also has the side-effect that we detect double-`destroy` - // since we make the field unint below. + // since we make the field uninit below. let id = rwlock_get_data(this, rwlock_op)?.id; if this.rwlock_is_locked(id) { @@ -821,12 +831,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let data = cond_get_data(this, cond_op)?; - let mutex_id = mutex_get_data(this, mutex_op)?.id; + let data = *cond_get_data(this, cond_op)?; + let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone(); this.condvar_wait( data.id, - mutex_id, + mutex_ref, None, // no timeout Scalar::from_i32(0), Scalar::from_i32(0), // retval_timeout -- unused @@ -845,8 +855,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let data = cond_get_data(this, cond_op)?; - let mutex_id = mutex_get_data(this, mutex_op)?.id; + let data = *cond_get_data(this, cond_op)?; + let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone(); // Extract the timeout. let duration = match this @@ -869,7 +879,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.condvar_wait( data.id, - mutex_id, + mutex_ref, Some((timeout_clock, TimeoutAnchor::Absolute, duration)), Scalar::from_i32(0), this.eval_libc("ETIMEDOUT"), // retval_timeout @@ -883,7 +893,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // Reading the field also has the side-effect that we detect double-`destroy` - // since we make the field unint below. + // since we make the field uninit below. let id = cond_get_data(this, cond_op)?.id; if this.condvar_is_awaited(id) { throw_ub_format!("destroying an awaited conditional variable"); diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 504efed3cfd53..c145cf3ceb8c1 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -7,6 +7,7 @@ use rustc_span::Symbol; use self::shims::windows::handle::{Handle, PseudoHandle}; use crate::shims::os_str::bytes_to_os_str; +use crate::shims::windows::handle::HandleError; use crate::shims::windows::*; use crate::*; @@ -488,7 +489,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let thread_id = this.CreateThread(security, stacksize, start, arg, flags, thread)?; - this.write_scalar(Handle::Thread(thread_id.to_u32()).to_scalar(this), dest)?; + this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?; } "WaitForSingleObject" => { let [handle, timeout] = @@ -513,10 +514,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let handle = this.read_scalar(handle)?; let name = this.read_wide_str(this.read_pointer(name)?)?; - let thread = match Handle::from_scalar(handle, this)? { - Some(Handle::Thread(thread)) => this.thread_id_try_from(thread), - Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()), - _ => this.invalid_handle("SetThreadDescription")?, + let thread = match Handle::try_from_scalar(handle, this)? { + Ok(Handle::Thread(thread)) => Ok(thread), + Ok(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()), + Ok(_) | Err(HandleError::InvalidHandle) => + this.invalid_handle("SetThreadDescription")?, + Err(HandleError::ThreadNotFound(e)) => Err(e), }; let res = match thread { Ok(thread) => { @@ -536,10 +539,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let handle = this.read_scalar(handle)?; let name_ptr = this.deref_pointer(name_ptr)?; // the pointer where we should store the ptr to the name - let thread = match Handle::from_scalar(handle, this)? { - Some(Handle::Thread(thread)) => this.thread_id_try_from(thread), - Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()), - _ => this.invalid_handle("GetThreadDescription")?, + let thread = match Handle::try_from_scalar(handle, this)? { + Ok(Handle::Thread(thread)) => Ok(thread), + Ok(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()), + Ok(_) | Err(HandleError::InvalidHandle) => + this.invalid_handle("GetThreadDescription")?, + Err(HandleError::ThreadNotFound(e)) => Err(e), }; let (name, res) = match thread { Ok(thread) => { diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs index b40c00efeddf4..3d872b65a6376 100644 --- a/src/tools/miri/src/shims/windows/handle.rs +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -2,6 +2,7 @@ use std::mem::variant_count; use rustc_abi::HasDataLayout; +use crate::concurrency::thread::ThreadNotFound; use crate::*; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -14,7 +15,7 @@ pub enum PseudoHandle { pub enum Handle { Null, Pseudo(PseudoHandle), - Thread(u32), + Thread(ThreadId), } impl PseudoHandle { @@ -34,6 +35,14 @@ impl PseudoHandle { } } +/// Errors that can occur when constructing a [`Handle`] from a Scalar. +pub enum HandleError { + /// There is no thread with the given ID. + ThreadNotFound(ThreadNotFound), + /// Can't convert scalar to handle because it is structurally invalid. + InvalidHandle, +} + impl Handle { const NULL_DISCRIMINANT: u32 = 0; const PSEUDO_DISCRIMINANT: u32 = 1; @@ -51,7 +60,7 @@ impl Handle { match self { Self::Null => 0, Self::Pseudo(pseudo_handle) => pseudo_handle.value(), - Self::Thread(thread) => thread, + Self::Thread(thread) => thread.to_u32(), } } @@ -95,7 +104,7 @@ impl Handle { match discriminant { Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null), Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)), - Self::THREAD_DISCRIMINANT => Some(Self::Thread(data)), + Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))), _ => None, } } @@ -126,10 +135,14 @@ impl Handle { Scalar::from_target_isize(signed_handle.into(), cx) } - pub fn from_scalar<'tcx>( + /// Convert a scalar into a structured `Handle`. + /// Structurally invalid handles return [`HandleError::InvalidHandle`]. + /// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread + /// ID, returns [`HandleError::ThreadNotFound`]. + pub fn try_from_scalar<'tcx>( handle: Scalar, - cx: &impl HasDataLayout, - ) -> InterpResult<'tcx, Option> { + cx: &MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { let sign_extended_handle = handle.to_target_isize(cx)?; #[expect(clippy::cast_sign_loss)] // we want to lose the sign @@ -137,10 +150,20 @@ impl Handle { signed_handle as u32 } else { // if a handle doesn't fit in an i32, it isn't valid. - return interp_ok(None); + return interp_ok(Err(HandleError::InvalidHandle)); }; - interp_ok(Self::from_packed(handle)) + match Self::from_packed(handle) { + Some(Self::Thread(thread)) => { + // validate the thread id + match cx.machine.threads.thread_id_try_from(thread.to_u32()) { + Ok(id) => interp_ok(Ok(Self::Thread(id))), + Err(e) => interp_ok(Err(HandleError::ThreadNotFound(e))), + } + } + Some(handle) => interp_ok(Ok(handle)), + None => interp_ok(Err(HandleError::InvalidHandle)), + } } } @@ -158,14 +181,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let handle = this.read_scalar(handle_op)?; - let ret = match Handle::from_scalar(handle, this)? { - Some(Handle::Thread(thread)) => { - if let Ok(thread) = this.thread_id_try_from(thread) { - this.detach_thread(thread, /*allow_terminated_joined*/ true)?; - this.eval_windows("c", "TRUE") - } else { - this.invalid_handle("CloseHandle")? - } + let ret = match Handle::try_from_scalar(handle, this)? { + Ok(Handle::Thread(thread)) => { + this.detach_thread(thread, /*allow_terminated_joined*/ true)?; + this.eval_windows("c", "TRUE") } _ => this.invalid_handle("CloseHandle")?, }; diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index b03dedea146c4..a394e0430bcd0 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -20,10 +20,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Windows sync primitives are pointer sized. // We only use the first 4 bytes for the id. - fn init_once_get_data( - &mut self, + fn init_once_get_data<'a>( + &'a mut self, init_once_ptr: &OpTy<'tcx>, - ) -> InterpResult<'tcx, WindowsInitOnce> { + ) -> InterpResult<'tcx, &'a WindowsInitOnce> + where + 'tcx: 'a, + { let this = self.eval_context_mut(); let init_once = this.deref_pointer(init_once_ptr)?; diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs index 7af15fc647ce3..efc1c2286bcbc 100644 --- a/src/tools/miri/src/shims/windows/thread.rs +++ b/src/tools/miri/src/shims/windows/thread.rs @@ -65,15 +65,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let handle = this.read_scalar(handle_op)?; let timeout = this.read_scalar(timeout_op)?.to_u32()?; - let thread = match Handle::from_scalar(handle, this)? { - Some(Handle::Thread(thread)) => - match this.thread_id_try_from(thread) { - Ok(thread) => thread, - Err(_) => this.invalid_handle("WaitForSingleObject")?, - }, + let thread = match Handle::try_from_scalar(handle, this)? { + Ok(Handle::Thread(thread)) => thread, // Unlike on posix, the outcome of joining the current thread is not documented. // On current Windows, it just deadlocks. - Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.active_thread(), + Ok(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.active_thread(), _ => this.invalid_handle("WaitForSingleObject")?, }; diff --git a/src/tools/miri/src/shims/x86/aesni.rs b/src/tools/miri/src/shims/x86/aesni.rs index 33b1f27713ca9..4c6c1cefeb1df 100644 --- a/src/tools/miri/src/shims/x86/aesni.rs +++ b/src/tools/miri/src/shims/x86/aesni.rs @@ -132,7 +132,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Performs an AES round (given by `f`) on each 128-bit word of // `state` with the corresponding 128-bit key of `key`. fn aes_round<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, state: &OpTy<'tcx>, key: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, @@ -145,21 +145,20 @@ fn aes_round<'tcx>( assert_eq!(dest.layout.size.bytes() % 16, 0); let len = dest.layout.size.bytes() / 16; - let u128_array_layout = - this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u128, len))?; + let u128_array_layout = ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u128, len))?; - let state = state.transmute(u128_array_layout, this)?; - let key = key.transmute(u128_array_layout, this)?; - let dest = dest.transmute(u128_array_layout, this)?; + let state = state.transmute(u128_array_layout, ecx)?; + let key = key.transmute(u128_array_layout, ecx)?; + let dest = dest.transmute(u128_array_layout, ecx)?; for i in 0..len { - let state = this.read_scalar(&this.project_index(&state, i)?)?.to_u128()?; - let key = this.read_scalar(&this.project_index(&key, i)?)?.to_u128()?; - let dest = this.project_index(&dest, i)?; + let state = ecx.read_scalar(&ecx.project_index(&state, i)?)?.to_u128()?; + let key = ecx.read_scalar(&ecx.project_index(&key, i)?)?.to_u128()?; + let dest = ecx.project_index(&dest, i)?; let res = f(state, key); - this.write_scalar(Scalar::from_u128(res), &dest)?; + ecx.write_scalar(Scalar::from_u128(res), &dest)?; } interp_ok(()) diff --git a/src/tools/miri/src/shims/x86/gfni.rs b/src/tools/miri/src/shims/x86/gfni.rs index 7b92d422cc535..92010345f55dd 100644 --- a/src/tools/miri/src/shims/x86/gfni.rs +++ b/src/tools/miri/src/shims/x86/gfni.rs @@ -75,21 +75,21 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { /// If `inverse` is set, then the inverse transformation with respect to the reduction polynomial /// x^8 + x^4 + x^3 + x + 1 is performed instead. fn affine_transform<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, imm8: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, inverse: bool, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, right_len); assert_eq!(dest_len, left_len); - let imm8 = this.read_scalar(imm8)?.to_u8()?; + let imm8 = ecx.read_scalar(imm8)?.to_u8()?; // Each 8x8 bit matrix gets multiplied with eight bit vectors. // Therefore, the iteration is done in chunks of eight. @@ -98,13 +98,13 @@ fn affine_transform<'tcx>( let mut matrix = [0u8; 8]; for j in 0..8 { matrix[usize::try_from(j).unwrap()] = - this.read_scalar(&this.project_index(&right, i.wrapping_add(j))?)?.to_u8()?; + ecx.read_scalar(&ecx.project_index(&right, i.wrapping_add(j))?)?.to_u8()?; } // Multiply the matrix with the vector and perform the addition. for j in 0..8 { let index = i.wrapping_add(j); - let left = this.read_scalar(&this.project_index(&left, index)?)?.to_u8()?; + let left = ecx.read_scalar(&ecx.project_index(&left, index)?)?.to_u8()?; let left = if inverse { TABLE[usize::from(left)] } else { left }; let mut res = 0; @@ -124,8 +124,8 @@ fn affine_transform<'tcx>( // Perform the addition. res ^= imm8; - let dest = this.project_index(&dest, index)?; - this.write_scalar(Scalar::from_u8(res), &dest)?; + let dest = ecx.project_index(&dest, index)?; + ecx.write_scalar(Scalar::from_u8(res), &dest)?; } } diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index 433e9e966f2a2..3e02a4b363769 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -1,4 +1,3 @@ -use rand::Rng as _; use rustc_abi::{ExternAbi, Size}; use rustc_apfloat::Float; use rustc_apfloat::ieee::Single; @@ -226,7 +225,7 @@ impl FloatBinOp { /// Convert from the `imm` argument used to specify the comparison /// operation in intrinsics such as `llvm.x86.sse.cmp.ss`. fn cmp_from_imm<'tcx>( - this: &crate::MiriInterpCx<'tcx>, + ecx: &crate::MiriInterpCx<'tcx>, imm: i8, intrinsic: Symbol, ) -> InterpResult<'tcx, Self> { @@ -260,7 +259,7 @@ impl FloatBinOp { }; // When bit 3 is 1 (only possible in AVX), unord is toggled. if imm & 0b1000 != 0 { - this.expect_target_feature_for_intrinsic(intrinsic, "avx")?; + ecx.expect_target_feature_for_intrinsic(intrinsic, "avx")?; unord = !unord; } interp_ok(Self::Cmp { gt, lt, eq, unord }) @@ -327,28 +326,28 @@ fn bin_op_float<'tcx, F: rustc_apfloat::Float>( /// Performs `which` operation on the first component of `left` and `right` /// and copies the other components from `left`. The result is stored in `dest`. fn bin_op_simd_float_first<'tcx, F: rustc_apfloat::Float>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, which: FloatBinOp, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); assert_eq!(dest_len, right_len); let res0 = bin_op_float::( which, - &this.read_immediate(&this.project_index(&left, 0)?)?, - &this.read_immediate(&this.project_index(&right, 0)?)?, + &ecx.read_immediate(&ecx.project_index(&left, 0)?)?, + &ecx.read_immediate(&ecx.project_index(&right, 0)?)?, )?; - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + ecx.write_scalar(res0, &ecx.project_index(&dest, 0)?)?; for i in 1..dest_len { - this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?; + ecx.copy_op(&ecx.project_index(&left, i)?, &ecx.project_index(&dest, i)?)?; } interp_ok(()) @@ -357,26 +356,26 @@ fn bin_op_simd_float_first<'tcx, F: rustc_apfloat::Float>( /// Performs `which` operation on each component of `left` and /// `right`, storing the result is stored in `dest`. fn bin_op_simd_float_all<'tcx, F: rustc_apfloat::Float>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, which: FloatBinOp, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); assert_eq!(dest_len, right_len); for i in 0..dest_len { - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let right = this.read_immediate(&this.project_index(&right, i)?)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.read_immediate(&ecx.project_index(&left, i)?)?; + let right = ecx.read_immediate(&ecx.project_index(&right, i)?)?; + let dest = ecx.project_index(&dest, i)?; let res = bin_op_float::(which, &left, &right)?; - this.write_scalar(res, &dest)?; + ecx.write_scalar(res, &dest)?; } interp_ok(()) @@ -398,7 +397,7 @@ enum FloatUnaryOp { /// Performs `which` scalar operation on `op` and returns the result. fn unary_op_f32<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, which: FloatUnaryOp, op: &ImmTy<'tcx>, ) -> InterpResult<'tcx, Scalar> { @@ -408,56 +407,38 @@ fn unary_op_f32<'tcx>( let div = (Single::from_u128(1).value / op).value; // Apply a relative error with a magnitude on the order of 2^-12 to simulate the // inaccuracy of RCP. - let res = apply_random_float_error(this, div, -12); + let res = math::apply_random_float_error(ecx, div, -12); interp_ok(Scalar::from_f32(res)) } FloatUnaryOp::Rsqrt => { - let op = op.to_scalar().to_u32()?; - // FIXME using host floats - let sqrt = Single::from_bits(f32::from_bits(op).sqrt().to_bits().into()); - let rsqrt = (Single::from_u128(1).value / sqrt).value; + let op = op.to_scalar().to_f32()?; + let rsqrt = (Single::from_u128(1).value / math::sqrt(op)).value; // Apply a relative error with a magnitude on the order of 2^-12 to simulate the // inaccuracy of RSQRT. - let res = apply_random_float_error(this, rsqrt, -12); + let res = math::apply_random_float_error(ecx, rsqrt, -12); interp_ok(Scalar::from_f32(res)) } } } -/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). -#[expect(clippy::arithmetic_side_effects)] // floating point arithmetic cannot panic -fn apply_random_float_error( - this: &mut crate::MiriInterpCx<'_>, - val: F, - err_scale: i32, -) -> F { - let rng = this.machine.rng.get_mut(); - // generates rand(0, 2^64) * 2^(scale - 64) = rand(0, 1) * 2^scale - let err = F::from_u128(rng.gen::().into()).value.scalbn(err_scale.strict_sub(64)); - // give it a random sign - let err = if rng.gen::() { -err } else { err }; - // multiple the value with (1+err) - (val * (F::from_u128(1).value + err).value).value -} - /// Performs `which` operation on the first component of `op` and copies /// the other components. The result is stored in `dest`. fn unary_op_ss<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, which: FloatUnaryOp, op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (op, op_len) = this.project_to_simd(op)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, op_len); - let res0 = unary_op_f32(this, which, &this.read_immediate(&this.project_index(&op, 0)?)?)?; - this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + let res0 = unary_op_f32(ecx, which, &ecx.read_immediate(&ecx.project_index(&op, 0)?)?)?; + ecx.write_scalar(res0, &ecx.project_index(&dest, 0)?)?; for i in 1..dest_len { - this.copy_op(&this.project_index(&op, i)?, &this.project_index(&dest, i)?)?; + ecx.copy_op(&ecx.project_index(&op, i)?, &ecx.project_index(&dest, i)?)?; } interp_ok(()) @@ -466,22 +447,22 @@ fn unary_op_ss<'tcx>( /// Performs `which` operation on each component of `op`, storing the /// result is stored in `dest`. fn unary_op_ps<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, which: FloatUnaryOp, op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (op, op_len) = this.project_to_simd(op)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, op_len); for i in 0..dest_len { - let op = this.read_immediate(&this.project_index(&op, i)?)?; - let dest = this.project_index(&dest, i)?; + let op = ecx.read_immediate(&ecx.project_index(&op, i)?)?; + let dest = ecx.project_index(&dest, i)?; - let res = unary_op_f32(this, which, &op)?; - this.write_scalar(res, &dest)?; + let res = unary_op_f32(ecx, which, &op)?; + ecx.write_scalar(res, &dest)?; } interp_ok(()) @@ -503,14 +484,14 @@ enum ShiftOp { /// For arithmetic right-shifts, when right is larger than BITS - 1, the sign /// bit is copied to all bits. fn shift_simd_by_scalar<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, which: ShiftOp, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); // `right` may have a different length, and we only care about its @@ -520,11 +501,11 @@ fn shift_simd_by_scalar<'tcx>( // by checked_{shl,shr} (u32). // It is ok to saturate the value to u32::MAX because any value // above BITS - 1 will produce the same result. - let shift = u32::try_from(extract_first_u64(this, right)?).unwrap_or(u32::MAX); + let shift = u32::try_from(extract_first_u64(ecx, right)?).unwrap_or(u32::MAX); for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.read_scalar(&ecx.project_index(&left, i)?)?; + let dest = ecx.project_index(&dest, i)?; let res = match which { ShiftOp::Left => { @@ -547,7 +528,7 @@ fn shift_simd_by_scalar<'tcx>( Scalar::from_int(res, dest.layout.size) } }; - this.write_scalar(res, &dest)?; + ecx.write_scalar(res, &dest)?; } interp_ok(()) @@ -559,23 +540,23 @@ fn shift_simd_by_scalar<'tcx>( /// For arithmetic right-shifts, when right is larger than BITS - 1, the sign /// bit is copied to all bits. fn shift_simd_by_simd<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, which: ShiftOp, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); assert_eq!(dest_len, right_len); for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?; - let right = this.read_scalar(&this.project_index(&right, i)?)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.read_scalar(&ecx.project_index(&left, i)?)?; + let right = ecx.read_scalar(&ecx.project_index(&right, i)?)?; + let dest = ecx.project_index(&dest, i)?; // It is ok to saturate the value to u32::MAX because any value // above BITS - 1 will produce the same result. @@ -602,7 +583,7 @@ fn shift_simd_by_simd<'tcx>( Scalar::from_int(res, dest.layout.size) } }; - this.write_scalar(res, &dest)?; + ecx.write_scalar(res, &dest)?; } interp_ok(()) @@ -611,44 +592,44 @@ fn shift_simd_by_simd<'tcx>( /// Takes a 128-bit vector, transmutes it to `[u64; 2]` and extracts /// the first value. fn extract_first_u64<'tcx>( - this: &crate::MiriInterpCx<'tcx>, + ecx: &crate::MiriInterpCx<'tcx>, op: &OpTy<'tcx>, ) -> InterpResult<'tcx, u64> { // Transmute vector to `[u64; 2]` - let array_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u64, 2))?; - let op = op.transmute(array_layout, this)?; + let array_layout = ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u64, 2))?; + let op = op.transmute(array_layout, ecx)?; // Get the first u64 from the array - this.read_scalar(&this.project_index(&op, 0)?)?.to_u64() + ecx.read_scalar(&ecx.project_index(&op, 0)?)?.to_u64() } // Rounds the first element of `right` according to `rounding` // and copies the remaining elements from `left`. fn round_first<'tcx, F: rustc_apfloat::Float>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, rounding: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); assert_eq!(dest_len, right_len); - let rounding = rounding_from_imm(this.read_scalar(rounding)?.to_i32()?)?; + let rounding = rounding_from_imm(ecx.read_scalar(rounding)?.to_i32()?)?; - let op0: F = this.read_scalar(&this.project_index(&right, 0)?)?.to_float()?; + let op0: F = ecx.read_scalar(&ecx.project_index(&right, 0)?)?.to_float()?; let res = op0.round_to_integral(rounding).value; - this.write_scalar( + ecx.write_scalar( Scalar::from_uint(res.to_bits(), Size::from_bits(F::BITS)), - &this.project_index(&dest, 0)?, + &ecx.project_index(&dest, 0)?, )?; for i in 1..dest_len { - this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?; + ecx.copy_op(&ecx.project_index(&left, i)?, &ecx.project_index(&dest, i)?)?; } interp_ok(()) @@ -656,24 +637,24 @@ fn round_first<'tcx, F: rustc_apfloat::Float>( // Rounds all elements of `op` according to `rounding`. fn round_all<'tcx, F: rustc_apfloat::Float>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, op: &OpTy<'tcx>, rounding: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (op, op_len) = this.project_to_simd(op)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, op_len); - let rounding = rounding_from_imm(this.read_scalar(rounding)?.to_i32()?)?; + let rounding = rounding_from_imm(ecx.read_scalar(rounding)?.to_i32()?)?; for i in 0..dest_len { - let op: F = this.read_scalar(&this.project_index(&op, i)?)?.to_float()?; + let op: F = ecx.read_scalar(&ecx.project_index(&op, i)?)?.to_float()?; let res = op.round_to_integral(rounding).value; - this.write_scalar( + ecx.write_scalar( Scalar::from_uint(res.to_bits(), Size::from_bits(F::BITS)), - &this.project_index(&dest, i)?, + &ecx.project_index(&dest, i)?, )?; } @@ -708,31 +689,31 @@ fn rounding_from_imm<'tcx>(rounding: i32) -> InterpResult<'tcx, rustc_apfloat::R /// If `op` has more elements than `dest`, extra elements are ignored. If `op` /// has less elements than `dest`, the rest is filled with zeros. fn convert_float_to_int<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, op: &OpTy<'tcx>, rnd: rustc_apfloat::Round, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (op, op_len) = this.project_to_simd(op)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; // Output must be *signed* integers. - assert!(matches!(dest.layout.field(this, 0).ty.kind(), ty::Int(_))); + assert!(matches!(dest.layout.field(ecx, 0).ty.kind(), ty::Int(_))); for i in 0..op_len.min(dest_len) { - let op = this.read_immediate(&this.project_index(&op, i)?)?; - let dest = this.project_index(&dest, i)?; + let op = ecx.read_immediate(&ecx.project_index(&op, i)?)?; + let dest = ecx.project_index(&dest, i)?; - let res = this.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| { + let res = ecx.float_to_int_checked(&op, dest.layout, rnd)?.unwrap_or_else(|| { // Fallback to minimum according to SSE/AVX semantics. ImmTy::from_int(dest.layout.size.signed_int_min(), dest.layout) }); - this.write_immediate(*res, &dest)?; + ecx.write_immediate(*res, &dest)?; } // Fill remainder with zeros for i in op_len..dest_len { - let dest = this.project_index(&dest, i)?; - this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; + let dest = ecx.project_index(&dest, i)?; + ecx.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; } interp_ok(()) @@ -743,26 +724,26 @@ fn convert_float_to_int<'tcx>( /// In case of overflow (when the operand is the minimum value), the operation /// will wrap around. fn int_abs<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (op, op_len) = this.project_to_simd(op)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(op_len, dest_len); - let zero = ImmTy::from_int(0, op.layout.field(this, 0)); + let zero = ImmTy::from_int(0, op.layout.field(ecx, 0)); for i in 0..dest_len { - let op = this.read_immediate(&this.project_index(&op, i)?)?; - let dest = this.project_index(&dest, i)?; + let op = ecx.read_immediate(&ecx.project_index(&op, i)?)?; + let dest = ecx.project_index(&dest, i)?; - let lt_zero = this.binary_op(mir::BinOp::Lt, &op, &zero)?; + let lt_zero = ecx.binary_op(mir::BinOp::Lt, &op, &zero)?; let res = - if lt_zero.to_scalar().to_bool()? { this.unary_op(mir::UnOp::Neg, &op)? } else { op }; + if lt_zero.to_scalar().to_bool()? { ecx.unary_op(mir::UnOp::Neg, &op)? } else { op }; - this.write_immediate(*res, &dest)?; + ecx.write_immediate(*res, &dest)?; } interp_ok(()) @@ -776,25 +757,25 @@ fn int_abs<'tcx>( /// * The third element is the `op` vector split into chunks, i.e, it's /// type is `[[T; M]; N]` where `T` is the element type of `op`. fn split_simd_to_128bit_chunks<'tcx, P: Projectable<'tcx, Provenance>>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, op: &P, ) -> InterpResult<'tcx, (u64, u64, P)> { let simd_layout = op.layout(); - let (simd_len, element_ty) = simd_layout.ty.simd_size_and_type(this.tcx.tcx); + let (simd_len, element_ty) = simd_layout.ty.simd_size_and_type(ecx.tcx.tcx); assert_eq!(simd_layout.size.bits() % 128, 0); let num_chunks = simd_layout.size.bits() / 128; let items_per_chunk = simd_len.strict_div(num_chunks); // Transmute to `[[T; items_per_chunk]; num_chunks]` - let chunked_layout = this + let chunked_layout = ecx .layout_of(Ty::new_array( - this.tcx.tcx, - Ty::new_array(this.tcx.tcx, element_ty, items_per_chunk), + ecx.tcx.tcx, + Ty::new_array(ecx.tcx.tcx, element_ty, items_per_chunk), num_chunks, )) .unwrap(); - let chunked_op = op.transmute(chunked_layout, this)?; + let chunked_op = op.transmute(chunked_layout, ecx)?; interp_ok((num_chunks, items_per_chunk, chunked_op)) } @@ -809,7 +790,7 @@ fn split_simd_to_128bit_chunks<'tcx, P: Projectable<'tcx, Provenance>>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn horizontal_bin_op<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, which: mir::BinOp, saturating: bool, left: &OpTy<'tcx>, @@ -819,15 +800,15 @@ fn horizontal_bin_op<'tcx>( assert_eq!(left.layout, dest.layout); assert_eq!(right.layout, dest.layout); - let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; - let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; - let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?; + let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(ecx, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(ecx, right)?; + let (_, _, dest) = split_simd_to_128bit_chunks(ecx, dest)?; let middle = items_per_chunk / 2; for i in 0..num_chunks { - let left = this.project_index(&left, i)?; - let right = this.project_index(&right, i)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.project_index(&left, i)?; + let right = ecx.project_index(&right, i)?; + let dest = ecx.project_index(&dest, i)?; for j in 0..items_per_chunk { // `j` is the index in `dest` @@ -835,16 +816,16 @@ fn horizontal_bin_op<'tcx>( let (k, src) = if j < middle { (j, &left) } else { (j.strict_sub(middle), &right) }; // `base_i` is the index of the first item of the 2-item chunk in `src` let base_i = k.strict_mul(2); - let lhs = this.read_immediate(&this.project_index(src, base_i)?)?; - let rhs = this.read_immediate(&this.project_index(src, base_i.strict_add(1))?)?; + let lhs = ecx.read_immediate(&ecx.project_index(src, base_i)?)?; + let rhs = ecx.read_immediate(&ecx.project_index(src, base_i.strict_add(1))?)?; let res = if saturating { - Immediate::from(this.saturating_arith(which, &lhs, &rhs)?) + Immediate::from(ecx.saturating_arith(which, &lhs, &rhs)?) } else { - *this.binary_op(which, &lhs, &rhs)? + *ecx.binary_op(which, &lhs, &rhs)? }; - this.write_immediate(res, &this.project_index(&dest, j)?)?; + ecx.write_immediate(res, &ecx.project_index(&dest, j)?)?; } } @@ -860,7 +841,7 @@ fn horizontal_bin_op<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit blocks of `left` and `right`). fn conditional_dot_product<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, imm: &OpTy<'tcx>, @@ -869,20 +850,20 @@ fn conditional_dot_product<'tcx>( assert_eq!(left.layout, dest.layout); assert_eq!(right.layout, dest.layout); - let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; - let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; - let (_, _, dest) = split_simd_to_128bit_chunks(this, dest)?; + let (num_chunks, items_per_chunk, left) = split_simd_to_128bit_chunks(ecx, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(ecx, right)?; + let (_, _, dest) = split_simd_to_128bit_chunks(ecx, dest)?; - let element_layout = left.layout.field(this, 0).field(this, 0); + let element_layout = left.layout.field(ecx, 0).field(ecx, 0); assert!(items_per_chunk <= 4); // `imm` is a `u8` for SSE4.1 or an `i32` for AVX :/ - let imm = this.read_scalar(imm)?.to_uint(imm.layout.size)?; + let imm = ecx.read_scalar(imm)?.to_uint(imm.layout.size)?; for i in 0..num_chunks { - let left = this.project_index(&left, i)?; - let right = this.project_index(&right, i)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.project_index(&left, i)?; + let right = ecx.project_index(&right, i)?; + let dest = ecx.project_index(&dest, i)?; // Calculate dot product // Elements are floating point numbers, but we can use `from_int` @@ -890,22 +871,22 @@ fn conditional_dot_product<'tcx>( let mut sum = ImmTy::from_int(0u8, element_layout); for j in 0..items_per_chunk { if imm & (1 << j.strict_add(4)) != 0 { - let left = this.read_immediate(&this.project_index(&left, j)?)?; - let right = this.read_immediate(&this.project_index(&right, j)?)?; + let left = ecx.read_immediate(&ecx.project_index(&left, j)?)?; + let right = ecx.read_immediate(&ecx.project_index(&right, j)?)?; - let mul = this.binary_op(mir::BinOp::Mul, &left, &right)?; - sum = this.binary_op(mir::BinOp::Add, &sum, &mul)?; + let mul = ecx.binary_op(mir::BinOp::Mul, &left, &right)?; + sum = ecx.binary_op(mir::BinOp::Add, &sum, &mul)?; } } // Write to destination (conditioned to imm) for j in 0..items_per_chunk { - let dest = this.project_index(&dest, j)?; + let dest = ecx.project_index(&dest, j)?; if imm & (1 << j) != 0 { - this.write_immediate(*sum, &dest)?; + ecx.write_immediate(*sum, &dest)?; } else { - this.write_scalar(Scalar::from_int(0u8, element_layout.size), &dest)?; + ecx.write_scalar(Scalar::from_int(0u8, element_layout.size), &dest)?; } } } @@ -918,25 +899,25 @@ fn conditional_dot_product<'tcx>( /// The first is true when all the bits of `op & mask` are zero. /// The second is true when `(op & mask) == mask` fn test_bits_masked<'tcx>( - this: &crate::MiriInterpCx<'tcx>, + ecx: &crate::MiriInterpCx<'tcx>, op: &OpTy<'tcx>, mask: &OpTy<'tcx>, ) -> InterpResult<'tcx, (bool, bool)> { assert_eq!(op.layout, mask.layout); - let (op, op_len) = this.project_to_simd(op)?; - let (mask, mask_len) = this.project_to_simd(mask)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (mask, mask_len) = ecx.project_to_simd(mask)?; assert_eq!(op_len, mask_len); let mut all_zero = true; let mut masked_set = true; for i in 0..op_len { - let op = this.project_index(&op, i)?; - let mask = this.project_index(&mask, i)?; + let op = ecx.project_index(&op, i)?; + let mask = ecx.project_index(&mask, i)?; - let op = this.read_scalar(&op)?.to_uint(op.layout.size)?; - let mask = this.read_scalar(&mask)?.to_uint(mask.layout.size)?; + let op = ecx.read_scalar(&op)?.to_uint(op.layout.size)?; + let mask = ecx.read_scalar(&mask)?.to_uint(mask.layout.size)?; all_zero &= (op & mask) == 0; masked_set &= (op & mask) == mask; } @@ -949,27 +930,27 @@ fn test_bits_masked<'tcx>( /// The first is true when the highest bit of each element of `op & mask` is zero. /// The second is true when the highest bit of each element of `!op & mask` is zero. fn test_high_bits_masked<'tcx>( - this: &crate::MiriInterpCx<'tcx>, + ecx: &crate::MiriInterpCx<'tcx>, op: &OpTy<'tcx>, mask: &OpTy<'tcx>, ) -> InterpResult<'tcx, (bool, bool)> { assert_eq!(op.layout, mask.layout); - let (op, op_len) = this.project_to_simd(op)?; - let (mask, mask_len) = this.project_to_simd(mask)?; + let (op, op_len) = ecx.project_to_simd(op)?; + let (mask, mask_len) = ecx.project_to_simd(mask)?; assert_eq!(op_len, mask_len); - let high_bit_offset = op.layout.field(this, 0).size.bits().strict_sub(1); + let high_bit_offset = op.layout.field(ecx, 0).size.bits().strict_sub(1); let mut direct = true; let mut negated = true; for i in 0..op_len { - let op = this.project_index(&op, i)?; - let mask = this.project_index(&mask, i)?; + let op = ecx.project_index(&op, i)?; + let mask = ecx.project_index(&mask, i)?; - let op = this.read_scalar(&op)?.to_uint(op.layout.size)?; - let mask = this.read_scalar(&mask)?.to_uint(mask.layout.size)?; + let op = ecx.read_scalar(&op)?.to_uint(op.layout.size)?; + let mask = ecx.read_scalar(&mask)?.to_uint(mask.layout.size)?; direct &= (op & mask) >> high_bit_offset == 0; negated &= (!op & mask) >> high_bit_offset == 0; } @@ -980,30 +961,30 @@ fn test_high_bits_masked<'tcx>( /// Conditionally loads from `ptr` according the high bit of each /// element of `mask`. `ptr` does not need to be aligned. fn mask_load<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, ptr: &OpTy<'tcx>, mask: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (mask, mask_len) = this.project_to_simd(mask)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (mask, mask_len) = ecx.project_to_simd(mask)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, mask_len); - let mask_item_size = mask.layout.field(this, 0).size; + let mask_item_size = mask.layout.field(ecx, 0).size; let high_bit_offset = mask_item_size.bits().strict_sub(1); - let ptr = this.read_pointer(ptr)?; + let ptr = ecx.read_pointer(ptr)?; for i in 0..dest_len { - let mask = this.project_index(&mask, i)?; - let dest = this.project_index(&dest, i)?; + let mask = ecx.project_index(&mask, i)?; + let dest = ecx.project_index(&dest, i)?; - if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { - let ptr = ptr.wrapping_offset(dest.layout.size * i, &this.tcx); + if ecx.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { + let ptr = ptr.wrapping_offset(dest.layout.size * i, &ecx.tcx); // Unaligned copy, which is what we want. - this.mem_copy(ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?; + ecx.mem_copy(ptr, dest.ptr(), dest.layout.size, /*nonoverlapping*/ true)?; } else { - this.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; + ecx.write_scalar(Scalar::from_int(0, dest.layout.size), &dest)?; } } @@ -1013,31 +994,31 @@ fn mask_load<'tcx>( /// Conditionally stores into `ptr` according the high bit of each /// element of `mask`. `ptr` does not need to be aligned. fn mask_store<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, ptr: &OpTy<'tcx>, mask: &OpTy<'tcx>, value: &OpTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (mask, mask_len) = this.project_to_simd(mask)?; - let (value, value_len) = this.project_to_simd(value)?; + let (mask, mask_len) = ecx.project_to_simd(mask)?; + let (value, value_len) = ecx.project_to_simd(value)?; assert_eq!(value_len, mask_len); - let mask_item_size = mask.layout.field(this, 0).size; + let mask_item_size = mask.layout.field(ecx, 0).size; let high_bit_offset = mask_item_size.bits().strict_sub(1); - let ptr = this.read_pointer(ptr)?; + let ptr = ecx.read_pointer(ptr)?; for i in 0..value_len { - let mask = this.project_index(&mask, i)?; - let value = this.project_index(&value, i)?; + let mask = ecx.project_index(&mask, i)?; + let value = ecx.project_index(&value, i)?; - if this.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { + if ecx.read_scalar(&mask)?.to_uint(mask_item_size)? >> high_bit_offset != 0 { // *Non-inbounds* pointer arithmetic to compute the destination. // (That's why we can't use a place projection.) - let ptr = ptr.wrapping_offset(value.layout.size * i, &this.tcx); + let ptr = ptr.wrapping_offset(value.layout.size * i, &ecx.tcx); // Deref the pointer *unaligned*, and do the copy. - let dest = this.ptr_to_mplace_unaligned(ptr, value.layout); - this.copy_op(&value, &dest)?; + let dest = ecx.ptr_to_mplace_unaligned(ptr, value.layout); + ecx.copy_op(&value, &dest)?; } } @@ -1056,7 +1037,7 @@ fn mask_store<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn mpsadbw<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, imm: &OpTy<'tcx>, @@ -1065,13 +1046,13 @@ fn mpsadbw<'tcx>( assert_eq!(left.layout, right.layout); assert_eq!(left.layout.size, dest.layout.size); - let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; - let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; - let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(this, dest)?; + let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(ecx, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(ecx, right)?; + let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(ecx, dest)?; assert_eq!(op_items_per_chunk, dest_items_per_chunk.strict_mul(2)); - let imm = this.read_scalar(imm)?.to_uint(imm.layout.size)?; + let imm = ecx.read_scalar(imm)?.to_uint(imm.layout.size)?; // Bit 2 of `imm` specifies the offset for indices of `left`. // The offset is 0 when the bit is 0 or 4 when the bit is 1. let left_offset = u64::try_from((imm >> 2) & 1).unwrap().strict_mul(4); @@ -1080,23 +1061,23 @@ fn mpsadbw<'tcx>( let right_offset = u64::try_from(imm & 0b11).unwrap().strict_mul(4); for i in 0..num_chunks { - let left = this.project_index(&left, i)?; - let right = this.project_index(&right, i)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.project_index(&left, i)?; + let right = ecx.project_index(&right, i)?; + let dest = ecx.project_index(&dest, i)?; for j in 0..dest_items_per_chunk { let left_offset = left_offset.strict_add(j); let mut res: u16 = 0; for k in 0..4 { - let left = this - .read_scalar(&this.project_index(&left, left_offset.strict_add(k))?)? + let left = ecx + .read_scalar(&ecx.project_index(&left, left_offset.strict_add(k))?)? .to_u8()?; - let right = this - .read_scalar(&this.project_index(&right, right_offset.strict_add(k))?)? + let right = ecx + .read_scalar(&ecx.project_index(&right, right_offset.strict_add(k))?)? .to_u8()?; res = res.strict_add(left.abs_diff(right).into()); } - this.write_scalar(Scalar::from_u16(res), &this.project_index(&dest, j)?)?; + ecx.write_scalar(Scalar::from_u16(res), &ecx.project_index(&dest, j)?)?; } } @@ -1111,22 +1092,22 @@ fn mpsadbw<'tcx>( /// /// fn pmulhrsw<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); assert_eq!(dest_len, right_len); for i in 0..dest_len { - let left = this.read_scalar(&this.project_index(&left, i)?)?.to_i16()?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_i16()?; - let dest = this.project_index(&dest, i)?; + let left = ecx.read_scalar(&ecx.project_index(&left, i)?)?.to_i16()?; + let right = ecx.read_scalar(&ecx.project_index(&right, i)?)?.to_i16()?; + let dest = ecx.project_index(&dest, i)?; let res = (i32::from(left).strict_mul(right.into()) >> 14).strict_add(1) >> 1; @@ -1135,7 +1116,7 @@ fn pmulhrsw<'tcx>( #[expect(clippy::cast_possible_truncation)] let res = res as i16; - this.write_scalar(Scalar::from_i16(res), &dest)?; + ecx.write_scalar(Scalar::from_i16(res), &dest)?; } interp_ok(()) @@ -1152,7 +1133,7 @@ fn pmulhrsw<'tcx>( /// /// fn pclmulqdq<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, imm8: &OpTy<'tcx>, @@ -1166,14 +1147,14 @@ fn pclmulqdq<'tcx>( // Transmute the input into arrays of `[u64; len]`. // Transmute the output into an array of `[u128, len / 2]`. - let src_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u64, len))?; - let dest_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u128, len / 2))?; + let src_layout = ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u64, len))?; + let dest_layout = ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u128, len / 2))?; - let left = left.transmute(src_layout, this)?; - let right = right.transmute(src_layout, this)?; - let dest = dest.transmute(dest_layout, this)?; + let left = left.transmute(src_layout, ecx)?; + let right = right.transmute(src_layout, ecx)?; + let dest = dest.transmute(dest_layout, ecx)?; - let imm8 = this.read_scalar(imm8)?.to_u8()?; + let imm8 = ecx.read_scalar(imm8)?.to_u8()?; for i in 0..(len / 2) { let lo = i.strict_mul(2); @@ -1181,11 +1162,11 @@ fn pclmulqdq<'tcx>( // select the 64-bit integer from left that the user specified (low or high) let index = if (imm8 & 0x01) == 0 { lo } else { hi }; - let left = this.read_scalar(&this.project_index(&left, index)?)?.to_u64()?; + let left = ecx.read_scalar(&ecx.project_index(&left, index)?)?.to_u64()?; // select the 64-bit integer from right that the user specified (low or high) let index = if (imm8 & 0x10) == 0 { lo } else { hi }; - let right = this.read_scalar(&this.project_index(&right, index)?)?.to_u64()?; + let right = ecx.read_scalar(&ecx.project_index(&right, index)?)?.to_u64()?; // Perform carry-less multiplication. // @@ -1203,8 +1184,8 @@ fn pclmulqdq<'tcx>( } } - let dest = this.project_index(&dest, i)?; - this.write_scalar(Scalar::from_u128(result), &dest)?; + let dest = ecx.project_index(&dest, i)?; + ecx.write_scalar(Scalar::from_u128(result), &dest)?; } interp_ok(()) @@ -1218,7 +1199,7 @@ fn pclmulqdq<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn pack_generic<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, @@ -1227,28 +1208,28 @@ fn pack_generic<'tcx>( assert_eq!(left.layout, right.layout); assert_eq!(left.layout.size, dest.layout.size); - let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(this, left)?; - let (_, _, right) = split_simd_to_128bit_chunks(this, right)?; - let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(this, dest)?; + let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(ecx, left)?; + let (_, _, right) = split_simd_to_128bit_chunks(ecx, right)?; + let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(ecx, dest)?; assert_eq!(dest_items_per_chunk, op_items_per_chunk.strict_mul(2)); for i in 0..num_chunks { - let left = this.project_index(&left, i)?; - let right = this.project_index(&right, i)?; - let dest = this.project_index(&dest, i)?; + let left = ecx.project_index(&left, i)?; + let right = ecx.project_index(&right, i)?; + let dest = ecx.project_index(&dest, i)?; for j in 0..op_items_per_chunk { - let left = this.read_scalar(&this.project_index(&left, j)?)?; - let right = this.read_scalar(&this.project_index(&right, j)?)?; - let left_dest = this.project_index(&dest, j)?; - let right_dest = this.project_index(&dest, j.strict_add(op_items_per_chunk))?; + let left = ecx.read_scalar(&ecx.project_index(&left, j)?)?; + let right = ecx.read_scalar(&ecx.project_index(&right, j)?)?; + let left_dest = ecx.project_index(&dest, j)?; + let right_dest = ecx.project_index(&dest, j.strict_add(op_items_per_chunk))?; let left_res = f(left)?; let right_res = f(right)?; - this.write_scalar(left_res, &left_dest)?; - this.write_scalar(right_res, &right_dest)?; + ecx.write_scalar(left_res, &left_dest)?; + ecx.write_scalar(right_res, &right_dest)?; } } @@ -1262,12 +1243,12 @@ fn pack_generic<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn packsswb<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - pack_generic(this, left, right, dest, |op| { + pack_generic(ecx, left, right, dest, |op| { let op = op.to_i16()?; let res = i8::try_from(op).unwrap_or(if op < 0 { i8::MIN } else { i8::MAX }); interp_ok(Scalar::from_i8(res)) @@ -1281,12 +1262,12 @@ fn packsswb<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn packuswb<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - pack_generic(this, left, right, dest, |op| { + pack_generic(ecx, left, right, dest, |op| { let op = op.to_i16()?; let res = u8::try_from(op).unwrap_or(if op < 0 { 0 } else { u8::MAX }); interp_ok(Scalar::from_u8(res)) @@ -1300,12 +1281,12 @@ fn packuswb<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn packssdw<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - pack_generic(this, left, right, dest, |op| { + pack_generic(ecx, left, right, dest, |op| { let op = op.to_i32()?; let res = i16::try_from(op).unwrap_or(if op < 0 { i16::MIN } else { i16::MAX }); interp_ok(Scalar::from_i16(res)) @@ -1319,12 +1300,12 @@ fn packssdw<'tcx>( /// the is i-th 128-bit chunk of `dest` is calculated with the i-th /// 128-bit chunks of `left` and `right`). fn packusdw<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - pack_generic(this, left, right, dest, |op| { + pack_generic(ecx, left, right, dest, |op| { let op = op.to_i32()?; let res = u16::try_from(op).unwrap_or(if op < 0 { 0 } else { u16::MAX }); interp_ok(Scalar::from_u16(res)) @@ -1336,27 +1317,27 @@ fn packusdw<'tcx>( /// is written to the corresponding output element. /// In other words, multiplies `left` with `right.signum()`. fn psign<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, left: &OpTy<'tcx>, right: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> { - let (left, left_len) = this.project_to_simd(left)?; - let (right, right_len) = this.project_to_simd(right)?; - let (dest, dest_len) = this.project_to_simd(dest)?; + let (left, left_len) = ecx.project_to_simd(left)?; + let (right, right_len) = ecx.project_to_simd(right)?; + let (dest, dest_len) = ecx.project_to_simd(dest)?; assert_eq!(dest_len, left_len); assert_eq!(dest_len, right_len); for i in 0..dest_len { - let dest = this.project_index(&dest, i)?; - let left = this.read_immediate(&this.project_index(&left, i)?)?; - let right = this.read_scalar(&this.project_index(&right, i)?)?.to_int(dest.layout.size)?; + let dest = ecx.project_index(&dest, i)?; + let left = ecx.read_immediate(&ecx.project_index(&left, i)?)?; + let right = ecx.read_scalar(&ecx.project_index(&right, i)?)?.to_int(dest.layout.size)?; let res = - this.binary_op(mir::BinOp::Mul, &left, &ImmTy::from_int(right.signum(), dest.layout))?; + ecx.binary_op(mir::BinOp::Mul, &left, &ImmTy::from_int(right.signum(), dest.layout))?; - this.write_immediate(*res, &dest)?; + ecx.write_immediate(*res, &dest)?; } interp_ok(()) @@ -1366,7 +1347,7 @@ fn psign<'tcx>( /// of `op` and returns both the sum and the overflow bit. `op` is expected to be /// either one of `mir::BinOp::AddWithOverflow` and `mir::BinOp::SubWithOverflow`. fn carrying_add<'tcx>( - this: &mut crate::MiriInterpCx<'tcx>, + ecx: &mut crate::MiriInterpCx<'tcx>, cb_in: &OpTy<'tcx>, a: &OpTy<'tcx>, b: &OpTy<'tcx>, @@ -1374,13 +1355,13 @@ fn carrying_add<'tcx>( ) -> InterpResult<'tcx, (ImmTy<'tcx>, Scalar)> { assert!(op == mir::BinOp::AddWithOverflow || op == mir::BinOp::SubWithOverflow); - let cb_in = this.read_scalar(cb_in)?.to_u8()? != 0; - let a = this.read_immediate(a)?; - let b = this.read_immediate(b)?; + let cb_in = ecx.read_scalar(cb_in)?.to_u8()? != 0; + let a = ecx.read_immediate(a)?; + let b = ecx.read_immediate(b)?; - let (sum, overflow1) = this.binary_op(op, &a, &b)?.to_pair(this); + let (sum, overflow1) = ecx.binary_op(op, &a, &b)?.to_pair(ecx); let (sum, overflow2) = - this.binary_op(op, &sum, &ImmTy::from_uint(cb_in, a.layout))?.to_pair(this); + ecx.binary_op(op, &sum, &ImmTy::from_uint(cb_in, a.layout))?.to_pair(ecx); let cb_out = overflow1.to_scalar().to_bool()? | overflow2.to_scalar().to_bool()?; interp_ok((sum, Scalar::from_u8(cb_out.into()))) diff --git a/src/tools/miri/src/shims/x86/sha.rs b/src/tools/miri/src/shims/x86/sha.rs index 964204127d92c..f18ff1ec253b8 100644 --- a/src/tools/miri/src/shims/x86/sha.rs +++ b/src/tools/miri/src/shims/x86/sha.rs @@ -23,27 +23,27 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Prefix should have already been checked. let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sha").unwrap(); - fn read<'c>(this: &mut MiriInterpCx<'c>, reg: &OpTy<'c>) -> InterpResult<'c, [u32; 4]> { + fn read<'c>(ecx: &mut MiriInterpCx<'c>, reg: &OpTy<'c>) -> InterpResult<'c, [u32; 4]> { let mut res = [0; 4]; // We reverse the order because x86 is little endian but the copied implementation uses // big endian. for (i, dst) in res.iter_mut().rev().enumerate() { - let projected = &this.project_index(reg, i.try_into().unwrap())?; - *dst = this.read_scalar(projected)?.to_u32()? + let projected = &ecx.project_index(reg, i.try_into().unwrap())?; + *dst = ecx.read_scalar(projected)?.to_u32()? } interp_ok(res) } fn write<'c>( - this: &mut MiriInterpCx<'c>, + ecx: &mut MiriInterpCx<'c>, dest: &MPlaceTy<'c>, val: [u32; 4], ) -> InterpResult<'c, ()> { // We reverse the order because x86 is little endian but the copied implementation uses // big endian. for (i, part) in val.into_iter().rev().enumerate() { - let projected = &this.project_index(dest, i.try_into().unwrap())?; - this.write_scalar(Scalar::from_u32(part), projected)?; + let projected = &ecx.project_index(dest, i.try_into().unwrap())?; + ecx.write_scalar(Scalar::from_u32(part), projected)?; } interp_ok(()) } diff --git a/src/tools/miri/src/shims/x86/sse42.rs b/src/tools/miri/src/shims/x86/sse42.rs index cc7cfab504195..0b058a9911e2a 100644 --- a/src/tools/miri/src/shims/x86/sse42.rs +++ b/src/tools/miri/src/shims/x86/sse42.rs @@ -70,7 +70,7 @@ const USE_SIGNED: u8 = 2; /// For more information, see the Intel Software Developer's Manual, Vol. 2b, Chapter 4.1. #[expect(clippy::arithmetic_side_effects)] fn compare_strings<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, str1: &OpTy<'tcx>, str2: &OpTy<'tcx>, len: Option<(u64, u64)>, @@ -80,8 +80,8 @@ fn compare_strings<'tcx>( let (len1, len2) = if let Some(t) = len { t } else { - let len1 = implicit_len(this, str1, imm)?.unwrap_or(default_len); - let len2 = implicit_len(this, str2, imm)?.unwrap_or(default_len); + let len1 = implicit_len(ecx, str1, imm)?.unwrap_or(default_len); + let len2 = implicit_len(ecx, str2, imm)?.unwrap_or(default_len); (len1, len2) }; @@ -90,12 +90,12 @@ fn compare_strings<'tcx>( 0 => { // Equal any: Checks which characters of `str2` are inside `str1`. for i in 0..len2 { - let ch2 = this.read_immediate(&this.project_index(str2, i)?)?; + let ch2 = ecx.read_immediate(&ecx.project_index(str2, i)?)?; for j in 0..len1 { - let ch1 = this.read_immediate(&this.project_index(str1, j)?)?; + let ch1 = ecx.read_immediate(&ecx.project_index(str1, j)?)?; - let eq = this.binary_op(mir::BinOp::Eq, &ch1, &ch2)?; + let eq = ecx.binary_op(mir::BinOp::Eq, &ch1, &ch2)?; if eq.to_scalar().to_bool()? { result |= 1 << i; break; @@ -119,9 +119,9 @@ fn compare_strings<'tcx>( for i in 0..len2 { for j in (0..len1).step_by(2) { - let ch2 = get_ch(this.read_scalar(&this.project_index(str2, i)?)?)?; - let ch1_1 = get_ch(this.read_scalar(&this.project_index(str1, j)?)?)?; - let ch1_2 = get_ch(this.read_scalar(&this.project_index(str1, j + 1)?)?)?; + let ch2 = get_ch(ecx.read_scalar(&ecx.project_index(str2, i)?)?)?; + let ch1_1 = get_ch(ecx.read_scalar(&ecx.project_index(str1, j)?)?)?; + let ch1_2 = get_ch(ecx.read_scalar(&ecx.project_index(str1, j + 1)?)?)?; if ch1_1 <= ch2 && ch2 <= ch1_2 { result |= 1 << i; @@ -135,9 +135,9 @@ fn compare_strings<'tcx>( result ^= (1 << len1.max(len2)) - 1; for i in 0..len1.min(len2) { - let ch1 = this.read_immediate(&this.project_index(str1, i)?)?; - let ch2 = this.read_immediate(&this.project_index(str2, i)?)?; - let eq = this.binary_op(mir::BinOp::Eq, &ch1, &ch2)?; + let ch1 = ecx.read_immediate(&ecx.project_index(str1, i)?)?; + let ch2 = ecx.read_immediate(&ecx.project_index(str2, i)?)?; + let eq = ecx.binary_op(mir::BinOp::Eq, &ch1, &ch2)?; result |= i32::from(eq.to_scalar().to_bool()?) << i; } } @@ -159,9 +159,9 @@ fn compare_strings<'tcx>( if k >= default_len { break; } else { - let ch1 = this.read_immediate(&this.project_index(str1, j)?)?; - let ch2 = this.read_immediate(&this.project_index(str2, k)?)?; - let ne = this.binary_op(mir::BinOp::Ne, &ch1, &ch2)?; + let ch1 = ecx.read_immediate(&ecx.project_index(str1, j)?)?; + let ch2 = ecx.read_immediate(&ecx.project_index(str2, k)?)?; + let ne = ecx.binary_op(mir::BinOp::Ne, &ch1, &ch2)?; if ne.to_scalar().to_bool()? { result &= !(1 << i); @@ -198,16 +198,16 @@ fn compare_strings<'tcx>( /// corresponding to the x86 128-bit integer SIMD type. fn deconstruct_args<'tcx>( unprefixed_name: &str, - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, link_name: Symbol, abi: ExternAbi, args: &[OpTy<'tcx>], ) -> InterpResult<'tcx, (OpTy<'tcx>, OpTy<'tcx>, Option<(u64, u64)>, u8)> { - let array_layout_fn = |this: &mut MiriInterpCx<'tcx>, imm: u8| { + let array_layout_fn = |ecx: &mut MiriInterpCx<'tcx>, imm: u8| { if imm & USE_WORDS != 0 { - this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u16, 8)) + ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u16, 8)) } else { - this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u8, 16)) + ecx.layout_of(Ty::new_array(ecx.tcx.tcx, ecx.tcx.types.u8, 16)) } }; @@ -223,26 +223,26 @@ fn deconstruct_args<'tcx>( if is_explicit { let [str1, len1, str2, len2, imm] = - this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?; - let imm = this.read_scalar(imm)?.to_u8()?; + ecx.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?; + let imm = ecx.read_scalar(imm)?.to_u8()?; let default_len = default_len::(imm); - let len1 = u64::from(this.read_scalar(len1)?.to_u32()?.min(default_len)); - let len2 = u64::from(this.read_scalar(len2)?.to_u32()?.min(default_len)); + let len1 = u64::from(ecx.read_scalar(len1)?.to_u32()?.min(default_len)); + let len2 = u64::from(ecx.read_scalar(len2)?.to_u32()?.min(default_len)); - let array_layout = array_layout_fn(this, imm)?; - let str1 = str1.transmute(array_layout, this)?; - let str2 = str2.transmute(array_layout, this)?; + let array_layout = array_layout_fn(ecx, imm)?; + let str1 = str1.transmute(array_layout, ecx)?; + let str2 = str2.transmute(array_layout, ecx)?; interp_ok((str1, str2, Some((len1, len2)), imm)) } else { let [str1, str2, imm] = - this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?; - let imm = this.read_scalar(imm)?.to_u8()?; + ecx.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?; + let imm = ecx.read_scalar(imm)?.to_u8()?; - let array_layout = array_layout_fn(this, imm)?; - let str1 = str1.transmute(array_layout, this)?; - let str2 = str2.transmute(array_layout, this)?; + let array_layout = array_layout_fn(ecx, imm)?; + let str1 = str1.transmute(array_layout, ecx)?; + let str2 = str2.transmute(array_layout, ecx)?; interp_ok((str1, str2, None, imm)) } @@ -251,16 +251,16 @@ fn deconstruct_args<'tcx>( /// Calculate the c-style string length for a given string `str`. /// The string is either a length 16 array of bytes a length 8 array of two-byte words. fn implicit_len<'tcx>( - this: &mut MiriInterpCx<'tcx>, + ecx: &mut MiriInterpCx<'tcx>, str: &OpTy<'tcx>, imm: u8, ) -> InterpResult<'tcx, Option> { let mut result = None; - let zero = ImmTy::from_int(0, str.layout.field(this, 0)); + let zero = ImmTy::from_int(0, str.layout.field(ecx, 0)); for i in 0..default_len::(imm) { - let ch = this.read_immediate(&this.project_index(str, i)?)?; - let is_zero = this.binary_op(mir::BinOp::Eq, &ch, &zero)?; + let ch = ecx.read_immediate(&ecx.project_index(str, i)?)?; + let is_zero = ecx.binary_op(mir::BinOp::Eq, &ch, &zero)?; if is_zero.to_scalar().to_bool()? { result = Some(i); break; diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs new file mode 100644 index 0000000000000..65d29b2c6bad1 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs @@ -0,0 +1,65 @@ +//@only-target: linux +//~^ERROR: deadlocked +//~^^ERROR: deadlocked +//@compile-flags: -Zmiri-preemption-rate=0 +//@error-in-other-file: deadlock + +use std::thread; + +// Test the behaviour of a thread being blocked on an eventfd read, get unblocked, and then +// get blocked again. + +// The expected execution is +// 1. Thread 1 blocks. +// 2. Thread 2 blocks. +// 3. Thread 3 unblocks both thread 1 and thread 2. +// 4. Thread 1 reads. +// 5. Thread 2's `read` deadlocked. + +fn main() { + // eventfd write will block when EFD_NONBLOCK flag is clear + // and the addition caused counter to exceed u64::MAX - 1. + let flags = libc::EFD_CLOEXEC; + let fd = unsafe { libc::eventfd(0, flags) }; + + let thread1 = thread::spawn(move || { + thread::park(); + let mut buf: [u8; 8] = [0; 8]; + // This read will block initially. + let res: i64 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8).try_into().unwrap() }; + assert_eq!(res, 8); + let counter = u64::from_ne_bytes(buf); + assert_eq!(counter, 1_u64); + }); + + let thread2 = thread::spawn(move || { + thread::park(); + let mut buf: [u8; 8] = [0; 8]; + // This read will block initially, then get unblocked by thread3, then get blocked again + // because the `read` in thread1 executes first and set the counter to 0 again. + let res: i64 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8).try_into().unwrap() }; + //~^ERROR: deadlocked + assert_eq!(res, 8); + let counter = u64::from_ne_bytes(buf); + assert_eq!(counter, 1_u64); + }); + + let thread3 = thread::spawn(move || { + thread::park(); + let sized_8_data = 1_u64.to_ne_bytes(); + // Write 1 to the counter, so both thread1 and thread2 will unblock. + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + // Make sure that write is successful. + assert_eq!(res, 8); + }); + + thread1.thread().unpark(); + thread2.thread().unpark(); + thread3.thread().unpark(); + + thread1.join().unwrap(); + thread2.join().unwrap(); + thread3.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr new file mode 100644 index 0000000000000..bb235345c5ed1 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.stderr @@ -0,0 +1,41 @@ +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC + | +LL | thread2.join().unwrap(); + | ^^^^^^^^^^^^^^ + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +error: deadlock: the evaluated program deadlocked + --> tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC + | +LL | let res: i64 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8).try_into().unwrap() }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 4 previous errors + diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs new file mode 100644 index 0000000000000..f9d34d2fb58a2 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs @@ -0,0 +1,71 @@ +//@only-target: linux +//~^ERROR: deadlocked +//~^^ERROR: deadlocked +//@compile-flags: -Zmiri-preemption-rate=0 +//@error-in-other-file: deadlock + +use std::thread; + +// Test the behaviour of a thread being blocked on an eventfd `write`, get unblocked, and then +// get blocked again. + +// The expected execution is +// 1. Thread 1 blocks. +// 2. Thread 2 blocks. +// 3. Thread 3 unblocks both thread 1 and thread 2. +// 4. Thread 1 writes u64::MAX. +// 5. Thread 2's `write` deadlocked. +fn main() { + // eventfd write will block when EFD_NONBLOCK flag is clear + // and the addition caused counter to exceed u64::MAX - 1. + let flags = libc::EFD_CLOEXEC; + let fd = unsafe { libc::eventfd(0, flags) }; + // Write u64 - 1, so the all subsequent write will block. + let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + assert_eq!(res, 8); + + let thread1 = thread::spawn(move || { + thread::park(); + let sized_8_data = (u64::MAX - 1).to_ne_bytes(); + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + // Make sure that write is successful. + assert_eq!(res, 8); + }); + + let thread2 = thread::spawn(move || { + thread::park(); + let sized_8_data = (u64::MAX - 1).to_ne_bytes(); + // Write u64::MAX - 1, so the all subsequent write will block. + let res: i64 = unsafe { + // This `write` will initially blocked, then get unblocked by thread3, then get blocked again + // because the `write` in thread1 executes first. + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + //~^ERROR: deadlocked + }; + // Make sure that write is successful. + assert_eq!(res, 8); + }); + + let thread3 = thread::spawn(move || { + thread::park(); + let mut buf: [u8; 8] = [0; 8]; + // This will unblock both `write` in thread1 and thread2. + let res: i64 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8).try_into().unwrap() }; + assert_eq!(res, 8); + let counter = u64::from_ne_bytes(buf); + assert_eq!(counter, (u64::MAX - 1)); + }); + + thread1.thread().unpark(); + thread2.thread().unpark(); + thread3.thread().unpark(); + + thread1.join().unwrap(); + thread2.join().unwrap(); + thread3.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr new file mode 100644 index 0000000000000..d9163a5748cc0 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.stderr @@ -0,0 +1,41 @@ +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC + | +LL | thread2.join().unwrap(); + | ^^^^^^^^^^^^^^ + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +error: deadlock: the evaluated program deadlocked + --> tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC + | +LL | libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + | ^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 4 previous errors + diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs new file mode 100644 index 0000000000000..7f5ec477e192c --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -0,0 +1,96 @@ +//@compile-flags: -Zmiri-preemption-rate=0 +//~^ERROR: deadlocked +//~^^ERROR: deadlocked +//@only-target: linux +//@error-in-other-file: deadlock + +use std::convert::TryInto; +use std::thread; +use std::thread::spawn; + +// Using `as` cast since `EPOLLET` wraps around +const EPOLL_IN_OUT_ET: u32 = (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _; + +#[track_caller] +fn check_epoll_wait( + epfd: i32, + expected_notifications: &[(u32, u64)], + timeout: i32, +) { + let epoll_event = libc::epoll_event { events: 0, u64: 0 }; + let mut array: [libc::epoll_event; N] = [epoll_event; N]; + let maxsize = N; + let array_ptr = array.as_mut_ptr(); + let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), timeout) }; + if res < 0 { + panic!("epoll_wait failed: {}", std::io::Error::last_os_error()); + } + assert_eq!( + res, + expected_notifications.len().try_into().unwrap(), + "got wrong number of notifications" + ); + let slice = unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) }; + for (return_event, expected_event) in slice.iter().zip(expected_notifications.iter()) { + let event = return_event.events; + let data = return_event.u64; + assert_eq!(event, expected_event.0, "got wrong events"); + assert_eq!(data, expected_event.1, "got wrong data"); + } +} + +// Test if only one thread is unblocked if multiple threads blocked on same epfd. +// Expected execution: +// 1. Thread 2 blocks. +// 2. Thread 3 blocks. +// 3. Thread 1 unblocks thread 3. +// 4. Thread 2 deadlocks. +fn main() { + // Create an epoll instance. + let epfd = unsafe { libc::epoll_create1(0) }; + assert_ne!(epfd, -1); + + // Create a socketpair instance. + let mut fds = [-1, -1]; + let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }; + assert_eq!(res, 0); + + // Register one side of the socketpair with epoll. + let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 }; + let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) }; + assert_eq!(res, 0); + + // epoll_wait to clear notification. + let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); + let expected_value = fds[0] as u64; + check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 0); + + let thread1 = spawn(move || { + thread::park(); + let data = "abcde".as_bytes().as_ptr(); + let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) }; + assert_eq!(res, 5); + }); + + let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); + let expected_value = fds[0] as u64; + let thread2 = spawn(move || { + thread::park(); + check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1); + //~^ERROR: deadlocked + }); + let thread3 = spawn(move || { + thread::park(); + check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1); + }); + + thread2.thread().unpark(); + thread::yield_now(); + thread3.thread().unpark(); + thread::yield_now(); + thread1.thread().unpark(); + + thread1.join().unwrap(); + thread2.join().unwrap(); + thread3.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr new file mode 100644 index 0000000000000..010dabc136449 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr @@ -0,0 +1,41 @@ +error: deadlock: the evaluated program deadlocked + --> RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + | +LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; + | ^ the evaluated program deadlocked + | + = note: BACKTRACE: + = note: inside `std::sys::pal::PLATFORM::thread::Thread::join` at RUSTLIB/std/src/sys/pal/PLATFORM/thread.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC + | +LL | thread2.join().unwrap(); + | ^^^^^^^^^^^^^^ + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +error: deadlock: the evaluated program deadlocked + --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC + | +LL | check_epoll_wait::(epfd, &[(expected_event, expected_value)], -1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC + +error: deadlock: the evaluated program deadlocked + | + = note: the evaluated program deadlocked + = note: (no span available) + = note: BACKTRACE on thread `unnamed-ID`: + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 4 previous errors + diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs deleted file mode 100644 index 0212a63bd0fa2..0000000000000 --- a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs +++ /dev/null @@ -1,11 +0,0 @@ -//@only-target: linux -fn main() { - // eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0. - // This will pass when blocking is implemented. - let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; - let mut buf: [u8; 8] = [0; 8]; - let _res: i32 = unsafe { - libc::read(fd, buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap() //~ERROR: blocking is unsupported - }; -} diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr deleted file mode 100644 index aff30c81ebdb9..0000000000000 --- a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error: unsupported operation: eventfd: blocking is unsupported - --> tests/fail-dep/libc/libc_eventfd_read_block.rs:LL:CC - | -LL | libc::read(fd, buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ eventfd: blocking is unsupported - | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support - = note: BACKTRACE: - = note: inside `main` at tests/fail-dep/libc/libc_eventfd_read_block.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs deleted file mode 100644 index ed6ad466901bc..0000000000000 --- a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs +++ /dev/null @@ -1,21 +0,0 @@ -//@only-target: linux -fn main() { - // eventfd write will block when EFD_NONBLOCK flag is clear - // and the addition caused counter to exceed u64::MAX - 1. - // This will pass when blocking is implemented. - let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; - // Write u64 - 1. - let mut sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); - let res: i64 = unsafe { - libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() - }; - assert_eq!(res, 8); - - // Write 1. - sized_8_data = 1_u64.to_ne_bytes(); - // Write 1 to the counter. - let _res: i64 = unsafe { - libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() //~ERROR: blocking is unsupported - }; -} diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr deleted file mode 100644 index 2b6066057941f..0000000000000 --- a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error: unsupported operation: eventfd: blocking is unsupported - --> tests/fail-dep/libc/libc_eventfd_write_block.rs:LL:CC - | -LL | libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ eventfd: blocking is unsupported - | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support - = note: BACKTRACE: - = note: inside `main` at tests/fail-dep/libc/libc_eventfd_write_block.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs index a47bb671e32b8..a2e8a533c43ad 100644 --- a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs +++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs @@ -8,8 +8,8 @@ struct Foo(u64); impl Foo { - #[rustfmt::skip] // rustfmt is wrong about which line contains an error - fn add(&mut self, n: u64) -> u64 { //~ ERROR: /reborrow through .* is forbidden/ + fn add(&mut self, n: u64) -> u64 { + //~^ ERROR: /reborrow through .* is forbidden/ self.0 + n } } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs index c92d9c3fe7095..dd9c0eb0b54d6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs @@ -1,5 +1,5 @@ //@only-target: linux -// test_race depends on a deterministic schedule. +// test_race, test_blocking_read and test_blocking_write depend on a deterministic schedule. //@compile-flags: -Zmiri-preemption-rate=0 // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint @@ -11,6 +11,9 @@ fn main() { test_read_write(); test_race(); test_syscall(); + test_blocking_read(); + test_blocking_write(); + test_two_threads_blocked_on_eventfd(); } fn read_bytes(fd: i32, buf: &mut [u8; N]) -> i32 { @@ -118,3 +121,117 @@ fn test_syscall() { let fd = unsafe { libc::syscall(libc::SYS_eventfd2, initval, flags) }; assert_ne!(fd, -1); } + +// This test will block on eventfd read then get unblocked by `write`. +fn test_blocking_read() { + // eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0. + let flags = libc::EFD_CLOEXEC; + let fd = unsafe { libc::eventfd(0, flags) }; + let thread1 = thread::spawn(move || { + let mut buf: [u8; 8] = [0; 8]; + // This will block. + let res = read_bytes(fd, &mut buf); + // read returns number of bytes has been read, which is always 8. + assert_eq!(res, 8); + let counter = u64::from_ne_bytes(buf); + assert_eq!(counter, 1); + }); + let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); + // Pass control to thread1 so it can block on eventfd `read`. + thread::yield_now(); + // Write 1 to the counter to unblock thread1. + let res = write_bytes(fd, sized_8_data); + assert_eq!(res, 8); + thread1.join().unwrap(); +} + +/// This test will block on eventfd `write` then get unblocked by `read`. +fn test_blocking_write() { + // eventfd write will block when EFD_NONBLOCK flag is clear + // and the addition caused counter to exceed u64::MAX - 1. + let flags = libc::EFD_CLOEXEC; + let fd = unsafe { libc::eventfd(0, flags) }; + // Write u64 - 1, so the all subsequent write will block. + let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + assert_eq!(res, 8); + + let thread1 = thread::spawn(move || { + let sized_8_data = 1_u64.to_ne_bytes(); + // Write 1 to the counter, this will block. + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + // Make sure that write is successful. + assert_eq!(res, 8); + }); + let mut buf: [u8; 8] = [0; 8]; + // Pass control to thread1 so it can block on eventfd `write`. + thread::yield_now(); + // This will unblock previously blocked eventfd read. + let res = read_bytes(fd, &mut buf); + // read returns number of bytes has been read, which is always 8. + assert_eq!(res, 8); + let counter = u64::from_ne_bytes(buf); + assert_eq!(counter, (u64::MAX - 1)); + thread1.join().unwrap(); +} + +// Test two threads blocked on eventfd. +// Expected behaviour: +// 1. thread1 and thread2 both blocked on `write`. +// 2. thread3 unblocks both thread1 and thread2 +// 3. The write in thread1 and thread2 return successfully. +fn test_two_threads_blocked_on_eventfd() { + // eventfd write will block when EFD_NONBLOCK flag is clear + // and the addition caused counter to exceed u64::MAX - 1. + let flags = libc::EFD_CLOEXEC; + let fd = unsafe { libc::eventfd(0, flags) }; + // Write u64 - 1, so the all subsequent write will block. + let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + assert_eq!(res, 8); + + let thread1 = thread::spawn(move || { + thread::park(); + let sized_8_data = 1_u64.to_ne_bytes(); + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + // Make sure that write is successful. + assert_eq!(res, 8); + }); + + let thread2 = thread::spawn(move || { + thread::park(); + let sized_8_data = 1_u64.to_ne_bytes(); + let res: i64 = unsafe { + libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() + }; + // Make sure that write is successful. + assert_eq!(res, 8); + }); + + let thread3 = thread::spawn(move || { + thread::park(); + let mut buf: [u8; 8] = [0; 8]; + // This will unblock previously blocked eventfd read. + let res = read_bytes(fd, &mut buf); + // read returns number of bytes has been read, which is always 8. + assert_eq!(res, 8); + let counter = u64::from_ne_bytes(buf); + assert_eq!(counter, (u64::MAX - 1)); + }); + + thread1.thread().unpark(); + thread2.thread().unpark(); + thread3.thread().unpark(); + + thread1.join().unwrap(); + thread2.join().unwrap(); + thread3.join().unwrap(); +} diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 66843ca584b8d..4de315e358975 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -959,10 +959,20 @@ pub fn libm() { unsafe { ldexp(a, b) } } - assert_approx_eq!(64f32.sqrt(), 8f32); - assert_approx_eq!(64f64.sqrt(), 8f64); + assert_eq!(64_f32.sqrt(), 8_f32); + assert_eq!(64_f64.sqrt(), 8_f64); + assert_eq!(f32::INFINITY.sqrt(), f32::INFINITY); + assert_eq!(f64::INFINITY.sqrt(), f64::INFINITY); + assert_eq!(0.0_f32.sqrt().total_cmp(&0.0), std::cmp::Ordering::Equal); + assert_eq!(0.0_f64.sqrt().total_cmp(&0.0), std::cmp::Ordering::Equal); + assert_eq!((-0.0_f32).sqrt().total_cmp(&-0.0), std::cmp::Ordering::Equal); + assert_eq!((-0.0_f64).sqrt().total_cmp(&-0.0), std::cmp::Ordering::Equal); assert!((-5.0_f32).sqrt().is_nan()); assert!((-5.0_f64).sqrt().is_nan()); + assert!(f32::NEG_INFINITY.sqrt().is_nan()); + assert!(f64::NEG_INFINITY.sqrt().is_nan()); + assert!(f32::NAN.sqrt().is_nan()); + assert!(f64::NAN.sqrt().is_nan()); assert_approx_eq!(25f32.powi(-2), 0.0016f32); assert_approx_eq!(23.2f64.powi(2), 538.24f64); diff --git a/src/tools/miri/tests/pass/stacked-borrows/2phase.rs b/src/tools/miri/tests/pass/stacked-borrows/2phase.rs index eb543d691e10b..fb4ba60583736 100644 --- a/src/tools/miri/tests/pass/stacked-borrows/2phase.rs +++ b/src/tools/miri/tests/pass/stacked-borrows/2phase.rs @@ -1,3 +1,6 @@ +// FIXME: this miscompiles with optimizations, see . +//@compile-flags: -Zmir-opt-level=0 + trait S: Sized { fn tpb(&mut self, _s: Self) {} } @@ -75,6 +78,25 @@ fn with_interior_mutability() { }); } +// This one really shouldn't be accepted, but since we treat 2phase as raw, we do accept it. +// Tree Borrows rejects it. +fn aliasing_violation() { + struct Foo(u64); + impl Foo { + fn add(&mut self, n: u64) -> u64 { + self.0 + n + } + } + + let mut f = Foo(0); + let alias = &mut f.0 as *mut u64; + let res = f.add(unsafe { + *alias = 42; + 0 + }); + assert_eq!(res, 42); +} + fn main() { two_phase1(); two_phase2(); @@ -84,4 +106,5 @@ fn main() { with_interior_mutability(); two_phase_overlapping1(); two_phase_overlapping2(); + aliasing_violation(); } diff --git a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs index c75824d7f9be0..4261f411eea47 100644 --- a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs +++ b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs @@ -8,6 +8,7 @@ fn main() { mut_raw_then_mut_shr(); mut_shr_then_mut_raw(); mut_raw_mut(); + mut_raw_mut2(); partially_invalidate_mut(); drop_after_sharing(); // direct_mut_to_const_raw(); @@ -96,6 +97,18 @@ fn mut_raw_mut() { assert_eq!(x, 4); } +// A variant of `mut_raw_mut` that does *not* get accepted by Tree Borrows. +// It's kind of an accident that we accept it in Stacked Borrows... +fn mut_raw_mut2() { + unsafe { + let mut root = 0; + let to = &mut root as *mut i32; + *to = 0; + let _val = root; + *to = 0; + } +} + fn partially_invalidate_mut() { let data = &mut (0u8, 0u8); let reborrow = &mut *data as *mut (u8, u8); diff --git a/tests/ui/specialization/min_specialization/specialize_with_generalize_lifetimes.stderr b/tests/ui/specialization/min_specialization/specialize_with_generalize_lifetimes.stderr index 2af75876d5b0a..04a41f0d9dd81 100644 --- a/tests/ui/specialization/min_specialization/specialize_with_generalize_lifetimes.stderr +++ b/tests/ui/specialization/min_specialization/specialize_with_generalize_lifetimes.stderr @@ -4,11 +4,7 @@ error[E0477]: the type `&'a i32` does not fulfill the required lifetime LL | impl<'a> Tr for &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^ | -note: type must satisfy the static lifetime as required by this binding - --> $DIR/specialize_with_generalize_lifetimes.rs:12:15 - | -LL | impl Tr for T { - | ^^^^^^^ + = note: type must satisfy the static lifetime error[E0477]: the type `Wrapper<'a>` does not fulfill the required lifetime --> $DIR/specialize_with_generalize_lifetimes.rs:31:1 @@ -16,11 +12,7 @@ error[E0477]: the type `Wrapper<'a>` does not fulfill the required lifetime LL | impl<'a> Tr for Wrapper<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: type must satisfy the static lifetime as required by this binding - --> $DIR/specialize_with_generalize_lifetimes.rs:12:15 - | -LL | impl Tr for T { - | ^^^^^^^ + = note: type must satisfy the static lifetime error: aborting due to 2 previous errors diff --git a/tests/ui/traits/const-traits/effects/dont-prefer-param-env-for-infer-self-ty.rs b/tests/ui/traits/const-traits/effects/dont-prefer-param-env-for-infer-self-ty.rs new file mode 100644 index 0000000000000..08dcd7d80b344 --- /dev/null +++ b/tests/ui/traits/const-traits/effects/dont-prefer-param-env-for-infer-self-ty.rs @@ -0,0 +1,16 @@ +//@ check-pass + +#![feature(const_trait_impl)] + +#[const_trait] +trait Foo {} + +impl const Foo for (T,) where T: ~const Foo {} + +const fn needs_const_foo(_: impl ~const Foo + Copy) {} + +const fn test(t: T) { + needs_const_foo((t,)); +} + +fn main() {}