about summary refs log tree commit diff
diff options
context:
space:
mode:
authortiif <pekyuan@gmail.com>2024-06-08 08:58:47 +0800
committertiif <pekyuan@gmail.com>2024-06-08 08:58:47 +0800
commita269cf5657262e8a1dcd1377b116db6223f1f54a (patch)
tree09e0f93b9087378dba8cb2127fc09bcdec922851
parent4c18f2a4c0066ec2de2b48c4451140e6c64c42f1 (diff)
downloadrust-a269cf5657262e8a1dcd1377b116db6223f1f54a.tar.gz
rust-a269cf5657262e8a1dcd1377b116db6223f1f54a.zip
Add eventfd shim
-rw-r--r--src/tools/miri/src/shims/unix/linux/eventfd.rs113
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs12
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr14
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs22
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr14
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs101
6 files changed, 257 insertions, 19 deletions
diff --git a/src/tools/miri/src/shims/unix/linux/eventfd.rs b/src/tools/miri/src/shims/unix/linux/eventfd.rs
index 777142b25c4..3080d5b8d07 100644
--- a/src/tools/miri/src/shims/unix/linux/eventfd.rs
+++ b/src/tools/miri/src/shims/unix/linux/eventfd.rs
@@ -1,14 +1,20 @@
 //! Linux `eventfd` implementation.
-//! Currently just a stub.
 use std::io;
+use std::io::{Error, ErrorKind};
 
 use rustc_target::abi::Endian;
 
 use crate::shims::unix::*;
-use crate::*;
+use crate::{concurrency::VClock, *};
 
 use self::shims::unix::fd::FileDescriptor;
 
+/// Minimum size of u8 array to hold u64 value.
+const U64_MIN_ARRAY_SIZE: usize = 8;
+
+/// Maximum value that the eventfd counter can hold.
+const MAX_COUNTER: u64 = u64::MAX - 1;
+
 /// A kind of file descriptor created by `eventfd`.
 /// The `Event` type isn't currently written to by `eventfd`.
 /// The interface is meant to keep track of objects associated
@@ -20,7 +26,9 @@ use self::shims::unix::fd::FileDescriptor;
 struct Event {
     /// 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.
-    val: u64,
+    counter: u64,
+    is_nonblock: bool,
+    clock: VClock,
 }
 
 impl FileDescription for Event {
@@ -35,6 +43,38 @@ impl FileDescription for Event {
         Ok(Ok(()))
     }
 
+    /// Read the counter in the buffer and return the counter if succeeded.
+    fn read<'tcx>(
+        &mut self,
+        _communicate_allowed: bool,
+        bytes: &mut [u8],
+        ecx: &mut MiriInterpCx<'tcx>,
+    ) -> InterpResult<'tcx, io::Result<usize>> {
+        // Check the size of slice, and return error only if the size of the slice < 8.
+        let Some(bytes) = bytes.first_chunk_mut::<U64_MIN_ARRAY_SIZE>() else {
+            return Ok(Err(Error::from(ErrorKind::InvalidInput)));
+        };
+        // Block when counter == 0.
+        if self.counter == 0 {
+            if self.is_nonblock {
+                return Ok(Err(Error::from(ErrorKind::WouldBlock)));
+            } else {
+                //FIXME: blocking is not supported
+                throw_unsup_format!("eventfd: blocking is unsupported");
+            }
+        } else {
+            // Prevent false alarm in data race detection when doing synchronisation via eventfd.
+            ecx.acquire_clock(&self.clock);
+            // Return the counter in the host endianness using the buffer provided by caller.
+            *bytes = match ecx.tcx.sess.target.endian {
+                Endian::Little => self.counter.to_le_bytes(),
+                Endian::Big => self.counter.to_be_bytes(),
+            };
+            self.counter = 0;
+            return Ok(Ok(U64_MIN_ARRAY_SIZE));
+        }
+    }
+
     /// A write call adds the 8-byte integer value supplied in
     /// its buffer (in native endianness) to the counter.  The maximum value that may be
     /// stored in the counter is the largest unsigned 64-bit value
@@ -53,16 +93,37 @@ impl FileDescription for Event {
         bytes: &[u8],
         ecx: &mut MiriInterpCx<'tcx>,
     ) -> InterpResult<'tcx, io::Result<usize>> {
