1//! Contains FreeBSD-specific synchronization functions23use core::time::Duration;45use rustc_abi::FieldIdx;67use crate::concurrency::sync::{FutexRef, SyncObj};8use crate::*;910pub struct FreeBsdFutex {11 futex: FutexRef,12}1314impl SyncObj for FreeBsdFutex {}1516/// Extended variant of the `timespec` struct.17pub struct UmtxTime {18 timeout: Duration,19 abs_time: bool,20 timeout_clock: TimeoutClock,21}2223impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}24pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {25 /// Implementation of the FreeBSD [`_umtx_op`](https://man.freebsd.org/cgi/man.cgi?query=_umtx_op&sektion=2&manpath=FreeBSD+14.2-RELEASE+and+Ports) syscall.26 /// This is used for futex operations on FreeBSD.27 ///28 /// `obj`: a pointer to the futex object (can be a lot of things, mostly *AtomicU32)29 /// `op`: the futex operation to run30 /// `val`: the current value of the object as a `c_long` (for wait/wake)31 /// `uaddr`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct32 /// `uaddr2`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct33 /// `dest`: the place this syscall returns to, 0 for success, -1 for failure34 ///35 /// # Note36 /// Curently only the WAIT and WAKE operations are implemented.37 fn _umtx_op(38 &mut self,39 obj: &OpTy<'tcx>,40 op: &OpTy<'tcx>,41 val: &OpTy<'tcx>,42 uaddr: &OpTy<'tcx>,43 uaddr2: &OpTy<'tcx>,44 dest: &MPlaceTy<'tcx>,45 ) -> InterpResult<'tcx> {46 let this = self.eval_context_mut();4748 let obj = this.read_pointer(obj)?;49 let op = this.read_scalar(op)?.to_i32()?;50 let val = this.read_target_usize(val)?;51 let uaddr = this.read_target_usize(uaddr)?;52 let uaddr2 = this.read_pointer(uaddr2)?;5354 let wait = this.eval_libc_i32("UMTX_OP_WAIT");55 let wait_uint = this.eval_libc_i32("UMTX_OP_WAIT_UINT");56 let wait_uint_private = this.eval_libc_i32("UMTX_OP_WAIT_UINT_PRIVATE");5758 let wake = this.eval_libc_i32("UMTX_OP_WAKE");59 let wake_private = this.eval_libc_i32("UMTX_OP_WAKE_PRIVATE");6061 let timespec_layout = this.libc_ty_layout("timespec");62 let umtx_time_layout = this.libc_ty_layout("_umtx_time");63 assert!(64 timespec_layout.size != umtx_time_layout.size,65 "`struct timespec` and `struct _umtx_time` should have different sizes."66 );6768 match op {69 // UMTX_OP_WAIT_UINT and UMTX_OP_WAIT_UINT_PRIVATE only differ in whether they work across70 // processes or not. For Miri, we can treat them the same.71 op if op == wait || op == wait_uint || op == wait_uint_private => {72 let obj_layout =73 if op == wait { this.machine.layouts.isize } else { this.machine.layouts.u32 };74 let obj = this.ptr_to_mplace(obj, obj_layout);7576 // Read the Linux futex wait implementation in Miri to understand why this fence is needed.77 this.atomic_fence(AtomicFenceOrd::SeqCst)?;78 let obj_val = this79 .read_scalar_atomic(&obj, AtomicReadOrd::Acquire)?80 .to_bits(obj_layout.size)?; // isize and u32 can have different sizes8182 if obj_val == u128::from(val) {83 // This cannot fail since we already did an atomic acquire read on that pointer.84 // Acquire reads are only allowed on mutable memory.85 let futex_ref = this86 .get_sync_or_init(obj.ptr(), |_| FreeBsdFutex { futex: Default::default() })87 .unwrap()88 .futex89 .clone();9091 // From the manual:92 // The timeout is specified by passing either the address of `struct timespec`, or its93 // extended variant, `struct _umtx_time`, as the `uaddr2` argument of _umtx_op().94 // They are distinguished by the `uaddr` value, which must be equal95 // to the size of the structure pointed to by `uaddr2`, casted to uintptr_t.96 let timeout = if this.ptr_is_null(uaddr2)? {97 // no timeout parameter98 None99 } else {100 if uaddr == umtx_time_layout.size.bytes() {101 // `uaddr2` points to a `struct _umtx_time`.102 let umtx_time_place = this.ptr_to_mplace(uaddr2, umtx_time_layout);103104 let Some(umtx_time) = this.read_umtx_time(&umtx_time_place)? else {105 return this.set_last_error_and_return(LibcError("EINVAL"), dest);106 };107108 let anchor = if umtx_time.abs_time {109 TimeoutAnchor::Absolute110 } else {111 TimeoutAnchor::Relative112 };113114 Some((umtx_time.timeout_clock, anchor, umtx_time.timeout))115 } else if uaddr == timespec_layout.size.bytes() {116 // RealTime clock can't be used in isolation mode.117 this.check_no_isolation("`_umtx_op` with `timespec` timeout")?;118119 // `uaddr2` points to a `struct timespec`.120 let timespec = this.ptr_to_mplace(uaddr2, timespec_layout);121 let Some(duration) = this.read_timespec(×pec)? else {122 return this.set_last_error_and_return(LibcError("EINVAL"), dest);123 };124125 // FreeBSD does not seem to document which clock is used when the timeout126 // is passed as a `struct timespec*`. Based on discussions online and the source127 // code (umtx_copyin_umtx_time() in kern_umtx.c), it seems to default to CLOCK_REALTIME,128 // so that's what we also do.129 // Discussion in golang: https://github.com/golang/go/issues/17168#issuecomment-250235271130 Some((TimeoutClock::RealTime, TimeoutAnchor::Relative, duration))131 } else {132 return this.set_last_error_and_return(LibcError("EINVAL"), dest);133 }134 };135136 let dest = dest.clone();137 this.futex_wait(138 futex_ref,139 u32::MAX, // we set the bitset to include all bits140 timeout,141 callback!(142 @capture<'tcx> {143 dest: MPlaceTy<'tcx>,144 }145 |ecx, unblock: UnblockKind| match unblock {146 UnblockKind::Ready => {147 // From the manual:148 // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP149 // sub-requests of the UMTX_OP_SHM request, will return zero.150 ecx.write_int(0, &dest)151 }152 UnblockKind::TimedOut => {153 ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)154 }155 }156 ),157 );158 interp_ok(())159 } else {160 // The manual doesn’t specify what should happen if the futex value doesn’t match the expected one.161 // On FreeBSD 14.2, testing shows that WAIT operations return 0 even when the value is incorrect.162 this.write_int(0, dest)?;163 interp_ok(())164 }165 }166 // UMTX_OP_WAKE and UMTX_OP_WAKE_PRIVATE only differ in whether they work across167 // processes or not. For Miri, we can treat them the same.168 op if op == wake || op == wake_private => {169 let Some(futex_ref) =170 this.get_sync_or_init(obj, |_| FreeBsdFutex { futex: Default::default() })171 else {172 // From Linux implementation:173 // No AllocId, or no live allocation at that AllocId.174 // Return an error code. (That seems nicer than silently doing something non-intuitive.)175 // This means that if an address gets reused by a new allocation,176 // we'll use an independent futex queue for this... that seems acceptable.177 return this.set_last_error_and_return(LibcError("EFAULT"), dest);178 };179 let futex_ref = futex_ref.futex.clone();180181 // Saturating cast for when usize is smaller than u64.182 let count = usize::try_from(val).unwrap_or(usize::MAX);183184 // Read the Linux futex wake implementation in Miri to understand why this fence is needed.185 this.atomic_fence(AtomicFenceOrd::SeqCst)?;186187 // `_umtx_op` doesn't return the amount of woken threads.188 let _woken = this.futex_wake(189 &futex_ref,190 u32::MAX, // we set the bitset to include all bits191 count,192 )?;193194 // From the manual:195 // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP196 // sub-requests of the UMTX_OP_SHM request, will return zero.197 this.write_int(0, dest)?;198 interp_ok(())199 }200 op => {201 throw_unsup_format!("Miri does not support `_umtx_op` syscall with op={}", op)202 }203 }204 }205206 /// Parses a `_umtx_time` struct.207 /// Returns `None` if the underlying `timespec` struct is invalid.208 fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<UmtxTime>> {209 let this = self.eval_context_mut();210 // Only flag allowed is UMTX_ABSTIME.211 let abs_time = this.eval_libc_u32("UMTX_ABSTIME");212213 let timespec_place = this.project_field(ut, FieldIdx::from_u32(0))?;214 // Inner `timespec` must still be valid.215 let Some(duration) = this.read_timespec(×pec_place)? else { return interp_ok(None) };216217 let flags_place = this.project_field(ut, FieldIdx::from_u32(1))?;218 let mut flags = this.read_scalar(&flags_place)?.to_u32()?;219220 let abs_time_flag = if flags & abs_time != 0 {221 flags &= !abs_time;222 true223 } else {224 false225 };226 if flags != 0 {227 throw_unsup_format!("unsupported `_umtx_time` flags: {:#x}", flags);228 }229230 let clock_id_place = this.project_field(ut, FieldIdx::from_u32(2))?;231 let clock_id = this.read_scalar(&clock_id_place)?;232 let Some(timeout_clock) = this.parse_clockid(clock_id) else {233 throw_unsup_format!("unsupported clock")234 };235 if timeout_clock == TimeoutClock::RealTime {236 this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME`")?;237 }238239 interp_ok(Some(UmtxTime { timeout: duration, abs_time: abs_time_flag, timeout_clock }))240 }241}