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