miri/shims/unix/freebsd/
sync.rs

1//! Contains FreeBSD-specific synchronization functions
2
3use core::time::Duration;
4
5use rustc_abi::FieldIdx;
6
7use crate::concurrency::sync::FutexRef;
8use crate::*;
9
10pub struct FreeBsdFutex {
11    futex: FutexRef,
12}
13
14/// Extended variant of the `timespec` struct.
15pub struct UmtxTime {
16    timeout: Duration,
17    abs_time: bool,
18    timeout_clock: TimeoutClock,
19}
20
21impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
22pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
23    /// 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.
24    /// This is used for futex operations on FreeBSD.
25    ///
26    /// `obj`: a pointer to the futex object (can be a lot of things, mostly *AtomicU32)
27    /// `op`: the futex operation to run
28    /// `val`: the current value of the object as a `c_long` (for wait/wake)
29    /// `uaddr`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct
30    /// `uaddr2`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct
31    /// `dest`: the place this syscall returns to, 0 for success, -1 for failure
32    ///
33    /// # Note
34    /// Curently only the WAIT and WAKE operations are implemented.
35    fn _umtx_op(
36        &mut self,
37        obj: &OpTy<'tcx>,
38        op: &OpTy<'tcx>,
39        val: &OpTy<'tcx>,
40        uaddr: &OpTy<'tcx>,
41        uaddr2: &OpTy<'tcx>,
42        dest: &MPlaceTy<'tcx>,
43    ) -> InterpResult<'tcx> {
44        let this = self.eval_context_mut();
45
46        let obj = this.read_pointer(obj)?;
47        let op = this.read_scalar(op)?.to_i32()?;
48        let val = this.read_target_usize(val)?;
49        let uaddr = this.read_target_usize(uaddr)?;
50        let uaddr2 = this.read_pointer(uaddr2)?;
51
52        let wait = this.eval_libc_i32("UMTX_OP_WAIT");
53        let wait_uint = this.eval_libc_i32("UMTX_OP_WAIT_UINT");
54        let wait_uint_private = this.eval_libc_i32("UMTX_OP_WAIT_UINT_PRIVATE");
55
56        let wake = this.eval_libc_i32("UMTX_OP_WAKE");
57        let wake_private = this.eval_libc_i32("UMTX_OP_WAKE_PRIVATE");
58
59        let timespec_layout = this.libc_ty_layout("timespec");
60        let umtx_time_layout = this.libc_ty_layout("_umtx_time");
61        assert!(
62            timespec_layout.size != umtx_time_layout.size,
63            "`struct timespec` and `struct _umtx_time` should have different sizes."
64        );
65
66        match op {
67            // UMTX_OP_WAIT_UINT and UMTX_OP_WAIT_UINT_PRIVATE only differ in whether they work across
68            // processes or not. For Miri, we can treat them the same.
69            op if op == wait || op == wait_uint || op == wait_uint_private => {
70                let obj_layout =
71                    if op == wait { this.machine.layouts.isize } else { this.machine.layouts.u32 };
72                let obj = this.ptr_to_mplace(obj, obj_layout);
73
74                // Read the Linux futex wait implementation in Miri to understand why this fence is needed.
75                this.atomic_fence(AtomicFenceOrd::SeqCst)?;
76                let obj_val = this
77                    .read_scalar_atomic(&obj, AtomicReadOrd::Acquire)?
78                    .to_bits(obj_layout.size)?; // isize and u32 can have different sizes
79
80                if obj_val == u128::from(val) {
81                    // This cannot fail since we already did an atomic acquire read on that pointer.
82                    // Acquire reads are only allowed on mutable memory.
83                    let futex_ref = this
84                        .get_sync_or_init(obj.ptr(), |_| FreeBsdFutex { futex: Default::default() })
85                        .unwrap()
86                        .futex
87                        .clone();
88
89                    // From the manual:
90                    // The timeout is specified by passing either the address of `struct timespec`, or its
91                    // extended variant, `struct _umtx_time`, as the `uaddr2` argument of _umtx_op().
92                    // They are distinguished by the `uaddr` value, which must be equal
93                    // to the size of the structure pointed to by `uaddr2`, casted to uintptr_t.
94                    let timeout = if this.ptr_is_null(uaddr2)? {
95                        // no timeout parameter
96                        None
97                    } else {
98                        if uaddr == umtx_time_layout.size.bytes() {
99                            // `uaddr2` points to a `struct _umtx_time`.
100                            let umtx_time_place = this.ptr_to_mplace(uaddr2, umtx_time_layout);
101
102                            let umtx_time = match this.read_umtx_time(&umtx_time_place)? {
103                                Some(ut) => ut,
104                                None => {
105                                    return this
106                                        .set_last_error_and_return(LibcError("EINVAL"), dest);
107                                }
108                            };
109
110                            let anchor = if umtx_time.abs_time {
111                                TimeoutAnchor::Absolute
112                            } else {
113                                TimeoutAnchor::Relative
114                            };
115
116                            Some((umtx_time.timeout_clock, anchor, umtx_time.timeout))
117                        } else if uaddr == timespec_layout.size.bytes() {
118                            // RealTime clock can't be used in isolation mode.
119                            this.check_no_isolation("`_umtx_op` with `timespec` timeout")?;
120
121                            // `uaddr2` points to a `struct timespec`.
122                            let timespec = this.ptr_to_mplace(uaddr2, timespec_layout);
123                            let duration = match this.read_timespec(&timespec)? {
124                                Some(duration) => duration,
125                                None => {
126                                    return this
127                                        .set_last_error_and_return(LibcError("EINVAL"), dest);
128                                }
129                            };
130
131                            // FreeBSD does not seem to document which clock is used when the timeout
132                            // is passed as a `struct timespec*`. Based on discussions online and the source
133                            // code (umtx_copyin_umtx_time() in kern_umtx.c), it seems to default to CLOCK_REALTIME,
134                            // so that's what we also do.
135                            // Discussion in golang: https://github.com/golang/go/issues/17168#issuecomment-250235271
136                            Some((TimeoutClock::RealTime, TimeoutAnchor::Relative, duration))
137                        } else {
138                            return this.set_last_error_and_return(LibcError("EINVAL"), dest);
139                        }
140                    };
141
142                    let dest = dest.clone();
143                    this.futex_wait(
144                        futex_ref,
145                        u32::MAX, // we set the bitset to include all bits
146                        timeout,
147                        callback!(
148                            @capture<'tcx> {
149                                dest: MPlaceTy<'tcx>,
150                            }
151                            |ecx, unblock: UnblockKind| match unblock {
152                                UnblockKind::Ready => {
153                                    // From the manual:
154                                    // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP
155                                    // sub-requests of the UMTX_OP_SHM request, will return zero.
156                                    ecx.write_int(0, &dest)
157                                }
158                                UnblockKind::TimedOut => {
159                                    ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
160                                }
161                            }
162                        ),
163                    );
164                    interp_ok(())
165                } else {
166                    // The manual doesn’t specify what should happen if the futex value doesn’t match the expected one.
167                    // On FreeBSD 14.2, testing shows that WAIT operations return 0 even when the value is incorrect.
168                    this.write_int(0, dest)?;
169                    interp_ok(())
170                }
171            }
172            // UMTX_OP_WAKE and UMTX_OP_WAKE_PRIVATE only differ in whether they work across
173            // processes or not. For Miri, we can treat them the same.
174            op if op == wake || op == wake_private => {
175                let Some(futex_ref) =
176                    this.get_sync_or_init(obj, |_| FreeBsdFutex { futex: Default::default() })
177                else {
178                    // From Linux implemenation:
179                    // No AllocId, or no live allocation at that AllocId.
180                    // Return an error code. (That seems nicer than silently doing something non-intuitive.)
181                    // This means that if an address gets reused by a new allocation,
182                    // we'll use an independent futex queue for this... that seems acceptable.
183                    return this.set_last_error_and_return(LibcError("EFAULT"), dest);
184                };
185                let futex_ref = futex_ref.futex.clone();
186
187                // Saturating cast for when usize is smaller than u64.
188                let count = usize::try_from(val).unwrap_or(usize::MAX);
189
190                // Read the Linux futex wake implementation in Miri to understand why this fence is needed.
191                this.atomic_fence(AtomicFenceOrd::SeqCst)?;
192
193                // `_umtx_op` doesn't return the amount of woken threads.
194                let _woken = this.futex_wake(
195                    &futex_ref,
196                    u32::MAX, // we set the bitset to include all bits
197                    count,
198                )?;
199
200                // From the manual:
201                // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP
202                // sub-requests of the UMTX_OP_SHM request, will return zero.
203                this.write_int(0, dest)?;
204                interp_ok(())
205            }
206            op => {
207                throw_unsup_format!("Miri does not support `_umtx_op` syscall with op={}", op)
208            }
209        }
210    }
211
212    /// Parses a `_umtx_time` struct.
213    /// Returns `None` if the underlying `timespec` struct is invalid.
214    fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<UmtxTime>> {
215        let this = self.eval_context_mut();
216        // Only flag allowed is UMTX_ABSTIME.
217        let abs_time = this.eval_libc_u32("UMTX_ABSTIME");
218
219        let timespec_place = this.project_field(ut, FieldIdx::from_u32(0))?;
220        // Inner `timespec` must still be valid.
221        let duration = match this.read_timespec(&timespec_place)? {
222            Some(dur) => dur,
223            None => return interp_ok(None),
224        };
225
226        let flags_place = this.project_field(ut, FieldIdx::from_u32(1))?;
227        let flags = this.read_scalar(&flags_place)?.to_u32()?;
228        let abs_time_flag = flags == abs_time;
229
230        let clock_id_place = this.project_field(ut, FieldIdx::from_u32(2))?;
231        let clock_id = this.read_scalar(&clock_id_place)?.to_i32()?;
232        let timeout_clock = this.translate_umtx_time_clock_id(clock_id)?;
233
234        interp_ok(Some(UmtxTime { timeout: duration, abs_time: abs_time_flag, timeout_clock }))
235    }
236
237    /// Translate raw FreeBSD clockid to a Miri TimeoutClock.
238    /// FIXME: share this code with the pthread and clock_gettime shims.
239    fn translate_umtx_time_clock_id(&mut self, raw_id: i32) -> InterpResult<'tcx, TimeoutClock> {
240        let this = self.eval_context_mut();
241
242        let timeout = if raw_id == this.eval_libc_i32("CLOCK_REALTIME") {
243            // RealTime clock can't be used in isolation mode.
244            this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME` timeout")?;
245            TimeoutClock::RealTime
246        } else if raw_id == this.eval_libc_i32("CLOCK_MONOTONIC") {
247            TimeoutClock::Monotonic
248        } else {
249            throw_unsup_format!("unsupported clock id {raw_id}");
250        };
251        interp_ok(timeout)
252    }
253}