-        let bytes: [u8; 8] = bytes.try_into().unwrap(); // FIXME fail gracefully when this has the wrong size
-        // Convert from target endianness to host endianness.
+        // Check the size of slice, and return error only if the size of the slice < 8.
+        let Some(bytes) = bytes.first_chunk::<U64_MIN_ARRAY_SIZE>() else {
+            return Ok(Err(Error::from(ErrorKind::InvalidInput)));
+        };
+        // Convert from bytes to int according to host endianness.
         let num = match ecx.tcx.sess.target.endian {
-            Endian::Little => u64::from_le_bytes(bytes),
-            Endian::Big => u64::from_be_bytes(bytes),
+            Endian::Little => u64::from_le_bytes(*bytes),
+            Endian::Big => u64::from_be_bytes(*bytes),
+        };
+        // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
+        if num == u64::MAX {
+            return Ok(Err(Error::from(ErrorKind::InvalidInput)));
+        }
+        // If the addition does not let the counter to exceed the maximum value, update the counter.
+        // Else, block.
+        match self.counter.checked_add(num) {
+            Some(new_count @ 0..=MAX_COUNTER) => {
+                // Prevent false alarm in data race detection when doing synchronisation via eventfd.
+                self.clock.join(&ecx.release_clock().unwrap());
+                self.counter = new_count;
+            }
+            None | Some(u64::MAX) => {
+                if self.is_nonblock {
+                    return Ok(Err(Error::from(ErrorKind::WouldBlock)));
+                } else {
+                    //FIXME: blocking is not supported
+                    throw_unsup_format!("eventfd: blocking is unsupported");
+                }
+            }
         };
-        // FIXME handle blocking when addition results in exceeding the max u64 value
-        // or fail with EAGAIN if the file descriptor is nonblocking.
-        self.val = self.val.checked_add(num).unwrap();
-        Ok(Ok(8))
+        Ok(Ok(U64_MIN_ARRAY_SIZE))
     }
 }
 
