1
- use crate :: * ;
1
+ use std:: time:: Duration ;
2
+
2
3
use crate :: shims:: unix:: env:: EvalContextExt ;
4
+ use crate :: * ;
3
5
4
6
/// Implementation of the SYS_futex syscall.
5
7
/// `args` is the arguments *after* the syscall number.
@@ -43,6 +45,46 @@ pub fn futex<'tcx>(
43
45
let futex_unlock_pi = this. eval_libc_i32 ( "FUTEX_UNLOCK_PI" ) ;
44
46
let futex_waiters = this. eval_libc_u32 ( "FUTEX_WAITERS" ) ;
45
47
48
+ // Ok(None) for EINVAL set, Ok(Some(None)) for no timeout (infinity), Ok(Some(Some(...))) for a timeout.
49
+ // Forgive me, I don't want to create an enum for this return value.
50
+ fn read_timeout < ' tcx > (
51
+ this : & mut MiriInterpCx < ' tcx > ,
52
+ arg3 : & OpTy < ' tcx > ,
53
+ use_realtime_clock : bool ,
54
+ use_absolute_time : bool ,
55
+ dest : & MPlaceTy < ' tcx > ,
56
+ ) -> InterpResult < ' tcx , Option < Option < ( TimeoutClock , TimeoutAnchor , Duration ) > > > {
57
+ let timeout = this. deref_pointer_as ( arg3, this. libc_ty_layout ( "timespec" ) ) ?;
58
+ interp_ok ( Some ( if this. ptr_is_null ( timeout. ptr ( ) ) ? {
59
+ None
60
+ } else {
61
+ let duration = match this. read_timespec ( & timeout) ? {
62
+ Some ( duration) => duration,
63
+ None => {
64
+ this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
65
+ this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
66
+ return interp_ok ( None ) ;
67
+ }
68
+ } ;
69
+ let timeout_clock = if use_realtime_clock {
70
+ this. check_no_isolation (
71
+ "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`" ,
72
+ ) ?;
73
+ TimeoutClock :: RealTime
74
+ } else {
75
+ TimeoutClock :: Monotonic
76
+ } ;
77
+ let timeout_anchor = if use_absolute_time {
78
+ // FUTEX_WAIT_BITSET uses an absolute timestamp.
79
+ TimeoutAnchor :: Absolute
80
+ } else {
81
+ // FUTEX_WAIT uses a relative timestamp.
82
+ TimeoutAnchor :: Relative
83
+ } ;
84
+ Some ( ( timeout_clock, timeout_anchor, duration) )
85
+ } ) )
86
+ }
87
+
46
88
// FUTEX_PRIVATE enables an optimization that stops it from working across processes.
47
89
// Miri doesn't support that anyway, so we ignore that flag.
48
90
match op & !futex_private {
@@ -54,7 +96,7 @@ pub fn futex<'tcx>(
54
96
// This is identical to FUTEX_WAIT, except:
55
97
// - The timeout is absolute rather than relative.
56
98
// - 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 || op == futex_lock_pi => {
99
+ op if op & !futex_realtime == futex_wait || op & !futex_realtime == futex_wait_bitset => {
58
100
let wait_bitset = op & !futex_realtime == futex_wait_bitset;
59
101
60
102
let bitset = if wait_bitset {
@@ -77,44 +119,25 @@ pub fn futex<'tcx>(
77
119
u32:: MAX
78
120
} ;
79
121
80
-
81
- let val = this. read_scalar ( & args[ 2 ] ) ?. to_u32 ( ) ?;
122
+ // We ensured at least 4 arguments above so these work.
123
+ let val = this. read_scalar ( & args[ 2 ] ) ?. to_i32 ( ) ?;
124
+ let Some ( timeout) = read_timeout (
125
+ this,
126
+ & args[ 3 ] ,
127
+ op & futex_realtime == futex_realtime,
128
+ wait_bitset,
129
+ dest,
130
+ ) ?
131
+ else {
132
+ return interp_ok ( ( ) ) ;
133
+ } ;
82
134
83
135
if bitset == 0 {
84
136
this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
85
137
this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
86
138
return interp_ok ( ( ) ) ;
87
139
}
88
140
89
- let timeout = this. deref_pointer_as ( & args[ 3 ] , this. libc_ty_layout ( "timespec" ) ) ?;
90
- let timeout = if this. ptr_is_null ( timeout. ptr ( ) ) ? {
91
- None
92
- } else {
93
- let duration = match this. read_timespec ( & timeout) ? {
94
- Some ( duration) => duration,
95
- None => {
96
- this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
97
- this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
98
- return interp_ok ( ( ) ) ;
99
- }
100
- } ;
101
- let timeout_clock = if op & futex_realtime == futex_realtime {
102
- this. check_no_isolation (
103
- "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`" ,
104
- ) ?;
105
- TimeoutClock :: RealTime
106
- } else {
107
- TimeoutClock :: Monotonic
108
- } ;
109
- let timeout_anchor = if wait_bitset {
110
- // FUTEX_WAIT_BITSET uses an absolute timestamp.
111
- TimeoutAnchor :: Absolute
112
- } else {
113
- // FUTEX_WAIT uses a relative timestamp.
114
- TimeoutAnchor :: Relative
115
- } ;
116
- Some ( ( timeout_clock, timeout_anchor, duration) )
117
- } ;
118
141
// There may be a concurrent thread changing the value of addr
119
142
// and then invoking the FUTEX_WAKE syscall. It is critical that the
120
143
// effects of this and the other thread are correctly observed,
@@ -158,16 +181,12 @@ pub fn futex<'tcx>(
158
181
//
159
182
// Thankfully, preemptions cannot happen inside a Miri shim, so we do not need to
160
183
// do anything special to guarantee fence-load-comparison atomicity.
161
- let futex_val = this. read_scalar_atomic ( & addr, AtomicReadOrd :: SeqCst ) ?. to_u32 ( ) ?;
162
- if if op == futex_lock_pi {
163
- futex_val != 0
164
- } else {
165
- futex_val == val
166
- } {
184
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
185
+ // Read an `i32` through the pointer, regardless of any wrapper types.
186
+ // It's not uncommon for `addr` to be passed as another type than `*mut i32`, such as `*const AtomicI32`.
187
+ let futex_val = this. read_scalar_atomic ( & addr, AtomicReadOrd :: Relaxed ) ?. to_i32 ( ) ?;
188
+ if val == futex_val {
167
189
// The value still matches, so we block the thread and make it wait for FUTEX_WAKE.
168
- if op == futex_lock_pi {
169
- this. write_scalar_atomic ( Scalar :: from_u32 ( val | futex_waiters) , & addr , AtomicWriteOrd :: SeqCst ) ?;
170
- }
171
190
this. futex_wait (
172
191
addr_usize,
173
192
bitset,
@@ -177,10 +196,6 @@ pub fn futex<'tcx>(
177
196
dest. clone ( ) ,
178
197
this. eval_libc ( "ETIMEDOUT" ) ,
179
198
) ;
180
- } else if op == futex_lock_pi {
181
- let tid = this. linux_gettid ( ) ?;
182
- this. write_scalar_atomic ( tid, & addr, AtomicWriteOrd :: SeqCst ) ?;
183
- this. write_scalar ( Scalar :: from_target_isize ( 0 , this) , dest) ?;
184
199
} else {
185
200
// The futex value doesn't match the expected value, so we return failure
186
201
// right away without sleeping: -1 and errno set to EAGAIN.
@@ -195,8 +210,15 @@ pub fn futex<'tcx>(
195
210
// Does not access the futex value at *addr.
196
211
// FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
197
212
// Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
198
- op if op == futex_wake || op == futex_wake_bitset || op == futex_unlock_pi => {
199
- let val = if op == futex_unlock_pi { 1 } else { this. read_scalar ( & args[ 2 ] ) ?. to_i32 ( ) ? } ;
213
+ op if op == futex_wake || op == futex_wake_bitset => {
214
+ if args. len ( ) < 3 {
215
+ throw_ub_format ! (
216
+ "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE`: got {}, expected at least 3" ,
217
+ args. len( )
218
+ ) ;
219
+ }
220
+
221
+ let val = this. read_scalar ( & args[ 2 ] ) ?. to_i32 ( ) ?;
200
222
201
223
let bitset = if op == futex_wake_bitset {
202
224
let [ _, _, _, timeout, uaddr2, bitset, ..] = args else {
@@ -219,11 +241,7 @@ pub fn futex<'tcx>(
219
241
// Together with the SeqCst fence in futex_wait, this makes sure that futex_wait
220
242
// will see the latest value on addr which could be changed by our caller
221
243
// before doing the syscall.
222
- if op == futex_unlock_pi {
223
- this. write_scalar_atomic ( Scalar :: from_u32 ( 0 ) , & addr, AtomicWriteOrd :: SeqCst ) ?;
224
- } else {
225
- this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
226
- }
244
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
227
245
let mut n = 0 ;
228
246
#[ allow( clippy:: arithmetic_side_effects) ]
229
247
for _ in 0 ..val {
@@ -233,7 +251,79 @@ pub fn futex<'tcx>(
233
251
break ;
234
252
}
235
253
}
236
- this. write_scalar ( Scalar :: from_target_isize ( if op != futex_unlock_pi { n } else { 0 } , this) , dest) ?;
254
+ this. write_scalar ( Scalar :: from_target_isize ( n, this) , dest) ?;
255
+ }
256
+ op if op == futex_lock_pi => {
257
+ if args. len ( ) < 4 {
258
+ throw_ub_format ! (
259
+ "incorrect number of arguments for `futex` syscall with `op=FUTEX_LOCK_PI`: got {}, expected at least 4" ,
260
+ args. len( )
261
+ ) ;
262
+ }
263
+
264
+ // FUTEX_LOCK_PI uses absolute CLOCK_REALTIME timestamp.
265
+ let Some ( timeout) = read_timeout ( this, & args[ 3 ] , true , true , dest) ? else {
266
+ return interp_ok ( ( ) ) ;
267
+ } ;
268
+
269
+ // The same as above. This makes modifications visible to us.
270
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
271
+
272
+ // For bitand working properly, we read it as a u32.
273
+ let futex_val = this. read_scalar_atomic ( & addr, AtomicReadOrd :: Relaxed ) ?. to_u32 ( ) ?;
274
+
275
+ if futex_val == 0 {
276
+ // 0 means unlocked - then lock it.
277
+ //
278
+ // The tid of the owner is store to *addr.
279
+ // N.B. it is not the same as posix thread id.
280
+ let tid = this. linux_gettid ( ) ?;
281
+ this. write_scalar_atomic ( tid, & addr, AtomicWriteOrd :: Relaxed ) ?;
282
+ } else {
283
+ // Other values mean locked.
284
+ //
285
+ // Mark the futex as contended.
286
+ this. write_scalar_atomic (
287
+ Scalar :: from_u32 ( futex_val | futex_waiters) ,
288
+ & addr,
289
+ AtomicWriteOrd :: Relaxed ,
290
+ ) ?;
291
+
292
+ // Put ourselves into the wait queue.
293
+ this. futex_wait (
294
+ addr_usize,
295
+ u32:: MAX ,
296
+ timeout,
297
+ Scalar :: from_target_isize ( 0 , this) , // retval_succ
298
+ Scalar :: from_target_isize ( -1 , this) , // retval_timeout
299
+ dest. clone ( ) ,
300
+ this. eval_libc ( "ETIMEDOUT" ) ,
301
+ ) ;
302
+ }
303
+
304
+ // This ensures all loads afterwards get updated value of *addr.
305
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
306
+
307
+ // FUTEX_LOCK_PI returns 0 on success.
308
+ this. write_scalar ( Scalar :: from_target_isize ( 0 , this) , dest) ?;
309
+ }
310
+ op if op == futex_unlock_pi => {
311
+ // This ensures all modifications happen before.
312
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
313
+
314
+ // Clear locked state.
315
+ this. write_scalar_atomic ( Scalar :: from_u32 ( 0 ) , & addr, AtomicWriteOrd :: Relaxed ) ?;
316
+
317
+ // This ensures all loads afterwards get updated value of *addr.
318
+ // There are no preemptions so no one can wake after we set the futex to unlocked
319
+ // and before we use futex_wake to wake one waiter.
320
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
321
+
322
+ // Unlocking wakes zero or one waiters.
323
+ let _ = this. futex_wake ( addr_usize, u32:: MAX ) ?;
324
+
325
+ // FUTEX_UNLOCK_PI returns 0 on success.
326
+ this. write_scalar ( Scalar :: from_target_isize ( 0 , this) , dest) ?;
237
327
}
238
328
op => throw_unsup_format ! ( "Miri does not support `futex` syscall with op={}" , op) ,
239
329
}
0 commit comments