]> git.lizzy.rs Git - rust.git/blob - src/shims/posix/linux/sync.rs
Add comments that document SYS_futex better.
[rust.git] / src / shims / posix / linux / sync.rs
1 use crate::*;
2 use rustc_target::abi::{Align, Size};
3
4 /// Implementation of the SYS_futex syscall.
5 pub fn futex<'tcx>(
6     this: &mut MiriEvalContext<'_, 'tcx>,
7     args: &[OpTy<'tcx, Tag>],
8     dest: PlaceTy<'tcx, Tag>,
9 ) -> InterpResult<'tcx> {
10     // The amount of arguments used depends on the type of futex operation.
11     // Some users always pass all arguments, even the unused ones, due to how they wrap this syscall in their code base.
12     // Some other users pass only the arguments the operation actually needs. So we don't use `check_arg_count` here.
13     if !(4..=7).contains(&args.len()) {
14         throw_ub_format!("incorrect number of arguments for futex syscall: got {}, expected between 4 and 7 (inclusive)", args.len());
15     }
16
17     // The first three arguments (after the syscall number itself) are the same to all futex operations:
18     //     (int *addr, int op, int val).
19     // Although note that the first one is often passed as a different pointer type, e.g. `*const AtomicU32` or `*mut u32`.
20     let addr = this.deref_operand(args[1])?;
21     let op = this.read_scalar(args[2])?.to_i32()?;
22     let val = this.read_scalar(args[3])?.to_i32()?;
23
24     // The raw pointer value is used to identify the mutex.
25     // Not all mutex operations actually read from this address or even require this address to exist.
26     let futex_ptr = addr.ptr.assert_ptr();
27
28     let thread = this.get_active_thread();
29
30     let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
31     let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
32     let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
33
34     // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
35     // Miri doesn't support that anyway, so we ignore that flag.
36     match op & !futex_private {
37         // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
38         // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
39         // or *timeout expires. `timeout == null` for an infinite timeout.
40         op if op == futex_wait => {
41             if args.len() < 5 {
42                 throw_ub_format!("incorrect number of arguments for FUTEX_WAIT syscall: got {}, expected at least 5", args.len());
43             }
44             let timeout = this.read_scalar(args[4])?.check_init()?;
45             if !this.is_null(timeout)? {
46                 // FIXME: Implement timeouts. The condvar waiting code is probably a good example to start with.
47                 // Note that a triggered timeout should have this syscall return with -1 and errno set to ETIMEOUT.
48                 throw_ub_format!("miri does not support timeouts for futex operations");
49             }
50             // Check the pointer for alignment. Atomic operations are only available for fully aligned values.
51             this.memory.check_ptr_access(addr.ptr.into(), Size::from_bytes(4), Align::from_bytes(4).unwrap())?;
52             // Read an `i32` through the pointer, regardless of any wrapper types (e.g. `AtomicI32`).
53             let futex_val = this.read_scalar(addr.offset(Size::ZERO, MemPlaceMeta::None, this.machine.layouts.i32, this)?.into())?.to_i32()?;
54             if val == futex_val {
55                 // The value still matches, so we block the trait make it wait for FUTEX_WAKE.
56                 this.block_thread(thread);
57                 this.futex_wait(futex_ptr, thread);
58                 // Succesfully waking up from FUTEX_WAIT always returns zero.
59                 this.write_scalar(Scalar::from_i32(0), dest)?;
60             } else {
61                 // The futex value doesn't match the expected value, so we return failure
62                 // right away without sleeping: -1 and errno set to EAGAIN.
63                 let eagain = this.eval_libc("EAGAIN")?;
64                 this.set_last_error(eagain)?;
65                 this.write_scalar(Scalar::from_i32(-1), dest)?;
66             }
67         }
68         // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
69         // Wakes at most `val` threads waiting on the futex at `addr`.
70         // Returns the amount of threads woken up.
71         // Does not access the futex value at *addr.
72         op if op == futex_wake => {
73             let mut n = 0;
74             for _ in 0..val {
75                 if let Some(thread) = this.futex_wake(futex_ptr) {
76                     this.unblock_thread(thread);
77                     n += 1;
78                 } else {
79                     break;
80                 }
81             }
82             this.write_scalar(Scalar::from_i32(n), dest)?;
83         }
84         op => throw_unsup_format!("miri does not support SYS_futex operation {}", op),
85     }
86
87     Ok(())
88 }