diff --git a/src/shims/posix/linux/foreign_items.rs b/src/shims/posix/linux/foreign_items.rs index 357c55c926..8434d7bfa8 100644 --- a/src/shims/posix/linux/foreign_items.rs +++ b/src/shims/posix/linux/foreign_items.rs @@ -1,4 +1,5 @@ use rustc_middle::mir; +use rustc_target::abi::{Align, Size}; use crate::*; use crate::helpers::check_arg_count; @@ -120,6 +121,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx .eval_libc("SYS_statx")? .to_machine_usize(this)?; + let sys_futex = this + .eval_libc("SYS_futex")? + .to_machine_usize(this)?; + if args.is_empty() { throw_ub_format!("incorrect number of arguments for syscall: got 0, expected at least 1"); } @@ -139,6 +144,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(Scalar::from_machine_isize(result.into(), this), dest)?; } + // `futex` is used by some synchonization primitives. + id if id == sys_futex => { + futex(this, args, dest)?; + } id => throw_unsup_format!("miri does not support syscall ID {}", id), } } @@ -192,3 +201,61 @@ fn getrandom<'tcx>( this.write_scalar(Scalar::from_machine_usize(len, this), dest)?; Ok(()) } + +fn futex<'tcx>( + this: &mut MiriEvalContext<'_, 'tcx>, + args: &[OpTy<'tcx, Tag>], + dest: PlaceTy<'tcx, Tag>, +) -> InterpResult<'tcx> { + if args.len() < 4 { + throw_ub_format!("incorrect number of arguments for futex syscall: got {}, expected at least 4", args.len()); + } + let addr = this.read_scalar(args[1])?.check_init()?; + let op = this.read_scalar(args[2])?.to_i32()?; + let val = this.read_scalar(args[3])?.to_i32()?; + + this.memory.check_ptr_access(addr, Size::from_bytes(4), Align::from_bytes(4).unwrap())?; + + let addr = addr.assert_ptr(); + + let thread = this.get_active_thread(); + + let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG")?; + let futex_wait = this.eval_libc_i32("FUTEX_WAIT")?; + let futex_wake = this.eval_libc_i32("FUTEX_WAKE")?; + + match op & !futex_private { + op if op == futex_wait => { + if args.len() < 5 { + throw_ub_format!("incorrect number of arguments for FUTEX_WAIT syscall: got {}, expected at least 5", args.len()); + } + let timeout = this.read_scalar(args[4])?.check_init()?; + if !this.is_null(timeout)? { + throw_ub_format!("miri does not support timeouts for futex operations"); + } + let futex_val = this.read_scalar_at_offset(args[1], 0, this.machine.layouts.i32)?.to_i32()?; + if val == futex_val { + this.block_thread(thread); + this.futex_wait(addr, thread); + } else { + let eagain = this.eval_libc("EAGAIN")?; + this.set_last_error(eagain)?; + } + } + op if op == futex_wake => { + let mut n = 0; + for _ in 0..val { + if let Some(thread) = this.futex_wake(addr) { + this.unblock_thread(thread); + n += 1; + } else { + break; + } + } + this.write_scalar(Scalar::from_i32(n), dest)?; + } + op => throw_unsup_format!("miri does not support SYS_futex operation {}", op), + } + + Ok(()) +} diff --git a/src/sync.rs b/src/sync.rs index 7e3c27b386..d5594fb9ec 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -96,12 +96,26 @@ struct Condvar { waiters: VecDeque, } +/// The futex state. +#[derive(Default, Debug)] +struct Futex { + waiters: VecDeque, +} + +/// A thread waiting on a futex. +#[derive(Debug)] +struct FutexWaiter { + /// The thread that is waiting on this futex. + thread: ThreadId, +} + /// The state of all synchronization variables. #[derive(Default, Debug)] pub(super) struct SynchronizationState { mutexes: IndexVec, rwlocks: IndexVec, condvars: IndexVec, + futexes: HashMap, Futex>, } // Private extension trait for local helper methods @@ -403,4 +417,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let this = self.eval_context_mut(); this.machine.threads.sync.condvars[id].waiters.retain(|waiter| waiter.thread != thread); } + + fn futex_wait(&mut self, addr: Pointer, thread: ThreadId) { + let this = self.eval_context_mut(); + let waiters = &mut this.machine.threads.sync.futexes.entry(addr).or_default().waiters; + assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting"); + waiters.push_back(FutexWaiter { thread }); + } + + fn futex_wake(&mut self, addr: Pointer) -> Option { + let this = self.eval_context_mut(); + let waiters = &mut this.machine.threads.sync.futexes.get_mut(&addr)?.waiters; + waiters.pop_front().map(|waiter| waiter.thread) + } }