1+ use std:: time:: Duration ;
2+
3+ use crate :: shims:: unix:: env:: EvalContextExt ;
14use crate :: * ;
25
36/// Implementation of the SYS_futex syscall.
@@ -15,19 +18,18 @@ pub fn futex<'tcx>(
1518 // may or may not be left out from the `syscall()` call.
1619 // Therefore we don't use `check_arg_count` here, but only check for the
1720 // number of arguments to fall within a range.
18- let [ addr, op, val , ..] = args else {
21+ let [ addr, op, ..] = args else {
1922 throw_ub_format ! (
2023 "incorrect number of arguments for `futex` syscall: got {}, expected at least 3" ,
2124 args. len( )
2225 ) ;
2326 } ;
2427
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).
2730 // We checked above that these definitely exist.
2831 let addr = this. read_pointer ( addr) ?;
2932 let op = this. read_scalar ( op) ?. to_i32 ( ) ?;
30- let val = this. read_scalar ( val) ?. to_i32 ( ) ?;
3133
3234 // This is a vararg function so we have to bring our own type for this pointer.
3335 let addr = this. ptr_to_mplace ( addr, this. machine . layouts . i32 ) ;
@@ -39,6 +41,49 @@ pub fn futex<'tcx>(
3941 let futex_wake = this. eval_libc_i32 ( "FUTEX_WAKE" ) ;
4042 let futex_wake_bitset = this. eval_libc_i32 ( "FUTEX_WAKE_BITSET" ) ;
4143 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+
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+ }
4287
4388 // FUTEX_PRIVATE enables an optimization that stops it from working across processes.
4489 // Miri doesn't support that anyway, so we ignore that flag.
@@ -74,41 +119,25 @@ pub fn futex<'tcx>(
74119 u32:: MAX
75120 } ;
76121
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+ } ;
134+
77135 if bitset == 0 {
78136 this. set_last_error ( LibcError ( "EINVAL" ) ) ?;
79137 this. write_scalar ( Scalar :: from_target_isize ( -1 , this) , dest) ?;
80138 return interp_ok ( ( ) ) ;
81139 }
82140
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- } ;
112141 // There may be a concurrent thread changing the value of addr
113142 // and then invoking the FUTEX_WAKE syscall. It is critical that the
114143 // effects of this and the other thread are correctly observed,
@@ -182,6 +211,15 @@ pub fn futex<'tcx>(
182211 // FUTEX_WAKE_BITSET: (int *addr, int op = FUTEX_WAKE, int val, const timespect *_unused, int *_unused, unsigned int bitset)
183212 // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
184213 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 ( ) ?;
222+
185223 let bitset = if op == futex_wake_bitset {
186224 let [ _, _, _, timeout, uaddr2, bitset, ..] = args else {
187225 throw_ub_format ! (
@@ -215,6 +253,78 @@ pub fn futex<'tcx>(
215253 }
216254 this. write_scalar ( Scalar :: from_target_isize ( n, this) , dest) ?;
217255 }
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) ?;
327+ }
218328 op => throw_unsup_format ! ( "Miri does not support `futex` syscall with op={}" , op) ,
219329 }
220330
0 commit comments