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