miri/shims/unix/linux_like/sync.rs
1use crate::concurrency::sync::{FutexRef, SyncObj};
2use crate::shims::sig::Varargs;
3use crate::*;
4
5struct LinuxFutex {
6 futex: FutexRef,
7}
8
9impl SyncObj for LinuxFutex {}
10
11/// Implementation of the SYS_futex syscall.
12/// `args` is the arguments *including* the syscall number.
13pub fn futex<'tcx>(
14 ecx: &mut MiriInterpCx<'tcx>,
15 varargs: Varargs<'tcx, '_>,
16 dest: &MPlaceTy<'tcx>,
17) -> InterpResult<'tcx> {
18 let ([addr, op, val], varargs) =
19 ecx.check_varargs(shim_varargs![*u32, i32, u32], varargs, "syscall(SYS_futex, ...)")?;
20
21 // See <https://man7.org/linux/man-pages/man2/futex.2.html> for docs.
22 // The first three arguments (after the syscall number itself) are the same to all futex operations:
23 // (uint32_t *addr, int op, uint32_t val).
24 // We checked above that these definitely exist.
25 let addr = ecx.read_pointer(addr)?;
26 let op = ecx.read_scalar(op)?.to_i32()?;
27 let val = ecx.read_scalar(val)?.to_u32()?;
28
29 // This is a vararg function so we have to bring our own type for this pointer.
30 let addr = ecx.ptr_to_mplace(addr, ecx.machine.layouts.i32);
31
32 let futex_private = ecx.eval_libc_i32("FUTEX_PRIVATE_FLAG");
33 let futex_wait = ecx.eval_libc_i32("FUTEX_WAIT");
34 let futex_wait_bitset = ecx.eval_libc_i32("FUTEX_WAIT_BITSET");
35 let futex_wake = ecx.eval_libc_i32("FUTEX_WAKE");
36 let futex_wake_bitset = ecx.eval_libc_i32("FUTEX_WAKE_BITSET");
37 let futex_realtime = ecx.eval_libc_i32("FUTEX_CLOCK_REALTIME");
38
39 // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
40 // Miri doesn't support that anyway, so we ignore that flag.
41 match op & !futex_private {
42 // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
43 // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
44 // or *timeout expires. `timeout == null` for an infinite timeout.
45 //
46 // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
47 // This is identical to FUTEX_WAIT, except:
48 // - The timeout is absolute rather than relative.
49 // - You can specify the bitset to selecting what WAKE operations to respond to.
50 op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
51 let wait_bitset = op & !futex_realtime == futex_wait_bitset;
52
53 let (timeout, bitset) = if wait_bitset {
54 let ([timeout, uaddr2, bitset], _) = ecx.check_varargs(
55 shim_varargs![*libc::timespec, *u32, u32],
56 varargs,
57 "syscall(SYS_futex, ...)",
58 )?;
59 let uaddr2 = ecx.read_pointer(uaddr2)?;
60 if !ecx.ptr_is_null(uaddr2)? {
61 throw_ub_format!("`uaddr2` pointer must be null for `FUTEX_WAIT_BITSET`");
62 }
63 (timeout, ecx.read_scalar(bitset)?.to_u32()?)
64 } else {
65 let ([timeout], _) = ecx.check_varargs(
66 shim_varargs![*libc::timespec],
67 varargs,
68 "syscall(SYS_futex, ...)",
69 )?;
70 (timeout, u32::MAX)
71 };
72
73 if bitset == 0 {
74 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
75 }
76
77 let timeout = ecx.deref_pointer_as(timeout, ecx.libc_ty_layout("timespec"))?;
78 let deadline = if ecx.ptr_is_null(timeout.ptr())? {
79 None
80 } else {
81 let Some(duration) = ecx.read_timespec(&timeout)? else {
82 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
83 };
84 let timeout_clock = if op & futex_realtime == futex_realtime {
85 ecx.check_no_isolation(
86 "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`",
87 )?;
88 TimeoutClock::RealTime
89 } else {
90 TimeoutClock::Monotonic
91 };
92 let timeout_style = if wait_bitset {
93 // FUTEX_WAIT_BITSET uses an absolute timestamp.
94 TimeoutStyle::Absolute
95 } else {
96 // FUTEX_WAIT uses a relative timestamp.
97 TimeoutStyle::Relative
98 };
99 Some(ecx.machine.timeout(timeout_clock, timeout_style, duration))
100 };
101 // There may be a concurrent thread changing the value of addr
102 // and then invoking the FUTEX_WAKE syscall. It is critical that the
103 // effects of this and the other thread are correctly observed,
104 // otherwise we will deadlock.
105 //
106 // There are two scenarios to consider, depending on whether WAIT or WAKE goes first:
107 // 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into the waiters queue and
108 // go to sleep. They (FUTEX_WAKE) will see us in the queue and wake us up. It doesn't
109 // matter how the addr write is ordered.
110 // 2. If they (FUTEX_WAKE) execute first, that means the addr write is also before us
111 // (FUTEX_WAIT). It is crucial that we observe addr's new value. If we see an
112 // outdated value that happens to equal the expected val, then we'll put ourselves to
113 // sleep with no one to wake us up, so we end up with a deadlock. This is prevented
114 // by having a SeqCst fence inside FUTEX_WAKE syscall, and another SeqCst fence here
115 // in FUTEX_WAIT. The atomic read on addr after the SeqCst fence is guaranteed not to
116 // see any value older than the addr write immediately before calling FUTEX_WAKE.
117 // We'll see futex_val != val and return without sleeping.
118 //
119 // Note that the fences do not create any happens-before relationship.
120 // The read sees the write immediately before the fence not because
121 // one happens after the other, but is instead due to a guarantee unique
122 // to SeqCst fences that restricts what an atomic read placed AFTER the
123 // fence can see. The read still has to be atomic, otherwise it's a data
124 // race. This guarantee cannot be achieved with acquire-release fences
125 // since they only talk about reads placed BEFORE a fence - and places
126 // no restrictions on what the read itself can see, only that there is
127 // a happens-before between the fences IF the read happens to see the
128 // right value. This is useless to us, since we need the read itself
129 // to see an up-to-date value.
130 //
131 // The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE
132 // contain a SeqCst fence, therefore inducing a total order between the operations.
133 // It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT
134 // altogether happen atomically. If the other thread's fence in FUTEX_WAKE
135 // gets interleaved after our fence, then we lose the guarantee on the
136 // atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE
137 // call are interleaved after the load but before the comparison, then we get a TOCTOU
138 // race condition, and go to sleep thinking the other thread will wake us up,
139 // even though they have already finished.
140 //
141 // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
142 // do anything special to guarantee fence-load-comparison atomicity.
143 ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
144 // Read an `i32` through the pointer, regardless of any wrapper types.
145 // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
146 // We do an acquire read -- it only seems reasonable that if we observe a value here, we
147 // actually establish an ordering with that value.
148 let futex_val = ecx.read_scalar_atomic(&addr, AtomicReadOrd::Acquire)?.to_u32()?;
149 if val == futex_val {
150 // The value still matches, so we block the thread and make it wait for FUTEX_WAKE.
151
152 // This cannot fail since we already did an atomic acquire read on that pointer.
153 // Acquire reads are only allowed on mutable memory.
154 let futex_ref = ecx
155 .get_sync_or_init(addr.ptr(), |_| LinuxFutex { futex: Default::default() })
156 .unwrap()
157 .futex
158 .clone();
159
160 let dest = dest.clone();
161 ecx.futex_wait(
162 futex_ref,
163 bitset,
164 deadline,
165 callback!(
166 @capture<'tcx> {
167 dest: MPlaceTy<'tcx>,
168 }
169 |ecx, unblock: UnblockKind| match unblock {
170 UnblockKind::Ready => {
171 ecx.write_int(0, &dest)
172 }
173 UnblockKind::TimedOut => {
174 ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest)
175 }
176 }
177 ),
178 );
179 } else {
180 // The futex value doesn't match the expected value, so we return failure
181 // right away without sleeping: -1 and errno set to EAGAIN.
182 return ecx.set_errno_and_return_neg1(LibcError("EAGAIN"), dest);
183 }
184 }
185 // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
186 // Wakes at most `val` threads waiting on the futex at `addr`.
187 // Returns the amount of threads woken up.
188 // Does not access the futex value at *addr.
189 // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
190 // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
191 op if op == futex_wake || op == futex_wake_bitset => {
192 let Some(futex_ref) =
193 ecx.get_sync_or_init(addr.ptr(), |_| LinuxFutex { futex: Default::default() })
194 else {
195 // No AllocId, or no live allocation at that AllocId.
196 // Return an error code. (That seems nicer than silently doing something non-intuitive.)
197 // This means that if an address gets reused by a new allocation,
198 // we'll use an independent futex queue for this... that seems acceptable.
199 return ecx.set_errno_and_return_neg1(LibcError("EFAULT"), dest);
200 };
201 let futex_ref = futex_ref.futex.clone();
202
203 let bitset = if op == futex_wake_bitset {
204 let ([timeout, uaddr2, bitset], _) = ecx.check_varargs(
205 shim_varargs![*libc::timespec, *u32, u32],
206 varargs,
207 "syscall(SYS_futex, ...)",
208 )?;
209 let timeout = ecx.read_pointer(timeout)?;
210 if !ecx.ptr_is_null(timeout)? {
211 throw_ub_format!("`timeout` pointer must be null for `FUTEX_WAKE_BITSET`");
212 }
213 let uaddr2 = ecx.read_pointer(uaddr2)?;
214 if !ecx.ptr_is_null(uaddr2)? {
215 throw_ub_format!("`uaddr2` pointer must be null for `FUTEX_WAKE_BITSET`");
216 }
217 ecx.read_scalar(bitset)?.to_u32()?
218 } else {
219 u32::MAX
220 };
221 if bitset == 0 {
222 return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
223 }
224 // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
225 // will see the latest value on addr which could be changed by our caller
226 // before doing the syscall.
227 ecx.atomic_fence(AtomicFenceOrd::SeqCst)?;
228 let woken = ecx.futex_wake(&futex_ref, bitset, val.try_into().unwrap())?;
229 ecx.write_scalar(Scalar::from_target_isize(woken.try_into().unwrap(), ecx), dest)?;
230 }
231 op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
232 }
233
234 interp_ok(())
235}