diff --git a/src/data_race.rs b/src/data_race.rs index 44cce53957..2782fcc0b9 100644 --- a/src/data_race.rs +++ b/src/data_race.rs @@ -542,6 +542,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriEvalContextExt<'mir, 'tcx> { 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, Tag>, + rhs: ImmTy<'tcx, Tag>, + min: bool, + atomic: AtomicRwOp, + ) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> { + let this = self.eval_context_mut(); + + let old = this.allow_data_races_mut(|this| this.read_immediate(place.into()))?; + + // `binary_op` will bail if either of them is not a scalar. + let cond = if min { + this.overflowing_binary_op(mir::BinOp::Lt, old, rhs)?.0 + } else { + this.overflowing_binary_op(mir::BinOp::Gt, old, rhs)?.0 + }; + + if cond.to_bool()? { + this.allow_data_races_mut(|this| this.write_immediate(*rhs, place.into()))?; + } + + this.validate_atomic_rmw(place, atomic)?; + // Return the old value. + 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. diff --git a/src/shims/intrinsics.rs b/src/shims/intrinsics.rs index 956f3b5bde..ef18a776d6 100644 --- a/src/shims/intrinsics.rs +++ b/src/shims/intrinsics.rs @@ -417,6 +417,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx "atomic_xsub_acqrel" => this.atomic_op(args, dest, BinOp::Sub, false, AtomicRwOp::AcqRel)?, "atomic_xsub_relaxed" => this.atomic_op(args, dest, BinOp::Sub, false, AtomicRwOp::Relaxed)?, + "atomic_min" => this.atomic_min_max(args, dest, true, AtomicRwOp::SeqCst)?, + "atomic_min_acq" => this.atomic_min_max(args, dest, true, AtomicRwOp::Acquire)?, + "atomic_min_rel" => this.atomic_min_max(args, dest, true, AtomicRwOp::Release)?, + "atomic_min_acqrel" => this.atomic_min_max(args, dest, true, AtomicRwOp::AcqRel)?, + "atomic_min_relaxed" => this.atomic_min_max(args, dest, true, AtomicRwOp::Relaxed)?, + "atomic_max" => this.atomic_min_max(args, dest, false, AtomicRwOp::SeqCst)?, + "atomic_max_acq" => this.atomic_min_max(args, dest, false, AtomicRwOp::Acquire)?, + "atomic_max_rel" => this.atomic_min_max(args, dest, false, AtomicRwOp::Release)?, + "atomic_max_acqrel" => this.atomic_min_max(args, dest, false, AtomicRwOp::AcqRel)?, + "atomic_max_relaxed" => this.atomic_min_max(args, dest, false, AtomicRwOp::Relaxed)?, + "atomic_umin" => this.atomic_min_max(args, dest, true, AtomicRwOp::SeqCst)?, + "atomic_umin_acq" => this.atomic_min_max(args, dest, true, AtomicRwOp::Acquire)?, + "atomic_umin_rel" => this.atomic_min_max(args, dest, true, AtomicRwOp::Release)?, + "atomic_umin_acqrel" => this.atomic_min_max(args, dest, true, AtomicRwOp::AcqRel)?, + "atomic_umin_relaxed" => this.atomic_min_max(args, dest, true, AtomicRwOp::Relaxed)?, + "atomic_umax" => this.atomic_min_max(args, dest, false, AtomicRwOp::SeqCst)?, + "atomic_umax_acq" => this.atomic_min_max(args, dest, false, AtomicRwOp::Acquire)?, + "atomic_umax_rel" => this.atomic_min_max(args, dest, false, AtomicRwOp::Release)?, + "atomic_umax_acqrel" => this.atomic_min_max(args, dest, false, AtomicRwOp::AcqRel)?, + "atomic_umax_relaxed" => this.atomic_min_max(args, dest, false, AtomicRwOp::Relaxed)?, // Query type information "assert_zero_valid" | @@ -594,6 +614,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx self.atomic_compare_exchange(args, dest, success, fail) } + fn atomic_min_max( + &mut self, args: &[OpTy<'tcx, Tag>], dest: PlaceTy<'tcx, Tag>, + min: bool, + atomic: AtomicRwOp + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let &[place, rhs] = check_arg_count(args)?; + let place = this.deref_operand(place)?; + if !place.layout.ty.is_integral() { + bug!("Atomic arithmetic operations only work on integer types"); + } + let rhs = this.read_immediate(rhs)?; + + // Check alignment requirements. Atomics must always be aligned to their size, + // even if the type they wrap would be less aligned (e.g. AtomicU64 on 32bit must + // be 8-aligned). + let align = Align::from_bytes(place.layout.size.bytes()).unwrap(); + this.memory.check_ptr_access(place.ptr, place.layout.size, align)?; + + let old = this.atomic_min_max_scalar(place, rhs, min, atomic)?; + this.write_immediate(*old, dest)?; // old value is returned + Ok(()) + } + fn float_to_int_unchecked( &self, f: F, diff --git a/tests/run-pass/atomic.rs b/tests/run-pass/atomic.rs index b03a8c8901..0275895d19 100644 --- a/tests/run-pass/atomic.rs +++ b/tests/run-pass/atomic.rs @@ -63,6 +63,20 @@ fn atomic_u64() { Ok(1) ); assert_eq!(ATOMIC.load(Relaxed), 0x100); + + assert_eq!(ATOMIC.fetch_max(0x10, SeqCst), 0x100); + assert_eq!(ATOMIC.fetch_max(0x100, SeqCst), 0x100); + assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x100); + assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x2000); + + assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000); + assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000); + assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x2000); + assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_max(0x100, SeqCst), 0x1000); + assert_eq!(ATOMIC.fetch_max(0x10, SeqCst), 0x100); } fn atomic_fences() {