diff options
| author | Ralf Jung <post@ralfj.de> | 2025-09-05 10:46:21 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-05 10:46:21 +0000 |
| commit | 0dcc21b1249dc6ada6ec69e38074123eeaca72d8 (patch) | |
| tree | 018dcb78f45e11e531e88388a06c519a3f5077b4 | |
| parent | 3a16bbc1de1b1f7dc9bb157c4065266727256364 (diff) | |
| parent | 1186db896e2f4013b1f15612b31ed18e2b653228 (diff) | |
| download | rust-0dcc21b1249dc6ada6ec69e38074123eeaca72d8.tar.gz rust-0dcc21b1249dc6ada6ec69e38074123eeaca72d8.zip | |
Merge pull request #4569 from RalfJung/atomic-rmw
atomics: unify handling min/max and the other RMWs
| -rw-r--r-- | src/tools/miri/src/concurrency/data_race.rs | 85 | ||||
| -rw-r--r-- | src/tools/miri/src/concurrency/genmc/dummy.rs | 19 | ||||
| -rw-r--r-- | src/tools/miri/src/concurrency/genmc/mod.rs | 29 | ||||
| -rw-r--r-- | src/tools/miri/src/intrinsics/atomic.rs | 74 | ||||
| -rw-r--r-- | src/tools/miri/src/intrinsics/mod.rs | 3 |
5 files changed, 83 insertions, 127 deletions
diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 19d37a691d9..a7822d0d6fa 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -56,6 +56,7 @@ use super::vector_clock::{VClock, VTimestamp, VectorIdx}; use super::weak_memory::EvalContextExt as _; use crate::concurrency::GlobalDataRaceHandler; use crate::diagnostics::RacingOp; +use crate::intrinsics::AtomicRmwOp; use crate::*; pub type AllocState = VClockAlloc; @@ -778,9 +779,8 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { &mut self, place: &MPlaceTy<'tcx>, rhs: &ImmTy<'tcx>, - op: mir::BinOp, - not: bool, - atomic: AtomicRwOrd, + atomic_op: AtomicRmwOp, + ord: AtomicRwOrd, ) -> InterpResult<'tcx, ImmTy<'tcx>> { let this = self.eval_context_mut(); this.atomic_access_check(place, AtomicAccessType::Rmw)?; @@ -793,8 +793,9 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { this, place.ptr().addr(), place.layout.size, - atomic, - (op, not), + place.layout.backend_repr.is_signed(), + ord, + atomic_op, rhs.to_scalar(), old.to_scalar(), )?; @@ -804,13 +805,26 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { return interp_ok(ImmTy::from_scalar(old_val, old.layout)); } - let val = this.binary_op(op, &old, rhs)?; - let val = if not { this.unary_op(mir::UnOp::Not, &val)? } else { val }; + let val = match atomic_op { + AtomicRmwOp::MirOp { op, neg } => { + let val = this.binary_op(op, &old, rhs)?; + if neg { this.unary_op(mir::UnOp::Not, &val)? } else { val } + } + AtomicRmwOp::Max => { + let lt = this.binary_op(mir::BinOp::Lt, &old, rhs)?.to_scalar().to_bool()?; + if lt { rhs } else { &old }.clone() + } + AtomicRmwOp::Min => { + let lt = this.binary_op(mir::BinOp::Lt, &old, rhs)?.to_scalar().to_bool()?; + if lt { &old } else { rhs }.clone() + } + }; + this.allow_data_races_mut(|this| this.write_immediate(*val, place))?; - this.validate_atomic_rmw(place, atomic)?; + this.validate_atomic_rmw(place, ord)?; - this.buffered_atomic_rmw(val.to_scalar(), place, atomic, old.to_scalar())?; + this.buffered_atomic_rmw(val.to_scalar(), place, ord, old.to_scalar())?; interp_ok(old) } @@ -852,59 +866,6 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> { interp_ok(old) } - /// Perform an conditional atomic exchange with a memory place and a new - /// scalar value, the old value is returned. - fn atomic_min_max_scalar( - &mut self, - place: &MPlaceTy<'tcx>, - rhs: ImmTy<'tcx>, - min: bool, - atomic: AtomicRwOrd, - ) -> InterpResult<'tcx, ImmTy<'tcx>> { - let this = self.eval_context_mut(); - this.atomic_access_check(place, AtomicAccessType::Rmw)?; - - let old = this.allow_data_races_mut(|this| this.read_immediate(place))?; - - // Inform GenMC about the atomic min/max operation. - if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - let (old_val, new_val) = genmc_ctx.atomic_min_max_op( - this, - place.ptr().addr(), - place.layout.size, - atomic, - min, - old.layout.backend_repr.is_signed(), - rhs.to_scalar(), - old.to_scalar(), - )?; - // The store might be the latest store in coherence order (determined by GenMC). - // If it is, we need to update the value in Miri's memory: - if let Some(new_val) = new_val { - this.allow_data_races_mut(|this| this.write_scalar(new_val, place))?; - } - return interp_ok(ImmTy::from_scalar(old_val, old.layout)); - } - - let lt = this.binary_op(mir::BinOp::Lt, &old, &rhs)?.to_scalar().to_bool()?; - - #[rustfmt::skip] // rustfmt makes this unreadable - let new_val = if min { - if lt { &old } else { &rhs } - } else { - if lt { &rhs } else { &old } - }; - - this.allow_data_races_mut(|this| this.write_immediate(**new_val, place))?; - - this.validate_atomic_rmw(place, atomic)?; - - this.buffered_atomic_rmw(new_val.to_scalar(), place, atomic, old.to_scalar())?; - - // Return the old value. - interp_ok(old) - } - /// Perform an atomic compare and exchange at a given memory location. /// On success an atomic RMW operation is performed and on failure /// only an atomic read occurs. If `can_fail_spuriously` is true, diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index 1960ef37cc9..7c420961ec5 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -1,8 +1,8 @@ use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult}; -use rustc_middle::mir; pub use self::run::run_genmc_mode; +use crate::intrinsics::AtomicRmwOp; use crate::{ AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, Scalar, ThreadId, ThreadManager, VisitProvenance, VisitWith, @@ -94,22 +94,9 @@ impl GenmcCtx { _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, _address: Size, _size: Size, - _ordering: AtomicRwOrd, - (_rmw_op, _not): (mir::BinOp, bool), - _rhs_scalar: Scalar, - _old_value: Scalar, - ) -> InterpResult<'tcx, (Scalar, Option<Scalar>)> { - unreachable!() - } - - pub(crate) fn atomic_min_max_op<'tcx>( - &self, - _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - _address: Size, - _size: Size, - _ordering: AtomicRwOrd, - _min: bool, _is_signed: bool, + _ordering: AtomicRwOrd, + _atomic_op: AtomicRmwOp, _rhs_scalar: Scalar, _old_value: Scalar, ) -> InterpResult<'tcx, (Scalar, Option<Scalar>)> { diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index b5d20a439c0..26ce0b9c43a 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -7,7 +7,7 @@ use genmc_sys::{ }; use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult, interp_ok}; -use rustc_middle::{mir, throw_machine_stop, throw_ub_format, throw_unsup_format}; +use rustc_middle::{throw_machine_stop, throw_ub_format, throw_unsup_format}; // FIXME(genmc,tracing): Implement some work-around for enabling debug/trace level logging (currently disabled statically in rustc). use tracing::{debug, info}; @@ -15,6 +15,7 @@ use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler}; use self::helper::{MAX_ACCESS_SIZE, genmc_scalar_to_scalar, scalar_to_genmc_scalar}; use self::thread_id_map::ThreadIdMap; use crate::concurrency::genmc::helper::split_access; +use crate::intrinsics::AtomicRmwOp; use crate::{ AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriConfig, MiriMachine, MiriMemoryKind, Scalar, TerminationInfo, ThreadId, ThreadManager, VisitProvenance, @@ -298,8 +299,9 @@ impl GenmcCtx { _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, _address: Size, _size: Size, + _is_signed: bool, _ordering: AtomicRwOrd, - (_rmw_op, _not): (mir::BinOp, bool), + _atomic_op: AtomicRmwOp, _rhs_scalar: Scalar, _old_value: Scalar, ) -> InterpResult<'tcx, (Scalar, Option<Scalar>)> { @@ -310,29 +312,6 @@ impl GenmcCtx { throw_unsup_format!("FIXME(genmc): Add support for atomic RMW.") } - /// Inform GenMC about an atomic `min` or `max` operation. - /// - /// Returns `(old_val, Option<new_val>)`. `new_val` might not be the latest write in coherence order, which is indicated by `None`. - /// - /// `old_value` is the value that a non-atomic load would read here, or `None` if the memory is uninitalized. - pub(crate) fn atomic_min_max_op<'tcx>( - &self, - _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - _address: Size, - _size: Size, - _ordering: AtomicRwOrd, - _min: bool, - _is_signed: bool, - _rhs_scalar: Scalar, - _old_value: Scalar, - ) -> InterpResult<'tcx, (Scalar, Option<Scalar>)> { - assert!( - !self.get_alloc_data_races(), - "atomic min/max operation with data race checking disabled." - ); - throw_unsup_format!("FIXME(genmc): Add support for atomic min/max.") - } - /// Returns `(old_val, Option<new_val>)`. `new_val` might not be the latest write in coherence order, which is indicated by `None`. /// /// `old_value` is the value that a non-atomic load would read here, or `None` if the memory is uninitalized. diff --git a/src/tools/miri/src/intrinsics/atomic.rs b/src/tools/miri/src/intrinsics/atomic.rs index e6341252927..c9f63640b85 100644 --- a/src/tools/miri/src/intrinsics/atomic.rs +++ b/src/tools/miri/src/intrinsics/atomic.rs @@ -5,10 +5,13 @@ use rustc_middle::{mir, ty}; use super::check_intrinsic_arg_count; use crate::*; -pub enum AtomicOp { - /// The `bool` indicates whether the result of the operation should be negated (`UnOp::Not`, - /// must be a boolean-typed operation). - MirOp(mir::BinOp, bool), +pub enum AtomicRmwOp { + MirOp { + op: mir::BinOp, + /// Indicates whether the result of the operation should be negated (`UnOp::Not`, must be a + /// boolean-typed operation). + neg: bool, + }, Max, Min, } @@ -106,55 +109,85 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "or" => { let ord = get_ord_at(2); - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord))?; + this.atomic_rmw_op( + args, + dest, + AtomicRmwOp::MirOp { op: BinOp::BitOr, neg: false }, + rw_ord(ord), + )?; } "xor" => { let ord = get_ord_at(2); - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord))?; + this.atomic_rmw_op( + args, + dest, + AtomicRmwOp::MirOp { op: BinOp::BitXor, neg: false }, + rw_ord(ord), + )?; } "and" => { let ord = get_ord_at(2); - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord))?; + this.atomic_rmw_op( + args, + dest, + AtomicRmwOp::MirOp { op: BinOp::BitAnd, neg: false }, + rw_ord(ord), + )?; } "nand" => { let ord = get_ord_at(2); - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord))?; + this.atomic_rmw_op( + args, + dest, + AtomicRmwOp::MirOp { op: BinOp::BitAnd, neg: true }, + rw_ord(ord), + )?; } "xadd" => { let ord = get_ord_at(2); - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord))?; + this.atomic_rmw_op( + args, + dest, + AtomicRmwOp::MirOp { op: BinOp::Add, neg: false }, + rw_ord(ord), + )?; } "xsub" => { let ord = get_ord_at(2); - this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord))?; + this.atomic_rmw_op( + args, + dest, + AtomicRmwOp::MirOp { op: BinOp::Sub, neg: false }, + rw_ord(ord), + )?; } "min" => { let ord = get_ord_at(1); // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Int(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord))?; + this.atomic_rmw_op(args, dest, AtomicRmwOp::Min, rw_ord(ord))?; } "umin" => { let ord = get_ord_at(1); // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord))?; + this.atomic_rmw_op(args, dest, AtomicRmwOp::Min, rw_ord(ord))?; } "max" => { let ord = get_ord_at(1); // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Int(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord))?; + this.atomic_rmw_op(args, dest, AtomicRmwOp::Max, rw_ord(ord))?; } "umax" => { let ord = get_ord_at(1); // Later we will use the type to indicate signed vs unsigned, // so make sure it matches the intrinsic name. assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_))); - this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord))?; + this.atomic_rmw_op(args, dest, AtomicRmwOp::Max, rw_ord(ord))?; } _ => return interp_ok(EmulateItemResult::NotSupported), @@ -222,8 +255,8 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { &mut self, args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, - atomic_op: AtomicOp, - atomic: AtomicRwOrd, + atomic_op: AtomicRmwOp, + ord: AtomicRwOrd, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -240,14 +273,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { ); } - let old = match atomic_op { - AtomicOp::Min => - this.atomic_min_max_scalar(&place, rhs, /* min */ true, atomic)?, - AtomicOp::Max => - this.atomic_min_max_scalar(&place, rhs, /* min */ false, atomic)?, - AtomicOp::MirOp(op, not) => - this.atomic_rmw_op_immediate(&place, &rhs, op, not, atomic)?, - }; + let old = this.atomic_rmw_op_immediate(&place, &rhs, atomic_op, ord)?; this.write_immediate(*old, dest)?; // old value is returned interp_ok(()) } diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 5e46768b0e6..f6f9af47d0a 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -3,6 +3,9 @@ mod atomic; mod simd; +pub use self::atomic::AtomicRmwOp; + +#[rustfmt::skip] // prevent `use` reordering use std::ops::Neg; use rand::Rng; |
