]> git.lizzy.rs Git - rust.git/blob - src/shims/posix/linux/sync.rs
09558554aa221dd66bee709d4fef426bae59c38e
[rust.git] / src / shims / posix / linux / sync.rs
1 use crate::thread::Time;
2 use crate::*;
3 use rustc_target::abi::{Align, Size};
4 use std::time::{Instant, SystemTime};
5
6 /// Implementation of the SYS_futex syscall.
7 pub fn futex<'tcx>(
8     this: &mut MiriEvalContext<'_, 'tcx>,
9     args: &[OpTy<'tcx, Tag>],
10     dest: PlaceTy<'tcx, Tag>,
11 ) -> InterpResult<'tcx> {
12     // The amount of arguments used depends on the type of futex operation.
13     // The full futex syscall takes six arguments (excluding the syscall
14     // number), which is also the maximum amount of arguments a linux syscall
15     // can take on most architectures.
16     // However, not all futex operations use all six arguments. The unused ones
17     // may or may not be left out from the `syscall()` call.
18     // Therefore we don't use `check_arg_count` here, but only check for the
19     // number of arguments to fall within a range.
20     if !(4..=7).contains(&args.len()) {
21         throw_ub_format!("incorrect number of arguments for futex syscall: got {}, expected between 4 and 7 (inclusive)", args.len());
22     }
23
24     // The first three arguments (after the syscall number itself) are the same to all futex operations:
25     //     (int *addr, int op, int val).
26     // We checked above that these definitely exist.
27     let addr = this.read_immediate(args[1])?;
28     let op = this.read_scalar(args[2])?.to_i32()?;
29     let val = this.read_scalar(args[3])?.to_i32()?;
30
31     // The raw pointer value is used to identify the mutex.
32     // Not all mutex operations actually read from this address or even require this address to exist.
33     // This will make FUTEX_WAKE fail on an integer cast to a pointer. But FUTEX_WAIT on
34     // such a pointer can never work anyway, so that seems fine.
35     let futex_ptr = this.force_ptr(addr.to_scalar()?)?;
36
37     let thread = this.get_active_thread();
38
39     let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
40     let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
41     let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
42     let futex_realtime = this.eval_libc_i32("FUTEX_CLOCK_REALTIME")?;
43
44     // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
45     // Miri doesn't support that anyway, so we ignore that flag.
46     match op & !futex_private {
47         // FUTEX_WAIT: (int *addr, int op = FUTEX_WAIT, int val, const timespec *timeout)
48         // Blocks the thread if *addr still equals val. Wakes up when FUTEX_WAKE is called on the same address,
49         // or *timeout expires. `timeout == null` for an infinite timeout.
50         op if op & !futex_realtime == futex_wait => {
51             if args.len() < 5 {
52                 throw_ub_format!("incorrect number of arguments for FUTEX_WAIT syscall: got {}, expected at least 5", args.len());
53             }
54             let timeout = args[4];
55             let timeout_time = if this.is_null(this.read_scalar(timeout)?.check_init()?)? {
56                 None
57             } else {
58                 let duration = match this.read_timespec(timeout)? {
59                     Some(duration) => duration,
60                     None => {
61                         let einval = this.eval_libc("EINVAL")?;
62                         this.set_last_error(einval)?;
63                         this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
64                         return Ok(());
65                     }
66                 };
67                 this.check_no_isolation("FUTEX_WAIT with timeout")?;
68                 Some(if op & futex_realtime != 0 {
69                     Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
70                 } else {
71                     Time::Monotonic(Instant::now().checked_add(duration).unwrap())
72                 })
73             };
74             // Check the pointer for alignment and validity.
75             // The API requires `addr` to be a 4-byte aligned pointer, and will
76             // use the 4 bytes at the given address as an (atomic) i32.
77             this.memory.check_ptr_access(addr.to_scalar()?, Size::from_bytes(4), Align::from_bytes(4).unwrap())?;
78             // Read an `i32` through the pointer, regardless of any wrapper types.
79             // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
80             let futex_val = this.read_scalar_at_offset(addr.into(), 0, this.machine.layouts.i32)?.to_i32()?;
81             if val == futex_val {
82                 // The value still matches, so we block the trait make it wait for FUTEX_WAKE.
83                 this.block_thread(thread);
84                 this.futex_wait(futex_ptr, thread);
85                 // Succesfully waking up from FUTEX_WAIT always returns zero.
86                 this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
87                 // Register a timeout callback if a timeout was specified.
88                 // This callback will override the return value when the timeout triggers.
89                 if let Some(timeout_time) = timeout_time {
90                     this.register_timeout_callback(
91                         thread,
92                         timeout_time,
93                         Box::new(move |this| {
94                             this.unblock_thread(thread);
95                             this.futex_remove_waiter(futex_ptr, thread);
96                             let etimedout = this.eval_libc("ETIMEDOUT")?;
97                             this.set_last_error(etimedout)?;
98                             this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
99                             Ok(())
100                         }),
101                     );
102                 }
103             } else {
104                 // The futex value doesn't match the expected value, so we return failure
105                 // right away without sleeping: -1 and errno set to EAGAIN.
106                 let eagain = this.eval_libc("EAGAIN")?;
107                 this.set_last_error(eagain)?;
108                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
109             }
110         }
111         // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
112         // Wakes at most `val` threads waiting on the futex at `addr`.
113         // Returns the amount of threads woken up.
114         // Does not access the futex value at *addr.
115         op if op == futex_wake => {
116             let mut n = 0;
117             for _ in 0..val {
118                 if let Some(thread) = this.futex_wake(futex_ptr) {
119                     this.unblock_thread(thread);
120                     this.unregister_timeout_callback_if_exists(thread);
121                     n += 1;
122                 } else {
123                     break;
124                 }
125             }
126             this.write_scalar(Scalar::from_machine_isize(n, this), dest)?;
127         }
128         op => throw_unsup_format!("miri does not support SYS_futex operation {}", op),
129     }
130
131     Ok(())
132 }