From e87acf1a1541bbc800bf83e0d684a99b6c348a74 Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Sat, 17 Dec 2022 13:41:30 -0700 Subject: [PATCH] busy waiting implementation for sleep --- src/shims/unix/linux/fd.rs | 58 ++++++++++++++++++++++++- src/shims/unix/linux/fd/event.rs | 35 ++++++++++++++- src/shims/unix/linux/foreign_items.rs | 6 +++ tests/pass-dep/tokio/sleep.rs | 14 ++++++ tests/pass-dep/{ => tokio}/tokio_mvp.rs | 2 +- 5 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 tests/pass-dep/tokio/sleep.rs rename tests/pass-dep/{ => tokio}/tokio_mvp.rs (88%) diff --git a/src/shims/unix/linux/fd.rs b/src/shims/unix/linux/fd.rs index 212b793634..fd4927fa10 100644 --- a/src/shims/unix/linux/fd.rs +++ b/src/shims/unix/linux/fd.rs @@ -7,6 +7,8 @@ use socketpair::SocketPair; use shims::unix::fs::EvalContextExt as _; +use std::cell::Cell; + pub mod epoll; pub mod event; pub mod socketpair; @@ -101,6 +103,60 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } + /// The `epoll_wait()` system call waits for events on the `Epoll` + /// instance referred to by the file descriptor `epfd`. The buffer + /// pointed to by `events` is used to return information from the ready + /// list about file descriptors in the interest list that have some + /// events available. Up to `maxevents` are returned by `epoll_wait()`. + /// The `maxevents` argument must be greater than zero. + + /// The `timeout` argument specifies the number of milliseconds that + /// `epoll_wait()` will block. Time is measured against the + /// CLOCK_MONOTONIC clock. + + /// A call to `epoll_wait()` will block until either: + /// • a file descriptor delivers an event; + /// • the call is interrupted by a signal handler; or + /// • the timeout expires. + + /// Note that the timeout interval will be rounded up to the system + /// clock granularity, and kernel scheduling delays mean that the + /// blocking interval may overrun by a small amount. Specifying a + /// timeout of -1 causes `epoll_wait()` to block indefinitely, while + /// specifying a timeout equal to zero cause `epoll_wait()` to return + /// immediately, even if no events are available. + /// + /// On success, `epoll_wait()` returns the number of file descriptors + /// ready for the requested I/O, or zero if no file descriptor became + /// ready during the requested timeout milliseconds. On failure, + /// `epoll_wait()` returns -1 and errno is set to indicate the error. + /// + /// + fn epoll_wait( + &mut self, + epfd: &OpTy<'tcx, Provenance>, + events: &OpTy<'tcx, Provenance>, + maxevents: &OpTy<'tcx, Provenance>, + timeout: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let epfd = this.read_scalar(epfd)?.to_i32()?; + let _events = this.read_scalar(events)?.to_pointer(this)?; + let _maxevents = this.read_scalar(maxevents)?.to_i32()?; + let _timeout = this.read_scalar(timeout)?.to_i32()?; + + let numevents = 0; + if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { + let _epfd = epfd.as_epoll_handle()?; + + // FIXME return number of events ready when scheme for marking events ready exists + Ok(Scalar::from_i32(numevents)) + } else { + Ok(Scalar::from_i32(this.handle_not_found()?)) + } + } + /// This function creates an `Event` that is used as an event wait/notify mechanism by /// user-space applications, and by the kernel to notify user-space applications of events. /// The `Event` contains an `u64` counter maintained by the kernel. The counter is initialized @@ -142,7 +198,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } let fh = &mut this.machine.file_handler; - let fd = fh.insert_fd(Box::new(Event { val })); + let fd = fh.insert_fd(Box::new(Event { val: Cell::new(val.into()) })); Ok(Scalar::from_i32(fd)) } diff --git a/src/shims/unix/linux/fd/event.rs b/src/shims/unix/linux/fd/event.rs index 239eb462a1..b28a6e0c56 100644 --- a/src/shims/unix/linux/fd/event.rs +++ b/src/shims/unix/linux/fd/event.rs @@ -2,6 +2,7 @@ use crate::shims::unix::fs::FileDescriptor; use rustc_const_eval::interpret::InterpResult; +use std::cell::Cell; use std::io; /// A kind of file descriptor created by `eventfd`. @@ -13,7 +14,9 @@ use std::io; /// #[derive(Debug)] pub struct Event { - pub val: u32, + /// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the + /// kernel. This counter is initialized with the value specified in the argument initval. + pub val: Cell, } impl FileDescriptor for Event { @@ -22,7 +25,7 @@ impl FileDescriptor for Event { } fn dup(&mut self) -> io::Result> { - Ok(Box::new(Event { val: self.val })) + Ok(Box::new(Event { val: self.val.clone() })) } fn is_tty(&self) -> bool { @@ -35,4 +38,32 @@ impl FileDescriptor for Event { ) -> InterpResult<'tcx, io::Result> { Ok(Ok(0)) } + + /// A write call adds the 8-byte integer value supplied in + /// its buffer to the counter. The maximum value that may be + /// stored in the counter is the largest unsigned 64-bit value + /// minus 1 (i.e., 0xfffffffffffffffe). If the addition would + /// cause the counter's value to exceed the maximum, then the + /// write either blocks until a read is performed on the + /// file descriptor, or fails with the error EAGAIN if the + /// file descriptor has been made nonblocking. + + /// A write fails with the error EINVAL if the size of the + /// supplied buffer is less than 8 bytes, or if an attempt is + /// made to write the value 0xffffffffffffffff. + /// + /// FIXME: use endianness + fn write<'tcx>( + &self, + _communicate_allowed: bool, + bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + let v1 = self.val.get(); + // FIXME handle blocking when addition results in exceeding the max u64 value + // or fail with EAGAIN if the file descriptor is nonblocking. + let v2 = v1.checked_add(u64::from_be_bytes(bytes.try_into().unwrap())).unwrap(); + self.val.set(v2); + assert_eq!(8, bytes.len()); + Ok(Ok(8)) + } } diff --git a/src/shims/unix/linux/foreign_items.rs b/src/shims/unix/linux/foreign_items.rs index 82cb21c124..56d8d42821 100644 --- a/src/shims/unix/linux/foreign_items.rs +++ b/src/shims/unix/linux/foreign_items.rs @@ -55,6 +55,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } + "epoll_wait" => { + let [epfd, events, maxevents, timeout] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.epoll_wait(epfd, events, maxevents, timeout)?; + this.write_scalar(result, dest)?; + } "eventfd" => { let [val, flag] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; diff --git a/tests/pass-dep/tokio/sleep.rs b/tests/pass-dep/tokio/sleep.rs new file mode 100644 index 0000000000..1341484dda --- /dev/null +++ b/tests/pass-dep/tokio/sleep.rs @@ -0,0 +1,14 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance -Zmiri-backtrace=full +//@only-target-x86_64-unknown-linux: support for tokio only on linux and x86 + +use tokio::time::{sleep, Duration, Instant}; + +#[tokio::main] +async fn main() { + let start = Instant::now(); + sleep(Duration::from_secs(1)).await; + // It takes 96 millisecond to sleep for 1 millisecond + // It takes 1025 millisecond to sleep for 1 second + let time_elapsed = &start.elapsed().as_millis(); + assert!(time_elapsed > &1000, "{}", time_elapsed); +} diff --git a/tests/pass-dep/tokio_mvp.rs b/tests/pass-dep/tokio/tokio_mvp.rs similarity index 88% rename from tests/pass-dep/tokio_mvp.rs rename to tests/pass-dep/tokio/tokio_mvp.rs index 642168253c..0bca7cc069 100644 --- a/tests/pass-dep/tokio_mvp.rs +++ b/tests/pass-dep/tokio/tokio_mvp.rs @@ -1,5 +1,5 @@ // Need to disable preemption to stay on the supported MVP codepath in mio. -//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance -Zmiri-preemption-rate=0 +//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance //@only-target-x86_64-unknown-linux: support for tokio exists only on linux and x86 #[tokio::main]