1
+ use std:: time:: Duration ;
2
+
3
+ use crate :: shims:: unix:: env:: EvalContextExt ;
1
4
use crate :: * ;
2
5
3
6
/// Implementation of the SYS_futex syscall.
@@ -15,19 +18,18 @@ pub fn futex<'tcx>(
15
18
// may or may not be left out from the `syscall()` call.
16
19
// Therefore we don't use `check_arg_count` here, but only check for the
17
20
// number of arguments to fall within a range.
18
- let [ addr, op, val , ..] = args else {
21
+ let [ addr, op, ..] = args else {
19
22
throw_ub_format ! (
20
23
"incorrect number of arguments for `futex` syscall: got {}, expected at least 3" ,
21
24
args. len( )
22
25
) ;
23
26
} ;
24
27
25
- // The first three arguments (after the syscall number itself) are the same to all futex operations:
26
- // (int *addr, int op, int val ).
28
+ // The first two arguments (after the syscall number itself) are the same to all futex operations:
29
+ // (int *addr, int op).
27
30
// We checked above that these definitely exist.
28
31
let addr = this. read_pointer ( addr) ?;
29
32
let op = this. read_scalar ( op) ?. to_i32 ( ) ?;
30
- let val = this. read_scalar ( val) ?. to_i32 ( ) ?;
31
33
32
34
// This is a vararg function so we have to bring our own type for this pointer.
33
35
let addr = this. ptr_to_mplace ( addr, this. machine . layouts . i32 ) ;
@@ -39,6 +41,50 @@ pub fn futex<'tcx>(
39
41
let futex_wake = this. eval_libc_i32 ( "FUTEX_WAKE" ) ;
40
42
let futex_wake_bitset = this. eval_libc_i32 ( "FUTEX_WAKE_BITSET" ) ;
41
43
let futex_realtime = this. eval_libc_i32 ( "FUTEX_CLOCK_REALTIME" ) ;
44
+ let futex_lock_pi = this. eval_libc_i32 ( "FUTEX_LOCK_PI" ) ;
45
+ let futex_unlock_pi = this. eval_libc_i32 ( "FUTEX_UNLOCK_PI" ) ;
46
+ let futex_waiters = this. eval_libc_u32 ( "FUTEX_WAITERS" ) ;
47
+ let futex_tid_mask = this. eval_libc_u32 ( "FUTEX_TID_MASK" ) ;
48
+
49
+ // Ok(None) for EINVAL set, Ok(Some(None)) for no timeout (infinity), Ok(Some(Some(...))) for a timeout.
50
+ // Forgive me, I don't want to create an enum for this return value.
51
+ fn read_timeout < ' tcx > (
52
+ this : & mut MiriInterpCx < ' tcx > ,
53
+ arg3 : & OpTy < ' tcx > ,
54
+ use_realtime_clock : bool ,
55
+ use_absolute_time : bool ,
56
+ dest : & MPlaceTy < ' tcx > ,
57
+ ) -> InterpResult < ' tcx , Option < Option < ( TimeoutClock , TimeoutAnchor , Duration ) > > > {
58
+ let timeout = this. deref_pointer_as ( arg3, this. libc_ty_layout ( "timespec" ) ) ?;
59
+ interp_ok ( Some ( if this. ptr_is_null ( timeout. ptr ( ) ) ? {
60
+ None
61
+ } else {
62
+ let duration = match this. read_timespec ( & timeout) ? {
63
+ Some ( duration) => duration,
64
+ None => {
65
+ this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
66
+ this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
67
+ return interp_ok ( None ) ;
68
+ }
69
+ } ;
70
+ let timeout_clock = if use_realtime_clock {
71
+ this. check_no_isolation (
72
+ "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`" ,
73
+ ) ?;
74
+ TimeoutClock :: RealTime
75
+ } else {
76
+ TimeoutClock :: Monotonic
77
+ } ;
78
+ let timeout_anchor = if use_absolute_time {
79
+ // FUTEX_WAIT_BITSET uses an absolute timestamp.
80
+ TimeoutAnchor :: Absolute
81
+ } else {
82
+ // FUTEX_WAIT uses a relative timestamp.
83
+ TimeoutAnchor :: Relative
84
+ } ;
85
+ Some ( ( timeout_clock, timeout_anchor, duration) )
86
+ } ) )
87
+ }
42
88
43
89
// FUTEX_PRIVATE enables an optimization that stops it from working across processes.
44
90
// Miri doesn't support that anyway, so we ignore that flag.
@@ -74,41 +120,25 @@ pub fn futex<'tcx>(
74
120
u32:: MAX
75
121
} ;
76
122
123
+ // We ensured at least 4 arguments above so these work.
124
+ let val = this. read_scalar ( & args[ 2 ] ) ?. to_i32 ( ) ?;
125
+ let Some ( timeout) = read_timeout (
126
+ this,
127
+ & args[ 3 ] ,
128
+ op & futex_realtime == futex_realtime,
129
+ wait_bitset,
130
+ dest,
131
+ ) ?
132
+ else {
133
+ return interp_ok ( ( ) ) ;
134
+ } ;
135
+
77
136
if bitset == 0 {
78
137
this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
79
138
this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
80
139
return interp_ok ( ( ) ) ;
81
140
}
82
141
83
- let timeout = this. deref_pointer_as ( & args[ 3 ] , this. libc_ty_layout ( "timespec" ) ) ?;
84
- let timeout = if this. ptr_is_null ( timeout. ptr ( ) ) ? {
85
- None
86
- } else {
87
- let duration = match this. read_timespec ( & timeout) ? {
88
- Some ( duration) => duration,
89
- None => {
90
- this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
91
- this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
92
- return interp_ok ( ( ) ) ;
93
- }
94
- } ;
95
- let timeout_clock = if op & futex_realtime == futex_realtime {
96
- this. check_no_isolation (
97
- "`futex` syscall with `op=FUTEX_WAIT` and non-null timeout with `FUTEX_CLOCK_REALTIME`" ,
98
- ) ?;
99
- TimeoutClock :: RealTime
100
- } else {
101
- TimeoutClock :: Monotonic
102
- } ;
103
- let timeout_anchor = if wait_bitset {
104
- // FUTEX_WAIT_BITSET uses an absolute timestamp.
105
- TimeoutAnchor :: Absolute
106
- } else {
107
- // FUTEX_WAIT uses a relative timestamp.
108
- TimeoutAnchor :: Relative
109
- } ;
110
- Some ( ( timeout_clock, timeout_anchor, duration) )
111
- } ;
112
142
// There may be a concurrent thread changing the value of addr
113
143
// and then invoking the FUTEX_WAKE syscall. It is critical that the
114
144
// effects of this and the other thread are correctly observed,
@@ -164,6 +194,7 @@ pub fn futex<'tcx>(
164
194
timeout,
165
195
Scalar :: from_target_isize ( 0 , this) , // retval_succ
166
196
Scalar :: from_target_isize ( -1 , this) , // retval_timeout
197
+ None ,
167
198
dest. clone ( ) ,
168
199
this. eval_libc ( "ETIMEDOUT" ) ,
169
200
) ;
@@ -182,6 +213,15 @@ pub fn futex<'tcx>(
182
213
// FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
183
214
// Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
184
215
op if op == futex_wake || op == futex_wake_bitset => {
216
+ if args. len ( ) < 3 {
217
+ throw_ub_format ! (
218
+ "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE`: got {}, expected at least 3" ,
219
+ args. len( )
220
+ ) ;
221
+ }
222
+
223
+ let val = this. read_scalar ( & args[ 2 ] ) ?. to_i32 ( ) ?;
224
+
185
225
let bitset = if op == futex_wake_bitset {
186
226
let [ _, _, _, timeout, uaddr2, bitset, ..] = args else {
187
227
throw_ub_format ! (
@@ -215,6 +255,114 @@ pub fn futex<'tcx>(
215
255
}
216
256
this. write_scalar ( Scalar :: from_target_isize ( n, this) , dest) ?;
217
257
}
258
+ op if op == futex_lock_pi => {
259
+ if args. len ( ) < 4 {
260
+ throw_ub_format ! (
261
+ "incorrect number of arguments for `futex` syscall with `op=FUTEX_LOCK_PI`: got {}, expected at least 4" ,
262
+ args. len( )
263
+ ) ;
264
+ }
265
+
266
+ // FUTEX_LOCK_PI uses absolute CLOCK_REALTIME timestamp.
267
+ let Some ( timeout) = read_timeout ( this, & args[ 3 ] , true , true , dest) ? else {
268
+ return interp_ok ( ( ) ) ;
269
+ } ;
270
+
271
+ // The tid of the owner is store to *addr.
272
+ // N.B. it is not the same as posix thread id.
273
+ let tid = this. linux_gettid ( ) ?. to_u32 ( ) ?;
274
+
275
+ // The same as above. This makes modifications visible to us.
276
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
277
+
278
+ // For bitand working properly, we read it as a u32.
279
+ let futex_val = this. read_scalar_atomic ( & addr, AtomicReadOrd :: Relaxed ) ?. to_u32 ( ) ?;
280
+
281
+ if futex_val == 0 {
282
+ // 0 means unlocked - then lock it.
283
+ this. write_scalar_atomic ( Scalar :: from_u32 ( tid) , & addr, AtomicWriteOrd :: Relaxed ) ?;
284
+
285
+ // This ensures all loads afterwards get updated value of *addr.
286
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
287
+
288
+ // FUTEX_LOCK_PI returns 0 on success.
289
+ this. write_scalar ( Scalar :: from_target_isize ( 0 , this) , dest) ?;
290
+ } else if ( futex_val & futex_tid_mask) == tid {
291
+ // Locked by self.
292
+ this. set_last_error ( LibcError ( "EDEADLK" ) ) ?;
293
+ this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
294
+ } else {
295
+ // Other values mean locked.
296
+ // Mark the futex as contended.
297
+ this. write_scalar_atomic (
298
+ Scalar :: from_u32 ( futex_val | futex_waiters) ,
299
+ & addr,
300
+ AtomicWriteOrd :: Relaxed ,
301
+ ) ?;
302
+
303
+ // This ensures all loads afterwards get updated value of *addr.
304
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
305
+
306
+ // Put ourselves into the wait queue.
307
+ this. futex_wait (
308
+ addr_usize,
309
+ u32:: MAX ,
310
+ timeout,
311
+ Scalar :: from_target_isize ( 0 , this) , // retval_succ
312
+ Scalar :: from_target_isize ( -1 , this) , // retval_timeout
313
+ Some ( tid) ,
314
+ dest. clone ( ) ,
315
+ this. eval_libc ( "ETIMEDOUT" ) ,
316
+ ) ;
317
+ }
318
+ }
319
+ op if op == futex_unlock_pi => {
320
+ let tid = this. linux_gettid ( ) ?. to_u32 ( ) ?;
321
+
322
+ // This ensures all modifications happen before.
323
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
324
+
325
+ // Check we owns lock.
326
+ let futex_val = this. read_scalar_atomic ( & addr, AtomicReadOrd :: Relaxed ) ?. to_u32 ( ) ?;
327
+ if ( futex_val & futex_tid_mask) != tid {
328
+ this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
329
+ this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
330
+ return interp_ok ( ( ) ) ;
331
+ }
332
+
333
+ let waiter_count = this. futex_waiter_count ( addr_usize) ;
334
+ let new_futex_val = if waiter_count == 0 {
335
+ 0
336
+ } else {
337
+ let Some ( new_tid) = this. futex_top_waiter_extra ( addr_usize) else {
338
+ // The waiter is not waiting using FUTEX_LOCK_PI.
339
+ this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
340
+ this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
341
+ return interp_ok ( ( ) ) ;
342
+ } ;
343
+
344
+ if waiter_count > 1 {
345
+ // Still contended after we unlocks.
346
+ new_tid | futex_waiters
347
+ } else {
348
+ new_tid
349
+ }
350
+ } ;
351
+
352
+ // Update locked state.
353
+ this. write_scalar_atomic ( Scalar :: from_u32 ( new_futex_val) , & addr, AtomicWriteOrd :: Relaxed ) ?;
354
+
355
+ // This ensures all loads afterwards get updated value of *addr.
356
+ // There are no preemptions so no one can wake after we set the futex to unlocked
357
+ // and before we use futex_wake to wake one waiter.
358
+ this. atomic_fence ( AtomicFenceOrd :: SeqCst ) ?;
359
+
360
+ // Unlocking wakes zero or one waiters.
361
+ let _ = this. futex_wake ( addr_usize, u32:: MAX ) ?;
362
+
363
+ // FUTEX_UNLOCK_PI returns 0 on success.
364
+ this. write_scalar ( Scalar :: from_target_isize ( 0 , this) , dest) ?;
365
+ }
218
366
op => throw_unsup_format ! ( "Miri does not support `futex` syscall with op={}" , op) ,
219
367
}
220
368
0 commit comments