@@ -87,27 +148,41 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn eventfd(&mut self, val: &OpTy<'tcx>, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
+        // eventfd is Linux specific.
+        this.assert_target_os("linux", "eventfd");
+
         let val = this.read_scalar(val)?.to_u32()?;
-        let flags = this.read_scalar(flags)?.to_i32()?;
+        let mut flags = this.read_scalar(flags)?.to_i32()?;
 
         let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC");
         let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK");
         let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE");
 
-        if flags & (efd_cloexec | efd_nonblock | efd_semaphore) != flags {
-            throw_unsup_format!("eventfd: flag {flags:#x} is unsupported");
+        if flags & efd_semaphore == efd_semaphore {
+            throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
         }
+
+        let mut is_nonblock = false;
+        // Unload the flag that we support.
+        // After unloading, flags != 0 means other flags are used.
         if flags & efd_cloexec == efd_cloexec {
-            // cloexec does nothing as we don't support `exec`
+            flags &= !efd_cloexec;
         }
         if flags & efd_nonblock == efd_nonblock {
-            // FIXME remember the nonblock flag
+            flags &= !efd_nonblock;
+            is_nonblock = true;
         }
-        if flags & efd_semaphore == efd_semaphore {
-            throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
+        if flags != 0 {
+            let einval = this.eval_libc("EINVAL");
+            this.set_last_error(einval)?;
+            return Ok(Scalar::from_i32(-1));
         }
 
-        let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event { val: val.into() }));
+        let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event {
+            counter: val.into(),
+            is_nonblock,
+            clock: VClock::default(),
+        }));
         Ok(Scalar::from_i32(fd))
     }
 }
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs
new file mode 100644
index 00000000000..b24635f9340
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.rs
@@ -0,0 +1,12 @@
+//@ignore-target-windows: No eventfd on Windows
+//@ignore-target-apple: No eventfd in macos
+fn main() {
+    // eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0.
+    // This will pass when blocking is implemented.
+    let flags = libc::EFD_CLOEXEC;
+    let fd = unsafe { libc::eventfd(0, flags) };
+    let mut buf: [u8; 8] = [0; 8];
+    let _res: i32 = unsafe {
+        libc::read(fd, buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap() //~ERROR: blocking is unsupported
+    };
+}
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr
new file mode 100644
index 00000000000..fdd0b4272ca
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_read_block.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: eventfd: blocking is unsupported
+  --> $DIR/libc_eventfd_read_block.rs:LL:CC
+   |
+LL |         libc::read(fd, buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap()
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ eventfd: blocking is unsupported
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_eventfd_read_block.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs
new file mode 100644
index 00000000000..32ca4a919f7
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.rs
@@ -0,0 +1,22 @@
+//@ignore-target-windows: No eventfd on Windows
+//@ignore-target-apple: No eventfd in macos
+fn main() {
+    // eventfd write will block when EFD_NONBLOCK flag is clear
+    // and the addition caused counter to exceed u64::MAX - 1.
+    // This will pass when blocking is implemented.
+    let flags = libc::EFD_CLOEXEC;
+    let fd = unsafe { libc::eventfd(0, flags) };
+    // Write u64 - 1.
+    let mut sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes();
+    let res: i64 = unsafe {
+        libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap()
+    };
+    assert_eq!(res, 8);
+
+    // Write 1.
+    sized_8_data = 1_u64.to_ne_bytes();
+    // Write 1 to the counter.
+    let _res: i64 = unsafe {
+        libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() //~ERROR: blocking is unsupported
+    };
+}
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr
new file mode 100644
index 00000000000..f12c0ddfb17
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/libc_eventfd_write_block.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: eventfd: blocking is unsupported
+  --> $DIR/libc_eventfd_write_block.rs:LL:CC
+   |
+LL |         libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap()
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ eventfd: blocking is unsupported
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/libc_eventfd_write_block.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs
new file mode 100644
index 00000000000..6af195316e8
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs
@@ -0,0 +1,101 @@
+//@ignore-target-windows: No eventfd in windows
+//@ignore-target-apple: No eventfd in macos
+// test_race depends on a deterministic schedule.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::thread;
+
+fn main() {
+    test_read_write();
+    test_race();
+}
+
+fn read_bytes<const N: usize>(fd: i32, buf: &mut [u8; N]) -> i32 {
+    let res: i32 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), N).try_into().unwrap() };
+    return res;
+}
+
+fn write_bytes<const N: usize>(fd: i32, data: [u8; N]) -> i32 {
+    let res: i32 =
+        unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, N).try_into().unwrap() };
+    return res;
+}
+
+fn test_read_write() {
+    let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
+    let fd = unsafe { libc::eventfd(0, flags) };
+    let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
+    // Write 1 to the counter.
+    let res = write_bytes(fd, sized_8_data);
+    assert_eq!(res, 8);
+
+    // Read 1 from the counter.
+    let mut buf: [u8; 8] = [0; 8];
+    let res = read_bytes(fd, &mut buf);
+    // Read returns number of bytes has been read, which is always 8.
+    assert_eq!(res, 8);
+    // Check the value of counter read.
+    let counter = u64::from_ne_bytes(buf);
+    assert_eq!(counter, 1);
+
+    // After read, the counter is currently 0, read counter 0 should fail with return
+    // value -1.
+    let mut buf: [u8; 8] = [0; 8];
+    let res = read_bytes(fd, &mut buf);
+    assert_eq!(res, -1);
+
+    // Write with supplied buffer that > 8 bytes should be allowed.
+    let sized_9_data: [u8; 9];
+    if cfg!(target_endian = "big") {
+        // Adjust the data based on the endianness of host system.
+        sized_9_data = [0, 0, 0, 0, 0, 0, 0, 1, 0];
+    } else {
+        sized_9_data = [1, 0, 0, 0, 0, 0, 0, 0, 0];
+    }
+    let res = write_bytes(fd, sized_9_data);
+    assert_eq!(res, 8);
+
+    // Read with supplied buffer that < 8 bytes should fail with return
+    // value -1.
+    let mut buf: [u8; 7] = [1; 7];
+    let res = read_bytes(fd, &mut buf);
+    assert_eq!(res, -1);
+
+    // Write with supplied buffer that < 8 bytes should fail with return
+    // value -1.
+    let size_7_data: [u8; 7] = [1; 7];
+    let res = write_bytes(fd, size_7_data);
+    assert_eq!(res, -1);
+
+    // Read with supplied buffer > 8 bytes should be allowed.
+    let mut buf: [u8; 9] = [1; 9];
+    let res = read_bytes(fd, &mut buf);
+    assert_eq!(res, 8);
+
+    // Write u64::MAX should fail.
+    let u64_max_bytes: [u8; 8] = [255; 8];
+    let res = write_bytes(fd, u64_max_bytes);
+    assert_eq!(res, -1);
+}
+
+fn test_race() {
+    static mut VAL: u8 = 0;
+    let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
+    let fd = unsafe { libc::eventfd(0, flags) };
+    let thread1 = thread::spawn(move || {
+        let mut buf: [u8; 8] = [0; 8];
+        let res = read_bytes(fd, &mut buf);
+        // read returns number of bytes has been read, which is always 8.
+        assert_eq!(res, 8);
+        let counter = u64::from_ne_bytes(buf);
+        assert_eq!(counter, 1);
+        unsafe { assert_eq!(VAL, 1) };
+    });
+    unsafe { VAL = 1 };
+    let data: [u8; 8] = 1_u64.to_ne_bytes();
+    let res = write_bytes(fd, data);
+    // write returns number of bytes written, which is always 8.
+    assert_eq!(res, 8);
+    thread::yield_now();
+    thread1.join().unwrap();
+}