]> git.lizzy.rs Git - rust.git/blob - src/shims/posix/linux/sync.rs
rustup
[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 args.len() < 4 {
21         throw_ub_format!(
22             "incorrect number of arguments for `futex` syscall: got {}, expected at least 4",
23             args.len()
24         );
25     }
26
27     // The first three arguments (after the syscall number itself) are the same to all futex operations:
28     //     (int *addr, int op, int val).
29     // We checked above that these definitely exist.
30     let addr = this.read_immediate(&args[1])?;
31     let op = this.read_scalar(&args[2])?.to_i32()?;
32     let val = this.read_scalar(&args[3])?.to_i32()?;
33
34     let thread = this.get_active_thread();
35     let addr_scalar = addr.to_scalar()?;
36
37     let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?;
38     let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?;
39     let futex_wait_bitset = this.eval_libc_i32("FUTEX_WAIT_BITSET")?;
40     let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?;
41     let futex_wake_bitset = this.eval_libc_i32("FUTEX_WAKE_BITSET")?;
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         //
51         // FUTEX_WAIT_BITSET: (int *addr, int op = FUTEX_WAIT_BITSET, int val, const timespec *timeout, int *_ignored, unsigned int bitset)
52         // This is identical to FUTEX_WAIT, except:
53         //  - The timeout is absolute rather than relative.
54         //  - You can specify the bitset to selecting what WAKE operations to respond to.
55         op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
56             let wait_bitset = op & !futex_realtime == futex_wait_bitset;
57
58             let bitset = if wait_bitset {
59                 if args.len() != 7 {
60                     throw_ub_format!(
61                         "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected 7",
62                         args.len()
63                     );
64                 }
65                 this.read_scalar(&args[6])?.to_u32()?
66             } else {
67                 if args.len() < 5 {
68                     throw_ub_format!(
69                         "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT`: got {}, expected at least 5",
70                         args.len()
71                     );
72                 }
73                 u32::MAX
74             };
75
76             if bitset == 0 {
77                 let einval = this.eval_libc("EINVAL")?;
78                 this.set_last_error(einval)?;
79                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
80                 return Ok(());
81             }
82
83             // `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!).
84             let timeout = this.ref_to_mplace(&this.read_immediate(&args[4])?)?;
85             let timeout_time = if this.ptr_is_null(timeout.ptr)? {
86                 None
87             } else {
88                 this.check_no_isolation(
89                     "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout",
90                 )?;
91                 let duration = match this.read_timespec(&timeout)? {
92                     Some(duration) => duration,
93                     None => {
94                         let einval = this.eval_libc("EINVAL")?;
95                         this.set_last_error(einval)?;
96                         this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
97                         return Ok(());
98                     }
99                 };
100                 Some(if wait_bitset {
101                     // FUTEX_WAIT_BITSET uses an absolute timestamp.
102                     if op & futex_realtime != 0 {
103                         Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
104                     } else {
105                         Time::Monotonic(this.machine.time_anchor.checked_add(duration).unwrap())
106                     }
107                 } else {
108                     // FUTEX_WAIT uses a relative timestamp.
109                     if op & futex_realtime != 0 {
110                         Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
111                     } else {
112                         Time::Monotonic(Instant::now().checked_add(duration).unwrap())
113                     }
114                 })
115             };
116             // Check the pointer for alignment and validity.
117             // The API requires `addr` to be a 4-byte aligned pointer, and will
118             // use the 4 bytes at the given address as an (atomic) i32.
119             this.check_ptr_access_align(
120                 this.scalar_to_ptr(addr_scalar),
121                 Size::from_bytes(4),
122                 Align::from_bytes(4).unwrap(),
123                 CheckInAllocMsg::MemoryAccessTest,
124             )?;
125             // Read an `i32` through the pointer, regardless of any wrapper types.
126             // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
127             // FIXME: this fails if `addr` is not a pointer type.
128             // The atomic ordering for futex(https://man7.org/linux/man-pages/man2/futex.2.html):
129             //  "The load of the value of the futex word is an
130             //   atomic memory access (i.e., using atomic machine instructions
131             //   of the respective architecture).  This load, the comparison
132             //   with the expected value, and starting to sleep are performed
133             //   atomically and totally ordered with respect to other futex
134             //   operations on the same futex word."
135             // SeqCst is total order over all operations.
136             // FIXME: check if this should be changed when weak memory orders are added.
137             let futex_val = this
138                 .read_scalar_at_offset_atomic(
139                     &addr.into(),
140                     0,
141                     this.machine.layouts.i32,
142                     AtomicReadOp::SeqCst,
143                 )?
144                 .to_i32()?;
145             if val == futex_val {
146                 // The value still matches, so we block the trait make it wait for FUTEX_WAKE.
147                 this.block_thread(thread);
148                 this.futex_wait(addr_scalar.to_machine_usize(this)?, thread, bitset);
149                 // Succesfully waking up from FUTEX_WAIT always returns zero.
150                 this.write_scalar(Scalar::from_machine_isize(0, this), dest)?;
151                 // Register a timeout callback if a timeout was specified.
152                 // This callback will override the return value when the timeout triggers.
153                 let dest = *dest;
154                 if let Some(timeout_time) = timeout_time {
155                     this.register_timeout_callback(
156                         thread,
157                         timeout_time,
158                         Box::new(move |this| {
159                             this.unblock_thread(thread);
160                             this.futex_remove_waiter(addr_scalar.to_machine_usize(this)?, thread);
161                             let etimedout = this.eval_libc("ETIMEDOUT")?;
162                             this.set_last_error(etimedout)?;
163                             this.write_scalar(Scalar::from_machine_isize(-1, this), &dest)?;
164                             Ok(())
165                         }),
166                     );
167                 }
168             } else {
169                 // The futex value doesn't match the expected value, so we return failure
170                 // right away without sleeping: -1 and errno set to EAGAIN.
171                 let eagain = this.eval_libc("EAGAIN")?;
172                 this.set_last_error(eagain)?;
173                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
174             }
175         }
176         // FUTEX_WAKE: (int *addr, int op = FUTEX_WAKE, int val)
177         // Wakes at most `val` threads waiting on the futex at `addr`.
178         // Returns the amount of threads woken up.
179         // Does not access the futex value at *addr.
180         // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
181         // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
182         op if op == futex_wake || op == futex_wake_bitset => {
183             let bitset = if op == futex_wake_bitset {
184                 if args.len() != 7 {
185                     throw_ub_format!(
186                         "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected 7",
187                         args.len()
188                     );
189                 }
190                 this.read_scalar(&args[6])?.to_u32()?
191             } else {
192                 u32::MAX
193             };
194             if bitset == 0 {
195                 let einval = this.eval_libc("EINVAL")?;
196                 this.set_last_error(einval)?;
197                 this.write_scalar(Scalar::from_machine_isize(-1, this), dest)?;
198                 return Ok(());
199             }
200             let mut n = 0;
201             for _ in 0..val {
202                 if let Some(thread) = this.futex_wake(addr_scalar.to_machine_usize(this)?, bitset) {
203                     this.unblock_thread(thread);
204                     this.unregister_timeout_callback_if_exists(thread);
205                     n += 1;
206                 } else {
207                     break;
208                 }
209             }
210             this.write_scalar(Scalar::from_machine_isize(n, this), dest)?;
211         }
212         op => throw_unsup_format!("Miri does not support `futex` syscall with op={}", op),
213     }
214
215     Ok(())
216 }