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, SyncObj};
8use crate::*;
9
10pub struct FreeBsdFutex {
11 futex: FutexRef,
12}
13
14impl SyncObj for FreeBsdFutex {}
15
16/// Extended variant of the `timespec` struct.
17pub struct UmtxTime {
18 timeout: Duration,
19 abs_time: bool,
20 timeout_clock: TimeoutClock,
21}
22
23impl<'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 run
30 /// `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 struct
32 /// `uaddr2`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct
33 /// `dest`: the place this syscall returns to, 0 for success, -1 for failure
34 ///
35 /// # Note
36 /// 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();
47
48 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)?;
53
54 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");
57
58 let wake = this.eval_libc_i32("UMTX_OP_WAKE");
59 let wake_private = this.eval_libc_i32("UMTX_OP_WAKE_PRIVATE");
60
61 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 );
67
68 match op {
69 // UMTX_OP_WAIT_UINT and UMTX_OP_WAIT_UINT_PRIVATE only differ in whether they work across
70 // 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);
75
76 // 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 = this
79 .read_scalar_atomic(&obj, AtomicReadOrd::Acquire)?
80 .to_bits(obj_layout.size)?; // isize and u32 can have different sizes
81
82 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 = this
86 .get_sync_or_init(obj.ptr(), |_| FreeBsdFutex { futex: Default::default() })
87 .unwrap()
88 .futex
89 .clone();
90
91 // From the manual:
92 // The timeout is specified by passing either the address of `struct timespec`, or its
93 // extended variant, `struct _umtx_time`, as the `uaddr2` argument of _umtx_op().
94 // They are distinguished by the `uaddr` value, which must be equal
95 // 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 parameter
98 None
99 } 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);
103
104 let umtx_time = match this.read_umtx_time(&umtx_time_place)? {
105 Some(ut) => ut,
106 None => {
107 return this
108 .set_last_error_and_return(LibcError("EINVAL"), dest);
109 }
110 };
111
112 let anchor = if umtx_time.abs_time {
113 TimeoutAnchor::Absolute
114 } else {
115 TimeoutAnchor::Relative
116 };
117
118 Some((umtx_time.timeout_clock, anchor, umtx_time.timeout))
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")?;
122
123 // `uaddr2` points to a `struct timespec`.
124 let timespec = this.ptr_to_mplace(uaddr2, timespec_layout);
125 let duration = match this.read_timespec(×pec)? {
126 Some(duration) => duration,
127 None => {
128 return this
129 .set_last_error_and_return(LibcError("EINVAL"), dest);
130 }
131 };
132
133 // FreeBSD does not seem to document which clock is used when the timeout
134 // is passed as a `struct timespec*`. Based on discussions online and the source
135 // code (umtx_copyin_umtx_time() in kern_umtx.c), it seems to default to CLOCK_REALTIME,
136 // so that's what we also do.
137 // Discussion in golang: https://github.com/golang/go/issues/17168#issuecomment-250235271
138 Some((TimeoutClock::RealTime, TimeoutAnchor::Relative, duration))
139 } else {
140 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
141 }
142 };
143
144 let dest = dest.clone();
145 this.futex_wait(
146 futex_ref,
147 u32::MAX, // we set the bitset to include all bits
148 timeout,
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_LOOKUP
157 // sub-requests of the UMTX_OP_SHM request, will return zero.
158 ecx.write_int(0, &dest)
159 }
160 UnblockKind::TimedOut => {
161 ecx.set_last_error_and_return(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 across
175 // 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 implemenation:
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_last_error_and_return(LibcError("EFAULT"), dest);
186 };
187 let futex_ref = futex_ref.futex.clone();
188
189 // Saturating cast for when usize is smaller than u64.
190 let count = usize::try_from(val).unwrap_or(usize::MAX);
191
192 // Read the Linux futex wake implementation in Miri to understand why this fence is needed.
193 this.atomic_fence(AtomicFenceOrd::SeqCst)?;
194
195 // `_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 bits
199 count,
200 )?;
201
202 // From the manual:
203 // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP
204 // 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 }
213
214 /// 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");
220
221 let timespec_place = this.project_field(ut, FieldIdx::from_u32(0))?;
222 // Inner `timespec` must still be valid.
223 let duration = match this.read_timespec(×pec_place)? {
224 Some(dur) => dur,
225 None => return interp_ok(None),
226 };
227
228 let flags_place = this.project_field(ut, FieldIdx::from_u32(1))?;
229 let flags = this.read_scalar(&flags_place)?.to_u32()?;
230 let abs_time_flag = flags == abs_time;
231
232 let clock_id_place = this.project_field(ut, FieldIdx::from_u32(2))?;
233 let clock_id = this.read_scalar(&clock_id_place)?;
234 let Some(timeout_clock) = this.parse_clockid(clock_id) else {
235 throw_unsup_format!("unsupported clock")
236 };
237 if timeout_clock == TimeoutClock::RealTime {
238 this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME`")?;
239 }
240
241 interp_ok(Some(UmtxTime { timeout: duration, abs_time: abs_time_flag, timeout_clock }))
242 }
243}