src/tools/miri/src/shims/unix/freebsd/sync.rs RUST 250 lines View on github.com → Search inside
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 deadline = 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_errno_and_return_neg1(LibcError("EINVAL"), dest);106                            };107108                            let style = if umtx_time.abs_time {109                                TimeoutStyle::Absolute110                            } else {111                                TimeoutStyle::Relative112                            };113114                            Some(this.machine.timeout(115                                umtx_time.timeout_clock,116                                style,117                                umtx_time.timeout,118                            ))119                        } else if uaddr == timespec_layout.size.bytes() {120                            // RealTime clock can't be used in isolation mode.121                            this.check_no_isolation("`_umtx_op` with `timespec` timeout")?;122123                            // `uaddr2` points to a `struct timespec`.124                            let timespec = this.ptr_to_mplace(uaddr2, timespec_layout);125                            let Some(duration) = this.read_timespec(&timespec)? else {126                                return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);127                            };128129                            // FreeBSD does not seem to document which clock is used when the timeout130                            // is passed as a `struct timespec*`. Based on discussions online and the source131                            // code (umtx_copyin_umtx_time() in kern_umtx.c), it seems to default to CLOCK_REALTIME,132                            // so that's what we also do.133                            // Discussion in golang: https://github.com/golang/go/issues/17168#issuecomment-250235271134                            Some(this.machine.timeout(135                                TimeoutClock::RealTime,136                                TimeoutStyle::Relative,137                                duration,138                            ))139                        } else {140                            return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);141                        }142                    };143144                    let dest = dest.clone();145                    this.futex_wait(146                        futex_ref,147                        u32::MAX, // we set the bitset to include all bits148                        deadline,149                        callback!(150                            @capture<'tcx> {151                                dest: MPlaceTy<'tcx>,152                            }153                            |ecx, unblock: UnblockKind| match unblock {154                                UnblockKind::Ready => {155                                    // From the manual:156                                    // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP157                                    // sub-requests of the UMTX_OP_SHM request, will return zero.158                                    ecx.write_int(0, &dest)159                                }160                                UnblockKind::TimedOut => {161                                    ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)162                                }163                            }164                        ),165                    );166                    interp_ok(())167                } else {168                    // The manual doesn’t specify what should happen if the futex value doesn’t match the expected one.169                    // On FreeBSD 14.2, testing shows that WAIT operations return 0 even when the value is incorrect.170                    this.write_int(0, dest)?;171                    interp_ok(())172                }173            }174            // UMTX_OP_WAKE and UMTX_OP_WAKE_PRIVATE only differ in whether they work across175            // processes or not. For Miri, we can treat them the same.176            op if op == wake || op == wake_private => {177                let Some(futex_ref) =178                    this.get_sync_or_init(obj, |_| FreeBsdFutex { futex: Default::default() })179                else {180                    // From Linux implementation:181                    // No AllocId, or no live allocation at that AllocId.182                    // Return an error code. (That seems nicer than silently doing something non-intuitive.)183                    // This means that if an address gets reused by a new allocation,184                    // we'll use an independent futex queue for this... that seems acceptable.185                    return this.set_errno_and_return_neg1(LibcError("EFAULT"), dest);186                };187                let futex_ref = futex_ref.futex.clone();188189                // Saturating cast for when usize is smaller than u64.190                let count = usize::try_from(val).unwrap_or(usize::MAX);191192                // Read the Linux futex wake implementation in Miri to understand why this fence is needed.193                this.atomic_fence(AtomicFenceOrd::SeqCst)?;194195                // `_umtx_op` doesn't return the amount of woken threads.196                let _woken = this.futex_wake(197                    &futex_ref,198                    u32::MAX, // we set the bitset to include all bits199                    count,200                )?;201202                // From the manual:203                // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP204                // sub-requests of the UMTX_OP_SHM request, will return zero.205                this.write_int(0, dest)?;206                interp_ok(())207            }208            op => {209                throw_unsup_format!("Miri does not support `_umtx_op` syscall with op={}", op)210            }211        }212    }213214    /// Parses a `_umtx_time` struct.215    /// Returns `None` if the underlying `timespec` struct is invalid.216    fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<UmtxTime>> {217        let this = self.eval_context_mut();218        // Only flag allowed is UMTX_ABSTIME.219        let abs_time = this.eval_libc_u32("UMTX_ABSTIME");220221        let timespec_place = this.project_field(ut, FieldIdx::from_u32(0))?;222        // Inner `timespec` must still be valid.223        let Some(duration) = this.read_timespec(&timespec_place)? else { return interp_ok(None) };224225        let flags_place = this.project_field(ut, FieldIdx::from_u32(1))?;226        let mut flags = this.read_scalar(&flags_place)?.to_u32()?;227228        let abs_time_flag = if flags & abs_time != 0 {229            flags &= !abs_time;230            true231        } else {232            false233        };234        if flags != 0 {235            throw_unsup_format!("unsupported `_umtx_time` flags: {:#x}", flags);236        }237238        let clock_id_place = this.project_field(ut, FieldIdx::from_u32(2))?;239        let clock_id = this.read_scalar(&clock_id_place)?;240        let Some(timeout_clock) = this.parse_clockid(clock_id) else {241            throw_unsup_format!("unsupported clock")242        };243        if timeout_clock == TimeoutClock::RealTime {244            this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME`")?;245        }246247        interp_ok(Some(UmtxTime { timeout: duration, abs_time: abs_time_flag, timeout_clock }))248    }249}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.