1 use crate::thread::Time;
3 use rustc_target::abi::{Align, Size};
4 use std::time::{Instant, SystemTime};
6 /// Implementation of the SYS_futex syscall.
7 /// `args` is the arguments *after* the syscall number.
9 this: &mut MiriEvalContext<'_, 'tcx>,
10 args: &[OpTy<'tcx, Tag>],
11 dest: &PlaceTy<'tcx, Tag>,
12 ) -> InterpResult<'tcx> {
13 // The amount of arguments used depends on the type of futex operation.
14 // The full futex syscall takes six arguments (excluding the syscall
15 // number), which is also the maximum amount of arguments a linux syscall
16 // can take on most architectures.
17 // However, not all futex operations use all six arguments. The unused ones
18 // may or may not be left out from the `syscall()` call.
19 // Therefore we don't use `check_arg_count` here, but only check for the
20 // number of arguments to fall within a range.
23 "incorrect number of arguments for `futex` syscall: got {}, expected at least 3",
28 // The first three arguments (after the syscall number itself) are the same to all futex operations:
29 // (int *addr, int op, int val).
30 // We checked above that these definitely exist.
31 let addr = this.read_immediate(&args[0])?;
32 let op = this.read_scalar(&args[1])?.to_i32()?;
33 let val = this.read_scalar(&args[2])?.to_i32()?;
35 let thread = this.get_active_thread();
36 let addr_scalar = addr.to_scalar()?;
37 let addr_usize = addr_scalar.to_machine_usize(this)?;
39 let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
40 let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
41 let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?;
42 let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
43 let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?;
44 let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;
46 // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
47 // Miri doesn't support that anyway, so we ignore that flag.
48 match op & !futex_private {
49 // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
50 // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
51 // or *timeout expires. `timeout == null` for an infinite timeout.
53 // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
54 // This is identical to FUTEX_WAIT, except:
55 // - The timeout is absolute rather than relative.
56 // - You can specify the bitset to selecting what WAKE operations to respond to.
57 op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
58 let wait_bitset = op & !futex_realtime == futex_wait_bitset;
60 let bitset = if wait_bitset {
63 "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6",
67 let _timeout = this.read_pointer(&args[3])?;
68 let _uaddr2 = this.read_pointer(&args[4])?;
69 this.read_scalar(&args[5])?.to_u32()?
73 "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 4",
81 let einval = this.eval_libc("EINVAL")?;
82 this.set_last_error(einval)?;
83 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
87 // `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
88 let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?;
89 let timeout_time = if this.ptr_is_null(timeout.ptr)? {
92 this.check_no_isolation(
93 "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout",
95 let duration = match this.read_timespec(&timeout)? {
96 Some(duration) => duration,
98 let einval = this.eval_libc("EINVAL")?;
99 this.set_last_error(einval)?;
100 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
104 Some(if wait_bitset {
105 // FUTEX_WAIT_BITSET uses an absolute timestamp.
106 if op & futex_realtime != 0 {
107 Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
109 Time::Monotonic(this.machine.time_anchor.checked_add(duration).unwrap())
112 // FUTEX_WAIT uses a relative timestamp.
113 if op & futex_realtime != 0 {
114 Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
116 Time::Monotonic(Instant::now().checked_add(duration).unwrap())
120 // Check the pointer for alignment and validity.
121 // The API requires `addr` to be a 4-byte aligned pointer, and will
122 // use the 4 bytes at the given address as an (atomic) i32.
123 this.check_ptr_access_align(
124 this.scalar_to_ptr(addr_scalar)?,
126 Align::from_bytes(4).unwrap(),
127 CheckInAllocMsg::MemoryAccessTest,
129 // There may be a concurrent thread changing the value of addr
130 // and then invoking the FUTEX_WAKE syscall. It is critical that the
131 // effects of this and the other thread are correctly observed,
132 // otherwise we will deadlock.
134 // There are two scenarios to consider:
135 // 1. If we (FUTEX_WAIT) execute first, we'll push ourselves into
136 // the waiters queue and go to sleep. They (addr write & FUTEX_WAKE)
137 // will see us in the queue and wake us up.
138 // 2. If they (addr write & FUTEX_WAKE) execute first, we must observe
139 // addr's new value. If we see an outdated value that happens to equal
140 // the expected val, then we'll put ourselves to sleep with no one to wake us
141 // up, so we end up with a deadlock. This is prevented by having a SeqCst
142 // fence inside FUTEX_WAKE syscall, and another SeqCst fence
143 // below, the atomic read on addr after the SeqCst fence is guaranteed
144 // not to see any value older than the addr write immediately before
145 // calling FUTEX_WAKE. We'll see futex_val != val and return without
148 // Note that the fences do not create any happens-before relationship.
149 // The read sees the write immediately before the fence not because
150 // one happens after the other, but is instead due to a guarantee unique
151 // to SeqCst fences that restricts what an atomic read placed AFTER the
152 // fence can see. The read still has to be atomic, otherwise it's a data
153 // race. This guarantee cannot be achieved with acquire-release fences
154 // since they only talk about reads placed BEFORE a fence - and places
155 // no restrictions on what the read itself can see, only that there is
156 // a happens-before between the fences IF the read happens to see the
157 // right value. This is useless to us, since we need the read itself
158 // to see an up-to-date value.
160 // The above case distinction is valid since both FUTEX_WAIT and FUTEX_WAKE
161 // contain a SeqCst fence, therefore inducting a total order between the operations.
162 // It is also critical that the fence, the atomic load, and the comparison in FUTEX_WAIT
163 // altogether happen atomically. If the other thread's fence in FUTEX_WAKE
164 // gets interleaved after our fence, then we lose the guarantee on the
165 // atomic load being up-to-date; if the other thread's write on addr and FUTEX_WAKE
166 // call are interleaved after the load but before the comparison, then we get a TOCTOU
167 // race condition, and go to sleep thinking the other thread will wake us up,
168 // even though they have already finished.
170 // Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
171 // do anything special to guarantee fence-load-comparison atomicity.
172 this.atomic_fence(&[], AtomicFenceOrd::SeqCst)?;
173 // Read an `i32` through the pointer, regardless of any wrapper types.
174 // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
176 .read_scalar_at_offset_atomic(
179 this.machine.layouts.i32,
180 AtomicReadOrd::Relaxed,
183 if val == futex_val {
184 // The value still matches, so we block the thread make it wait for FUTEX_WAKE.
185 this.block_thread(thread);
186 this.futex_wait(addr_usize, thread, bitset);
187 // Succesfully waking up from FUTEX_WAIT always returns zero.
188 this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
189 // Register a timeout callback if a timeout was specified.
190 // This callback will override the return value when the timeout triggers.
192 if let Some(timeout_time) = timeout_time {
193 this.register_timeout_callback(
196 Box::new(move |this| {
197 this.unblock_thread(thread);
198 this.futex_remove_waiter(addr_usize, thread);
199 let etimedout = this.eval_libc("ETIMEDOUT")?;
200 this.set_last_error(etimedout)?;
201 this.write_scalar(Scalar::from_machine_isize(-1, this), &dest)?;
207 // The futex value doesn't match the expected value, so we return failure
208 // right away without sleeping: -1 and errno set to EAGAIN.
209 let eagain = this.eval_libc("EAGAIN")?;
210 this.set_last_error(eagain)?;
211 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
214 // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
215 // Wakes at most `val` threads waiting on the futex at `addr`.
216 // Returns the amount of threads woken up.
217 // Does not access the futex value at *addr.
218 // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
219 // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
220 op if op == futex_wake || op == futex_wake_bitset => {
221 let bitset = if op == futex_wake_bitset {
224 "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6",
228 let _timeout = this.read_pointer(&args[3])?;
229 let _uaddr2 = this.read_pointer(&args[4])?;
230 this.read_scalar(&args[5])?.to_u32()?
235 let einval = this.eval_libc("EINVAL")?;
236 this.set_last_error(einval)?;
237 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
240 // Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
241 // will see the latest value on addr which could be changed by our caller
242 // before doing the syscall.
243 this.atomic_fence(&[], AtomicFenceOrd::SeqCst)?;
246 if let Some(thread) = this.futex_wake(addr_usize, bitset) {
247 this.unblock_thread(thread);
248 this.unregister_timeout_callback_if_exists(thread);
254 this.write_scalar(Scalar::from_machine_isize(n, this), dest)?;
256 op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),