Rollup merge of #131286 - RalfJung:miri-sync, r=RalfJung

Miri subtree update

r? `@ghost`
This commit is contained in:
Matthias Krüger 2024-10-05 13:15:59 +02:00 committed by GitHub
commit 72acacf60f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 1293 additions and 402 deletions

View File

@ -668,7 +668,6 @@ pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: Runner
RunnerPhase::Rustdoc => { RunnerPhase::Rustdoc => {
cmd.stdin(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped());
// the warning is wrong, we have a `wait` inside the `scope` closure. // the warning is wrong, we have a `wait` inside the `scope` closure.
#[expect(clippy::zombie_processes)]
let mut child = cmd.spawn().expect("failed to spawn process"); let mut child = cmd.spawn().expect("failed to spawn process");
let child_stdin = child.stdin.take().unwrap(); let child_stdin = child.stdin.take().unwrap();
// Write stdin in a background thread, as it may block. // Write stdin in a background thread, as it may block.

View File

@ -1 +1 @@
76ed7a1fa40c3f54d3fd3f834e12bf9c932d0146 7067e4aee45c18cfa1c6af3bf79bd097684fb294

View File

@ -859,14 +859,15 @@ impl Tree {
) -> Option<UniIndex> { ) -> Option<UniIndex> {
let node = self.nodes.get(idx).unwrap(); let node = self.nodes.get(idx).unwrap();
let [child_idx] = node.children[..] else { return None };
// We never want to replace the root node, as it is also kept in `root_ptr_tags`. // We never want to replace the root node, as it is also kept in `root_ptr_tags`.
if node.children.len() != 1 || live.contains(&node.tag) || node.parent.is_none() { if live.contains(&node.tag) || node.parent.is_none() {
return None; return None;
} }
// Since protected nodes are never GC'd (see `borrow_tracker::FrameExtra::visit_provenance`), // Since protected nodes are never GC'd (see `borrow_tracker::FrameExtra::visit_provenance`),
// we know that `node` is not protected because otherwise `live` would // we know that `node` is not protected because otherwise `live` would
// have contained `node.tag`. // have contained `node.tag`.
let child_idx = node.children[0];
let child = self.nodes.get(child_idx).unwrap(); let child = self.nodes.get(child_idx).unwrap();
// Check that for that one child, `can_be_replaced_by_child` holds for the permission // Check that for that one child, `can_be_replaced_by_child` holds for the permission
// on all locations. // on all locations.

View File

@ -39,7 +39,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|ecx| &mut ecx.machine.sync.init_onces, |ecx| &mut ecx.machine.sync.init_onces,
|_| interp_ok(Default::default()), |_| interp_ok(Default::default()),
)? )?
.ok_or_else(|| err_ub_format!("init_once has invalid ID")).into() .ok_or_else(|| err_ub_format!("init_once has invalid ID"))
.into()
} }
#[inline] #[inline]

View File

@ -307,7 +307,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|ecx| &mut ecx.machine.sync.mutexes, |ecx| &mut ecx.machine.sync.mutexes,
|ecx| initialize_data(ecx).map(|data| Mutex { data, ..Default::default() }), |ecx| initialize_data(ecx).map(|data| Mutex { data, ..Default::default() }),
)? )?
.ok_or_else(|| err_ub_format!("mutex has invalid ID")).into() .ok_or_else(|| err_ub_format!("mutex has invalid ID"))
.into()
} }
/// Retrieve the additional data stored for a mutex. /// Retrieve the additional data stored for a mutex.
@ -334,7 +335,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|ecx| &mut ecx.machine.sync.rwlocks, |ecx| &mut ecx.machine.sync.rwlocks,
|ecx| initialize_data(ecx).map(|data| RwLock { data, ..Default::default() }), |ecx| initialize_data(ecx).map(|data| RwLock { data, ..Default::default() }),
)? )?
.ok_or_else(|| err_ub_format!("rwlock has invalid ID")).into() .ok_or_else(|| err_ub_format!("rwlock has invalid ID"))
.into()
} }
/// Retrieve the additional data stored for a rwlock. /// Retrieve the additional data stored for a rwlock.
@ -375,7 +377,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|ecx| &mut ecx.machine.sync.condvars, |ecx| &mut ecx.machine.sync.condvars,
|ecx| initialize_data(ecx).map(|data| Condvar { data, ..Default::default() }), |ecx| initialize_data(ecx).map(|data| Condvar { data, ..Default::default() }),
)? )?
.ok_or_else(|| err_ub_format!("condvar has invalid ID")).into() .ok_or_else(|| err_ub_format!("condvar has invalid ID"))
.into()
} }
/// Retrieve the additional data stored for a condvar. /// Retrieve the additional data stored for a condvar.

View File

@ -151,7 +151,7 @@ impl VClock {
/// Load the internal timestamp slice in the vector clock /// Load the internal timestamp slice in the vector clock
#[inline] #[inline]
pub(super) fn as_slice(&self) -> &[VTimestamp] { pub(super) fn as_slice(&self) -> &[VTimestamp] {
debug_assert!(!self.0.last().is_some_and(|t| t.time() == 0)); debug_assert!(self.0.last().is_none_or(|t| t.time() != 0));
self.0.as_slice() self.0.as_slice()
} }

View File

@ -31,65 +31,6 @@ pub enum AccessKind {
Write, Write,
} }
// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/unix/mod.rs>.
const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
use std::io::ErrorKind::*;
&[
("E2BIG", ArgumentListTooLong),
("EADDRINUSE", AddrInUse),
("EADDRNOTAVAIL", AddrNotAvailable),
("EBUSY", ResourceBusy),
("ECONNABORTED", ConnectionAborted),
("ECONNREFUSED", ConnectionRefused),
("ECONNRESET", ConnectionReset),
("EDEADLK", Deadlock),
("EDQUOT", FilesystemQuotaExceeded),
("EEXIST", AlreadyExists),
("EFBIG", FileTooLarge),
("EHOSTUNREACH", HostUnreachable),
("EINTR", Interrupted),
("EINVAL", InvalidInput),
("EISDIR", IsADirectory),
("ELOOP", FilesystemLoop),
("ENOENT", NotFound),
("ENOMEM", OutOfMemory),
("ENOSPC", StorageFull),
("ENOSYS", Unsupported),
("EMLINK", TooManyLinks),
("ENAMETOOLONG", InvalidFilename),
("ENETDOWN", NetworkDown),
("ENETUNREACH", NetworkUnreachable),
("ENOTCONN", NotConnected),
("ENOTDIR", NotADirectory),
("ENOTEMPTY", DirectoryNotEmpty),
("EPIPE", BrokenPipe),
("EROFS", ReadOnlyFilesystem),
("ESPIPE", NotSeekable),
("ESTALE", StaleNetworkFileHandle),
("ETIMEDOUT", TimedOut),
("ETXTBSY", ExecutableFileBusy),
("EXDEV", CrossesDevices),
// The following have two valid options. We have both for the forwards mapping; only the
// first one will be used for the backwards mapping.
("EPERM", PermissionDenied),
("EACCES", PermissionDenied),
("EWOULDBLOCK", WouldBlock),
("EAGAIN", WouldBlock),
]
};
// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/mod.rs>.
const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
use std::io::ErrorKind::*;
// FIXME: this is still incomplete.
&[
("ERROR_ACCESS_DENIED", PermissionDenied),
("ERROR_FILE_NOT_FOUND", NotFound),
("ERROR_INVALID_PARAMETER", InvalidInput),
]
};
/// Gets an instance for a path. /// Gets an instance for a path.
/// ///
/// A `None` namespace indicates we are looking for a module. /// A `None` namespace indicates we are looking for a module.
@ -745,119 +686,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
self.eval_context_ref().tcx.sess.target.families.iter().any(|f| f == "unix") self.eval_context_ref().tcx.sess.target.families.iter().any(|f| f == "unix")
} }
/// Get last error variable as a place, lazily allocating thread-local storage for it if
/// necessary.
fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
let this = self.eval_context_mut();
if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() {
interp_ok(errno_place.clone())
} else {
// Allocate new place, set initial value to 0.
let errno_layout = this.machine.layouts.u32;
let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?;
this.write_scalar(Scalar::from_u32(0), &errno_place)?;
this.active_thread_mut().last_error = Some(errno_place.clone());
interp_ok(errno_place)
}
}
/// Sets the last error variable.
fn set_last_error(&mut self, scalar: Scalar) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let errno_place = this.last_error_place()?;
this.write_scalar(scalar, &errno_place)
}
/// Gets the last error variable.
fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let errno_place = this.last_error_place()?;
this.read_scalar(&errno_place)
}
/// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
/// as a platform-specific errnum.
fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
if target.families.iter().any(|f| f == "unix") {
for &(name, kind) in UNIX_IO_ERROR_TABLE {
if err.kind() == kind {
return interp_ok(this.eval_libc(name));
}
}
throw_unsup_format!("unsupported io error: {err}")
} else if target.families.iter().any(|f| f == "windows") {
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if err.kind() == kind {
return interp_ok(this.eval_windows("c", name));
}
}
throw_unsup_format!("unsupported io error: {err}");
} else {
throw_unsup_format!(
"converting io::Error into errnum is unsupported for OS {}",
target.os
)
}
}
/// The inverse of `io_error_to_errnum`.
#[allow(clippy::needless_return)]
fn try_errnum_to_io_error(
&self,
errnum: Scalar,
) -> InterpResult<'tcx, Option<std::io::ErrorKind>> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
if target.families.iter().any(|f| f == "unix") {
let errnum = errnum.to_i32()?;
for &(name, kind) in UNIX_IO_ERROR_TABLE {
if errnum == this.eval_libc_i32(name) {
return interp_ok(Some(kind));
}
}
return interp_ok(None);
} else if target.families.iter().any(|f| f == "windows") {
let errnum = errnum.to_u32()?;
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if errnum == this.eval_windows("c", name).to_u32()? {
return interp_ok(Some(kind));
}
}
return interp_ok(None);
} else {
throw_unsup_format!(
"converting errnum into io::Error is unsupported for OS {}",
target.os
)
}
}
/// Sets the last OS error using a `std::io::ErrorKind`.
fn set_last_error_from_io_error(&mut self, err: std::io::Error) -> InterpResult<'tcx> {
self.set_last_error(self.io_error_to_errnum(err)?)
}
/// Helper function that consumes a `std::io::Result<T>` and returns a
/// `InterpResult<'tcx, T>` instead. In case the result is an error, this function returns
/// `Ok(-1)` and sets the last OS error accordingly.
///
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
/// functions return different integer types (like `read`, that returns an `i64`).
fn try_unwrap_io_result<T: From<i32>>(
&mut self,
result: std::io::Result<T>,
) -> InterpResult<'tcx, T> {
match result {
Ok(ok) => interp_ok(ok),
Err(e) => {
self.eval_context_mut().set_last_error_from_io_error(e)?;
interp_ok((-1).into())
}
}
}
/// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type /// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type
fn deref_pointer_as( fn deref_pointer_as(
&self, &self,
@ -924,17 +752,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let nanoseconds_scalar = this.read_scalar(&nanoseconds_place)?; let nanoseconds_scalar = this.read_scalar(&nanoseconds_place)?;
let nanoseconds = nanoseconds_scalar.to_target_isize(this)?; let nanoseconds = nanoseconds_scalar.to_target_isize(this)?;
interp_ok(try { interp_ok(
// tv_sec must be non-negative. try {
let seconds: u64 = seconds.try_into().ok()?; // tv_sec must be non-negative.
// tv_nsec must be non-negative. let seconds: u64 = seconds.try_into().ok()?;
let nanoseconds: u32 = nanoseconds.try_into().ok()?; // tv_nsec must be non-negative.
if nanoseconds >= 1_000_000_000 { let nanoseconds: u32 = nanoseconds.try_into().ok()?;
// tv_nsec must not be greater than 999,999,999. if nanoseconds >= 1_000_000_000 {
None? // tv_nsec must not be greater than 999,999,999.
} None?
Duration::new(seconds, nanoseconds) }
}) Duration::new(seconds, nanoseconds)
},
)
} }
/// Read bytes from a byte slice. /// Read bytes from a byte slice.

View File

@ -150,6 +150,7 @@ pub use crate::range_map::RangeMap;
pub use crate::shims::EmulateItemResult; pub use crate::shims::EmulateItemResult;
pub use crate::shims::env::{EnvVars, EvalContextExt as _}; pub use crate::shims::env::{EnvVars, EvalContextExt as _};
pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _}; pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _};
pub use crate::shims::io_error::{EvalContextExt as _, LibcError};
pub use crate::shims::os_str::EvalContextExt as _; pub use crate::shims::os_str::EvalContextExt as _;
pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _}; pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _};
pub use crate::shims::time::EvalContextExt as _; pub use crate::shims::time::EvalContextExt as _;

View File

@ -0,0 +1,228 @@
use std::io;
use crate::*;
/// A representation of an IO error: either a libc error name,
/// or a host error.
#[derive(Debug)]
pub enum IoError {
LibcError(&'static str),
HostError(io::Error),
Raw(Scalar),
}
pub use self::IoError::*;
impl From<io::Error> for IoError {
fn from(value: io::Error) -> Self {
IoError::HostError(value)
}
}
impl From<io::ErrorKind> for IoError {
fn from(value: io::ErrorKind) -> Self {
IoError::HostError(value.into())
}
}
impl From<Scalar> for IoError {
fn from(value: Scalar) -> Self {
IoError::Raw(value)
}
}
// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/unix/mod.rs>.
const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
use std::io::ErrorKind::*;
&[
("E2BIG", ArgumentListTooLong),
("EADDRINUSE", AddrInUse),
("EADDRNOTAVAIL", AddrNotAvailable),
("EBUSY", ResourceBusy),
("ECONNABORTED", ConnectionAborted),
("ECONNREFUSED", ConnectionRefused),
("ECONNRESET", ConnectionReset),
("EDEADLK", Deadlock),
("EDQUOT", FilesystemQuotaExceeded),
("EEXIST", AlreadyExists),
("EFBIG", FileTooLarge),
("EHOSTUNREACH", HostUnreachable),
("EINTR", Interrupted),
("EINVAL", InvalidInput),
("EISDIR", IsADirectory),
("ELOOP", FilesystemLoop),
("ENOENT", NotFound),
("ENOMEM", OutOfMemory),
("ENOSPC", StorageFull),
("ENOSYS", Unsupported),
("EMLINK", TooManyLinks),
("ENAMETOOLONG", InvalidFilename),
("ENETDOWN", NetworkDown),
("ENETUNREACH", NetworkUnreachable),
("ENOTCONN", NotConnected),
("ENOTDIR", NotADirectory),
("ENOTEMPTY", DirectoryNotEmpty),
("EPIPE", BrokenPipe),
("EROFS", ReadOnlyFilesystem),
("ESPIPE", NotSeekable),
("ESTALE", StaleNetworkFileHandle),
("ETIMEDOUT", TimedOut),
("ETXTBSY", ExecutableFileBusy),
("EXDEV", CrossesDevices),
// The following have two valid options. We have both for the forwards mapping; only the
// first one will be used for the backwards mapping.
("EPERM", PermissionDenied),
("EACCES", PermissionDenied),
("EWOULDBLOCK", WouldBlock),
("EAGAIN", WouldBlock),
]
};
// This mapping should match `decode_error_kind` in
// <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/mod.rs>.
const WINDOWS_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = {
use std::io::ErrorKind::*;
// FIXME: this is still incomplete.
&[
("ERROR_ACCESS_DENIED", PermissionDenied),
("ERROR_FILE_NOT_FOUND", NotFound),
("ERROR_INVALID_PARAMETER", InvalidInput),
]
};
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Get last error variable as a place, lazily allocating thread-local storage for it if
/// necessary.
fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
let this = self.eval_context_mut();
if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() {
interp_ok(errno_place.clone())
} else {
// Allocate new place, set initial value to 0.
let errno_layout = this.machine.layouts.u32;
let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?;
this.write_scalar(Scalar::from_u32(0), &errno_place)?;
this.active_thread_mut().last_error = Some(errno_place.clone());
interp_ok(errno_place)
}
}
/// Sets the last error variable.
fn set_last_error(&mut self, err: impl Into<IoError>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let errno = match err.into() {
HostError(err) => this.io_error_to_errnum(err)?,
LibcError(name) => this.eval_libc(name),
Raw(val) => val,
};
let errno_place = this.last_error_place()?;
this.write_scalar(errno, &errno_place)
}
/// Sets the last OS error and writes -1 to dest place.
fn set_last_error_and_return(
&mut self,
err: impl Into<IoError>,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.set_last_error(err)?;
this.write_int(-1, dest)?;
interp_ok(())
}
/// Sets the last OS error and return `-1` as a `i32`-typed Scalar
fn set_last_error_and_return_i32(
&mut self,
err: impl Into<IoError>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
this.set_last_error(err)?;
interp_ok(Scalar::from_i32(-1))
}
/// Gets the last error variable.
fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let errno_place = this.last_error_place()?;
this.read_scalar(&errno_place)
}
/// This function tries to produce the most similar OS error from the `std::io::ErrorKind`
/// as a platform-specific errnum.
fn io_error_to_errnum(&self, err: std::io::Error) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
if target.families.iter().any(|f| f == "unix") {
for &(name, kind) in UNIX_IO_ERROR_TABLE {
if err.kind() == kind {
return interp_ok(this.eval_libc(name));
}
}
throw_unsup_format!("unsupported io error: {err}")
} else if target.families.iter().any(|f| f == "windows") {
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if err.kind() == kind {
return interp_ok(this.eval_windows("c", name));
}
}
throw_unsup_format!("unsupported io error: {err}");
} else {
throw_unsup_format!(
"converting io::Error into errnum is unsupported for OS {}",
target.os
)
}
}
/// The inverse of `io_error_to_errnum`.
#[allow(clippy::needless_return)]
fn try_errnum_to_io_error(
&self,
errnum: Scalar,
) -> InterpResult<'tcx, Option<std::io::ErrorKind>> {
let this = self.eval_context_ref();
let target = &this.tcx.sess.target;
if target.families.iter().any(|f| f == "unix") {
let errnum = errnum.to_i32()?;
for &(name, kind) in UNIX_IO_ERROR_TABLE {
if errnum == this.eval_libc_i32(name) {
return interp_ok(Some(kind));
}
}
return interp_ok(None);
} else if target.families.iter().any(|f| f == "windows") {
let errnum = errnum.to_u32()?;
for &(name, kind) in WINDOWS_IO_ERROR_TABLE {
if errnum == this.eval_windows("c", name).to_u32()? {
return interp_ok(Some(kind));
}
}
return interp_ok(None);
} else {
throw_unsup_format!(
"converting errnum into io::Error is unsupported for OS {}",
target.os
)
}
}
/// Helper function that consumes an `std::io::Result<T>` and returns an
/// `InterpResult<'tcx,T>::Ok` instead. In case the result is an error, this function returns
/// `Ok(-1)` and sets the last OS error accordingly.
///
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
/// functions return different integer types (like `read`, that returns an `i64`).
fn try_unwrap_io_result<T: From<i32>>(
&mut self,
result: std::io::Result<T>,
) -> InterpResult<'tcx, T> {
match result {
Ok(ok) => interp_ok(ok),
Err(e) => {
self.eval_context_mut().set_last_error(e)?;
interp_ok((-1).into())
}
}
}
}

View File

@ -12,6 +12,7 @@ mod x86;
pub mod env; pub mod env;
pub mod extern_static; pub mod extern_static;
pub mod foreign_items; pub mod foreign_items;
pub mod io_error;
pub mod os_str; pub mod os_str;
pub mod panic; pub mod panic;
pub mod time; pub mod time;

View File

@ -11,7 +11,8 @@ use crate::*;
/// Returns the time elapsed between the provided time and the unix epoch as a `Duration`. /// Returns the time elapsed between the provided time and the unix epoch as a `Duration`.
pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> { pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Duration> {
time.duration_since(SystemTime::UNIX_EPOCH) time.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| err_unsup_format!("times before the Unix epoch are not supported")).into() .map_err(|_| err_unsup_format!("times before the Unix epoch are not supported"))
.into()
} }
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}

View File

@ -177,9 +177,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_i32(0)) // return zero on success interp_ok(Scalar::from_i32(0)) // return zero on success
} else { } else {
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
let einval = this.eval_libc("EINVAL"); this.set_last_error_and_return_i32(LibcError("EINVAL"))
this.set_last_error(einval)?;
interp_ok(Scalar::from_i32(-1))
} }
} }
@ -203,9 +201,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_i32(0)) interp_ok(Scalar::from_i32(0))
} else { } else {
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character. // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
let einval = this.eval_libc("EINVAL"); this.set_last_error_and_return_i32(LibcError("EINVAL"))
this.set_last_error(einval)?;
interp_ok(Scalar::from_i32(-1))
} }
} }
@ -218,7 +214,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`getcwd`", reject_with)?; this.reject_in_isolation("`getcwd`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Pointer::null()); return interp_ok(Pointer::null());
} }
@ -228,10 +224,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if this.write_path_to_c_str(&cwd, buf, size)?.0 { if this.write_path_to_c_str(&cwd, buf, size)?.0 {
return interp_ok(buf); return interp_ok(buf);
} }
let erange = this.eval_libc("ERANGE"); this.set_last_error(LibcError("ERANGE"))?;
this.set_last_error(erange)?;
} }
Err(e) => this.set_last_error_from_io_error(e)?, Err(e) => this.set_last_error(e)?,
} }
interp_ok(Pointer::null()) interp_ok(Pointer::null())
@ -245,9 +240,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`chdir`", reject_with)?; this.reject_in_isolation("`chdir`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return interp_ok(Scalar::from_i32(-1));
} }
let result = env::set_current_dir(path).map(|()| 0); let result = env::set_current_dir(path).map(|()| 0);

View File

@ -150,7 +150,10 @@ impl FileDescription for io::Stdin {
helpers::isolation_abort_error("`read` from stdin")?; helpers::isolation_abort_error("`read` from stdin")?;
} }
let result = Read::read(&mut { self }, &mut bytes); let result = Read::read(&mut { self }, &mut bytes);
ecx.return_read_bytes_and_count(ptr, &bytes, result, dest) match result {
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn is_tty(&self, communicate_allowed: bool) -> bool { fn is_tty(&self, communicate_allowed: bool) -> bool {
@ -181,7 +184,10 @@ impl FileDescription for io::Stdout {
// the host -- there is no good in adding extra buffering // the host -- there is no good in adding extra buffering
// here. // here.
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
ecx.return_written_byte_count_or_error(result, dest) match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn is_tty(&self, communicate_allowed: bool) -> bool { fn is_tty(&self, communicate_allowed: bool) -> bool {
@ -207,7 +213,10 @@ impl FileDescription for io::Stderr {
// We allow writing to stderr even with isolation enabled. // We allow writing to stderr even with isolation enabled.
// No need to flush, stderr is not buffered. // No need to flush, stderr is not buffered.
let result = Write::write(&mut { self }, bytes); let result = Write::write(&mut { self }, bytes);
ecx.return_written_byte_count_or_error(result, dest) match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn is_tty(&self, communicate_allowed: bool) -> bool { fn is_tty(&self, communicate_allowed: bool) -> bool {
@ -234,8 +243,7 @@ impl FileDescription for NullOutput {
ecx: &mut MiriInterpCx<'tcx>, ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> { ) -> InterpResult<'tcx> {
// We just don't write anything, but report to the user that we did. // We just don't write anything, but report to the user that we did.
let result = Ok(len); ecx.return_write_success(len, dest)
ecx.return_written_byte_count_or_error(result, dest)
} }
} }
@ -473,14 +481,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
if args.len() < 2 { let [fd_num, cmd, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for fcntl: got {}, expected at least 2", "incorrect number of arguments for fcntl: got {}, expected at least 2",
args.len() args.len()
); );
} };
let fd_num = this.read_scalar(&args[0])?.to_i32()?; let fd_num = this.read_scalar(fd_num)?.to_i32()?;
let cmd = this.read_scalar(&args[1])?.to_i32()?; let cmd = this.read_scalar(cmd)?.to_i32()?;
// We only support getting the flags for a descriptor. // We only support getting the flags for a descriptor.
if cmd == this.eval_libc_i32("F_GETFD") { if cmd == this.eval_libc_i32("F_GETFD") {
@ -500,24 +508,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only // because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor, // differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
// thus they can share the same implementation here. // thus they can share the same implementation here.
if args.len() < 3 { let [_, _, start, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3", "incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
args.len() args.len()
); );
} };
let start = this.read_scalar(&args[2])?.to_i32()?; let start = this.read_scalar(start)?.to_i32()?;
match this.machine.fds.get(fd_num) { match this.machine.fds.get(fd_num) {
Some(fd) => interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start))), Some(fd) =>
interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start))),
None => interp_ok(Scalar::from_i32(this.fd_not_found()?)), None => interp_ok(Scalar::from_i32(this.fd_not_found()?)),
} }
} else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") { } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?; this.reject_in_isolation("`fcntl`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
return interp_ok(Scalar::from_i32(-1));
} }
this.ffullsync_fd(fd_num) this.ffullsync_fd(fd_num)
@ -598,10 +606,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
None => fd.read(&fd, communicate, buf, count, dest, this)?, None => fd.read(&fd, communicate, buf, count, dest, this)?,
Some(offset) => { Some(offset) => {
let Ok(offset) = u64::try_from(offset) else { let Ok(offset) = u64::try_from(offset) else {
let einval = this.eval_libc("EINVAL"); return this.set_last_error_and_return(LibcError("EINVAL"), dest);
this.set_last_error(einval)?;
this.write_int(-1, dest)?;
return interp_ok(());
}; };
fd.pread(communicate, offset, buf, count, dest, this)? fd.pread(communicate, offset, buf, count, dest, this)?
} }
@ -643,10 +648,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
None => fd.write(&fd, communicate, buf, count, dest, this)?, None => fd.write(&fd, communicate, buf, count, dest, this)?,
Some(offset) => { Some(offset) => {
let Ok(offset) = u64::try_from(offset) else { let Ok(offset) = u64::try_from(offset) else {
let einval = this.eval_libc("EINVAL"); return this.set_last_error_and_return(LibcError("EINVAL"), dest);
this.set_last_error(einval)?;
this.write_int(-1, dest)?;
return interp_ok(());
}; };
fd.pwrite(communicate, buf, count, offset, dest, this)? fd.pwrite(communicate, buf, count, offset, dest, this)?
} }
@ -655,46 +657,39 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
} }
/// Helper to implement `FileDescription::read`: /// Helper to implement `FileDescription::read`:
/// `result` should be the return value of some underlying `read` call that used `bytes` as its output buffer. /// This is only used when `read` is successful.
/// `actual_read_size` should be the return value of some underlying `read` call that used
/// `bytes` as its output buffer.
/// The length of `bytes` must not exceed either the host's or the target's `isize`. /// The length of `bytes` must not exceed either the host's or the target's `isize`.
/// If `Result` indicates success, `bytes` is written to `buf` and the size is written to `dest`. /// `bytes` is written to `buf` and the size is written to `dest`.
/// Otherwise, `-1` is written to `dest` and the last libc error is set appropriately. fn return_read_success(
fn return_read_bytes_and_count(
&mut self, &mut self,
buf: Pointer, buf: Pointer,
bytes: &[u8], bytes: &[u8],
result: io::Result<usize>, actual_read_size: usize,
dest: &MPlaceTy<'tcx>, dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> { ) -> InterpResult<'tcx> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
match result { // If reading to `bytes` did not fail, we write those bytes to the buffer.
Ok(read_bytes) => { // Crucially, if fewer than `bytes.len()` bytes were read, only write
// If reading to `bytes` did not fail, we write those bytes to the buffer. // that much into the output buffer!
// Crucially, if fewer than `bytes.len()` bytes were read, only write this.write_bytes_ptr(buf, bytes[..actual_read_size].iter().copied())?;
// that much into the output buffer!
this.write_bytes_ptr(buf, bytes[..read_bytes].iter().copied())?; // The actual read size is always less than what got originally requested so this cannot fail.
// The actual read size is always less than what got originally requested so this cannot fail. this.write_int(u64::try_from(actual_read_size).unwrap(), dest)?;
this.write_int(u64::try_from(read_bytes).unwrap(), dest)?; interp_ok(())
interp_ok(())
}
Err(e) => {
this.set_last_error_from_io_error(e)?;
this.write_int(-1, dest)?;
interp_ok(())
}
}
} }
/// This function writes the number of written bytes (given in `result`) to `dest`, or sets the /// Helper to implement `FileDescription::write`:
/// last libc error and writes -1 to dest. /// This function is only used when `write` is successful, and writes `actual_write_size` to `dest`
fn return_written_byte_count_or_error( fn return_write_success(
&mut self, &mut self,
result: io::Result<usize>, actual_write_size: usize,
dest: &MPlaceTy<'tcx>, dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> { ) -> InterpResult<'tcx> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
let result = this.try_unwrap_io_result(result.map(|c| i64::try_from(c).unwrap()))?; // The actual write size is always less than what got originally requested so this cannot fail.
this.write_int(result, dest)?; this.write_int(u64::try_from(actual_write_size).unwrap(), dest)?;
interp_ok(()) interp_ok(())
} }
} }

View File

@ -355,8 +355,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=reallocarray // FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=reallocarray
match this.compute_size_in_bytes(Size::from_bytes(size), nmemb) { match this.compute_size_in_bytes(Size::from_bytes(size), nmemb) {
None => { None => {
let einval = this.eval_libc("ENOMEM"); let enmem = this.eval_libc("ENOMEM");
this.set_last_error(einval)?; this.set_last_error(enmem)?;
this.write_null(dest)?; this.write_null(dest)?;
} }
Some(len) => { Some(len) => {
@ -646,13 +646,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let chunk_size = CpuAffinityMask::chunk_size(this); let chunk_size = CpuAffinityMask::chunk_size(this);
if this.ptr_is_null(mask)? { if this.ptr_is_null(mask)? {
let einval = this.eval_libc("EFAULT"); let efault = this.eval_libc("EFAULT");
this.set_last_error(einval)?; this.set_last_error(efault)?;
this.write_int(-1, dest)?; this.write_int(-1, dest)?;
} else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 { } else if cpusetsize == 0 || cpusetsize.checked_rem(chunk_size).unwrap() != 0 {
// we only copy whole chunks of size_of::<c_ulong>() // we only copy whole chunks of size_of::<c_ulong>()
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
this.write_int(-1, dest)?; this.write_int(-1, dest)?;
} else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) { } else if let Some(cpuset) = this.machine.thread_cpu_affinity.get(&thread_id) {
let cpuset = cpuset.clone(); let cpuset = cpuset.clone();
@ -662,8 +661,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_null(dest)?; this.write_null(dest)?;
} else { } else {
// The thread whose ID is pid could not be found // The thread whose ID is pid could not be found
let einval = this.eval_libc("ESRCH"); let esrch = this.eval_libc("ESRCH");
this.set_last_error(einval)?; this.set_last_error(esrch)?;
this.write_int(-1, dest)?; this.write_int(-1, dest)?;
} }
} }
@ -689,8 +688,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}; };
if this.ptr_is_null(mask)? { if this.ptr_is_null(mask)? {
let einval = this.eval_libc("EFAULT"); let efault = this.eval_libc("EFAULT");
this.set_last_error(einval)?; this.set_last_error(efault)?;
this.write_int(-1, dest)?; this.write_int(-1, dest)?;
} else { } else {
// NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`. // NOTE: cpusetsize might be smaller than `CpuAffinityMask::CPU_MASK_BYTES`.
@ -707,8 +706,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
} }
None => { None => {
// The intersection between the mask and the available CPUs was empty. // The intersection between the mask and the available CPUs was empty.
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
this.write_int(-1, dest)?; this.write_int(-1, dest)?;
} }
} }

View File

@ -41,7 +41,10 @@ impl FileDescription for FileHandle {
assert!(communicate_allowed, "isolation should have prevented even opening a file"); assert!(communicate_allowed, "isolation should have prevented even opening a file");
let mut bytes = vec![0; len]; let mut bytes = vec![0; len];
let result = (&mut &self.file).read(&mut bytes); let result = (&mut &self.file).read(&mut bytes);
ecx.return_read_bytes_and_count(ptr, &bytes, result, dest) match result {
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn write<'tcx>( fn write<'tcx>(
@ -56,7 +59,10 @@ impl FileDescription for FileHandle {
assert!(communicate_allowed, "isolation should have prevented even opening a file"); assert!(communicate_allowed, "isolation should have prevented even opening a file");
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
let result = (&mut &self.file).write(bytes); let result = (&mut &self.file).write(bytes);
ecx.return_written_byte_count_or_error(result, dest) match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn pread<'tcx>( fn pread<'tcx>(
@ -84,7 +90,10 @@ impl FileDescription for FileHandle {
res res
}; };
let result = f(); let result = f();
ecx.return_read_bytes_and_count(ptr, &bytes, result, dest) match result {
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn pwrite<'tcx>( fn pwrite<'tcx>(
@ -112,7 +121,10 @@ impl FileDescription for FileHandle {
res res
}; };
let result = f(); let result = f();
ecx.return_written_byte_count_or_error(result, dest) match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
} }
fn seek<'tcx>( fn seek<'tcx>(
@ -421,18 +433,18 @@ fn maybe_sync_file(
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn open(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> { fn open(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
if args.len() < 2 { let [path_raw, flag, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for `open`: got {}, expected at least 2", "incorrect number of arguments for `open`: got {}, expected at least 2",
args.len() args.len()
); );
} };
let this = self.eval_context_mut(); let this = self.eval_context_mut();
let path_raw = this.read_pointer(&args[0])?; let path_raw = this.read_pointer(path_raw)?;
let path = this.read_path_from_c_str(path_raw)?; let path = this.read_path_from_c_str(path_raw)?;
let flag = this.read_scalar(&args[1])?.to_i32()?; let flag = this.read_scalar(flag)?.to_i32()?;
let mut options = OpenOptions::new(); let mut options = OpenOptions::new();
@ -526,8 +538,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let o_tmpfile = this.eval_libc_i32("O_TMPFILE"); let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
if flag & o_tmpfile == o_tmpfile { if flag & o_tmpfile == o_tmpfile {
// if the flag contains `O_TMPFILE` then we return a graceful error // if the flag contains `O_TMPFILE` then we return a graceful error
let eopnotsupp = this.eval_libc("EOPNOTSUPP"); this.set_last_error(LibcError("EOPNOTSUPP"))?;
this.set_last_error(eopnotsupp)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
} }
@ -564,7 +575,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`open`", reject_with)?; this.reject_in_isolation("`open`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -583,8 +594,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let seek_from = if whence == this.eval_libc_i32("SEEK_SET") { let seek_from = if whence == this.eval_libc_i32("SEEK_SET") {
if offset < 0 { if offset < 0 {
// Negative offsets return `EINVAL`. // Negative offsets return `EINVAL`.
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
return interp_ok(Scalar::from_i64(-1)); return interp_ok(Scalar::from_i64(-1));
} else { } else {
SeekFrom::Start(u64::try_from(offset).unwrap()) SeekFrom::Start(u64::try_from(offset).unwrap())
@ -594,8 +604,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
} else if whence == this.eval_libc_i32("SEEK_END") { } else if whence == this.eval_libc_i32("SEEK_END") {
SeekFrom::End(i64::try_from(offset).unwrap()) SeekFrom::End(i64::try_from(offset).unwrap())
} else { } else {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
return interp_ok(Scalar::from_i64(-1)); return interp_ok(Scalar::from_i64(-1));
}; };
@ -619,7 +628,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`unlink`", reject_with)?; this.reject_in_isolation("`unlink`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -650,7 +659,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`symlink`", reject_with)?; this.reject_in_isolation("`symlink`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -951,7 +960,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`rename`", reject_with)?; this.reject_in_isolation("`rename`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -975,7 +984,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`mkdir`", reject_with)?; this.reject_in_isolation("`mkdir`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -1003,7 +1012,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Reject if isolation is enabled. // Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`rmdir`", reject_with)?; this.reject_in_isolation("`rmdir`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -1037,7 +1046,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_target_usize(id, this)) interp_ok(Scalar::from_target_usize(id, this))
} }
Err(e) => { Err(e) => {
this.set_last_error_from_io_error(e)?; this.set_last_error(e)?;
interp_ok(Scalar::null_ptr(this)) interp_ok(Scalar::null_ptr(this))
} }
} }
@ -1122,7 +1131,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
None None
} }
Some(Err(e)) => { Some(Err(e)) => {
this.set_last_error_from_io_error(e)?; this.set_last_error(e)?;
None None
} }
}; };
@ -1308,15 +1317,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_i32(result)) interp_ok(Scalar::from_i32(result))
} else { } else {
drop(fd); drop(fd);
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
interp_ok(Scalar::from_i32(-1)) interp_ok(Scalar::from_i32(-1))
} }
} else { } else {
drop(fd); drop(fd);
// The file is not writable // The file is not writable
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
interp_ok(Scalar::from_i32(-1)) interp_ok(Scalar::from_i32(-1))
} }
} }
@ -1394,16 +1401,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let flags = this.read_scalar(flags_op)?.to_i32()?; let flags = this.read_scalar(flags_op)?.to_i32()?;
if offset < 0 || nbytes < 0 { if offset < 0 || nbytes < 0 {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE") let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")
| this.eval_libc_i32("SYNC_FILE_RANGE_WRITE") | this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")
| this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER"); | this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER");
if flags & allowed_flags != flags { if flags & allowed_flags != flags {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -1465,7 +1470,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(path_bytes.len().try_into().unwrap()) interp_ok(path_bytes.len().try_into().unwrap())
} }
Err(e) => { Err(e) => {
this.set_last_error_from_io_error(e)?; this.set_last_error(e)?;
interp_ok(-1) interp_ok(-1)
} }
} }
@ -1545,7 +1550,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_maybe_pointer(dest, this)) interp_ok(Scalar::from_maybe_pointer(dest, this))
} }
Err(e) => { Err(e) => {
this.set_last_error_from_io_error(e)?; this.set_last_error(e)?;
interp_ok(Scalar::from_target_usize(0, this)) interp_ok(Scalar::from_target_usize(0, this))
} }
} }
@ -1597,8 +1602,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// If we don't find the suffix, it is an error. // If we don't find the suffix, it is an error.
if last_six_char_bytes != suffix_bytes { if last_six_char_bytes != suffix_bytes {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -1664,7 +1668,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
_ => { _ => {
// "On error, -1 is returned, and errno is set to // "On error, -1 is returned, and errno is set to
// indicate the error" // indicate the error"
this.set_last_error_from_io_error(e)?; this.set_last_error(e)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
}, },
@ -1744,7 +1748,7 @@ impl FileMetadata {
let metadata = match metadata { let metadata = match metadata {
Ok(metadata) => metadata, Ok(metadata) => metadata,
Err(e) => { Err(e) => {
ecx.set_last_error_from_io_error(e)?; ecx.set_last_error(e)?;
return interp_ok(None); return interp_ok(None);
} }
}; };

View File

@ -4,6 +4,7 @@ use std::io;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::time::Duration; use std::time::Duration;
use crate::concurrency::VClock;
use crate::shims::unix::fd::{FdId, FileDescriptionRef, WeakFileDescriptionRef}; use crate::shims::unix::fd::{FdId, FileDescriptionRef, WeakFileDescriptionRef};
use crate::shims::unix::*; use crate::shims::unix::*;
use crate::*; use crate::*;
@ -19,7 +20,7 @@ struct Epoll {
/// and file descriptor value. /// and file descriptor value.
// This is an Rc because EpollInterest need to hold a reference to update // This is an Rc because EpollInterest need to hold a reference to update
// it. // it.
ready_list: Rc<RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>>, ready_list: Rc<ReadyList>,
/// A list of thread ids blocked on this epoll instance. /// A list of thread ids blocked on this epoll instance.
thread_id: RefCell<Vec<ThreadId>>, thread_id: RefCell<Vec<ThreadId>>,
} }
@ -63,7 +64,7 @@ pub struct EpollEventInterest {
/// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html> /// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html>
data: u64, data: u64,
/// Ready list of the epoll instance under which this EpollEventInterest is registered. /// Ready list of the epoll instance under which this EpollEventInterest is registered.
ready_list: Rc<RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>>, ready_list: Rc<ReadyList>,
/// The epoll file description that this EpollEventInterest is registered under. /// The epoll file description that this EpollEventInterest is registered under.
weak_epfd: WeakFileDescriptionRef, weak_epfd: WeakFileDescriptionRef,
} }
@ -88,6 +89,12 @@ pub struct EpollReadyEvents {
pub epollerr: bool, pub epollerr: bool,
} }
#[derive(Debug, Default)]
struct ReadyList {
mapping: RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>,
clock: RefCell<VClock>,
}
impl EpollReadyEvents { impl EpollReadyEvents {
pub fn new() -> Self { pub fn new() -> Self {
EpollReadyEvents { EpollReadyEvents {
@ -127,7 +134,7 @@ impl EpollReadyEvents {
} }
impl Epoll { impl Epoll {
fn get_ready_list(&self) -> Rc<RefCell<BTreeMap<(FdId, i32), EpollEventInstance>>> { fn get_ready_list(&self) -> Rc<ReadyList> {
Rc::clone(&self.ready_list) Rc::clone(&self.ready_list)
} }
} }
@ -207,9 +214,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
); );
} }
let mut epoll_instance = Epoll::default();
epoll_instance.ready_list = Rc::new(RefCell::new(BTreeMap::new()));
let fd = this.machine.fds.insert_new(Epoll::default()); let fd = this.machine.fds.insert_new(Epoll::default());
interp_ok(Scalar::from_i32(fd)) interp_ok(Scalar::from_i32(fd))
} }
@ -261,8 +265,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Throw EINVAL if epfd and fd have the same value. // Throw EINVAL if epfd and fd have the same value.
if epfd_value == fd { if epfd_value == fd {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
return interp_ok(Scalar::from_i32(-1)); return interp_ok(Scalar::from_i32(-1));
} }
@ -378,7 +381,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
drop(epoll_interest); drop(epoll_interest);
// Remove related epoll_interest from ready list. // Remove related epoll_interest from ready list.
ready_list.borrow_mut().remove(&epoll_key); ready_list.mapping.borrow_mut().remove(&epoll_key);
// Remove dangling EpollEventInterest from its global table. // Remove dangling EpollEventInterest from its global table.
// .unwrap() below should succeed because the file description id must have registered // .unwrap() below should succeed because the file description id must have registered
@ -443,8 +446,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let timeout = this.read_scalar(timeout)?.to_i32()?; let timeout = this.read_scalar(timeout)?.to_i32()?;
if epfd_value <= 0 || maxevents <= 0 { if epfd_value <= 0 || maxevents <= 0 {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
this.write_int(-1, dest)?; this.write_int(-1, dest)?;
return interp_ok(()); return interp_ok(());
} }
@ -473,8 +475,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let epoll_file_description = epfd let epoll_file_description = epfd
.downcast::<Epoll>() .downcast::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
let binding = epoll_file_description.get_ready_list(); ready_list_empty = epoll_file_description.ready_list.mapping.borrow().is_empty();
ready_list_empty = binding.borrow_mut().is_empty();
thread_ids = epoll_file_description.thread_id.borrow_mut(); thread_ids = epoll_file_description.thread_id.borrow_mut();
} }
if timeout == 0 || !ready_list_empty { if timeout == 0 || !ready_list_empty {
@ -563,9 +564,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// holds a strong ref to epoll_interest. // holds a strong ref to epoll_interest.
let epfd = epoll_interest.borrow().weak_epfd.upgrade().unwrap(); let epfd = epoll_interest.borrow().weak_epfd.upgrade().unwrap();
// FIXME: We can randomly pick a thread to unblock. // FIXME: We can randomly pick a thread to unblock.
if let Some(thread_id) =
epfd.downcast::<Epoll>().unwrap().thread_id.borrow_mut().pop() let epoll = epfd.downcast::<Epoll>().unwrap();
{
// Synchronize running thread to the epoll ready list.
if let Some(clock) = &this.release_clock() {
epoll.ready_list.clock.borrow_mut().join(clock);
}
if let Some(thread_id) = epoll.thread_id.borrow_mut().pop() {
waiter.push(thread_id); waiter.push(thread_id);
}; };
} }
@ -619,7 +626,7 @@ fn check_and_update_one_event_interest<'tcx>(
// insert an epoll_return to the ready list. // insert an epoll_return to the ready list.
if flags != 0 { if flags != 0 {
let epoll_key = (id, epoll_event_interest.fd_num); let epoll_key = (id, epoll_event_interest.fd_num);
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut(); let ready_list = &mut epoll_event_interest.ready_list.mapping.borrow_mut();
let event_instance = EpollEventInstance::new(flags, epoll_event_interest.data); let event_instance = EpollEventInstance::new(flags, epoll_event_interest.data);
// Triggers the notification by inserting it to the ready list. // Triggers the notification by inserting it to the ready list.
ready_list.insert(epoll_key, event_instance); ready_list.insert(epoll_key, event_instance);
@ -646,7 +653,11 @@ fn blocking_epoll_callback<'tcx>(
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
let ready_list = epoll_file_description.get_ready_list(); let ready_list = epoll_file_description.get_ready_list();
let mut ready_list = ready_list.borrow_mut();
// Synchronize waking thread from the epoll ready list.
ecx.acquire_clock(&ready_list.clock.borrow());
let mut ready_list = ready_list.mapping.borrow_mut();
let mut num_of_events: i32 = 0; let mut num_of_events: i32 = 0;
let mut array_iter = ecx.project_array_fields(events)?; let mut array_iter = ecx.project_array_fields(events)?;

View File

@ -1,7 +1,7 @@
//! Linux `eventfd` implementation. //! Linux `eventfd` implementation.
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::io; use std::io;
use std::io::{Error, ErrorKind}; use std::io::ErrorKind;
use crate::concurrency::VClock; use crate::concurrency::VClock;
use crate::shims::unix::fd::FileDescriptionRef; use crate::shims::unix::fd::FileDescriptionRef;
@ -66,9 +66,7 @@ impl FileDescription for Event {
let ty = ecx.machine.layouts.u64; let ty = ecx.machine.layouts.u64;
// Check the size of slice, and return error only if the size of the slice < 8. // Check the size of slice, and return error only if the size of the slice < 8.
if len < ty.size.bytes_usize() { if len < ty.size.bytes_usize() {
ecx.set_last_error_from_io_error(Error::from(ErrorKind::InvalidInput))?; return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
ecx.write_int(-1, dest)?;
return interp_ok(());
} }
// eventfd read at the size of u64. // eventfd read at the size of u64.
@ -78,9 +76,7 @@ impl FileDescription for Event {
let counter = self.counter.get(); let counter = self.counter.get();
if counter == 0 { if counter == 0 {
if self.is_nonblock { if self.is_nonblock {
ecx.set_last_error_from_io_error(Error::from(ErrorKind::WouldBlock))?; return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
ecx.write_int(-1, dest)?;
return interp_ok(());
} }
throw_unsup_format!("eventfd: blocking is unsupported"); throw_unsup_format!("eventfd: blocking is unsupported");
@ -128,8 +124,7 @@ impl FileDescription for Event {
let ty = ecx.machine.layouts.u64; let ty = ecx.machine.layouts.u64;
// Check the size of slice, and return error only if the size of the slice < 8. // Check the size of slice, and return error only if the size of the slice < 8.
if len < ty.layout.size.bytes_usize() { if len < ty.layout.size.bytes_usize() {
let result = Err(Error::from(ErrorKind::InvalidInput)); return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
return ecx.return_written_byte_count_or_error(result, dest);
} }
// Read the user supplied value from the pointer. // Read the user supplied value from the pointer.
@ -138,8 +133,7 @@ impl FileDescription for Event {
// u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1. // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
if num == u64::MAX { if num == u64::MAX {
let result = Err(Error::from(ErrorKind::InvalidInput)); return ecx.set_last_error_and_return(ErrorKind::InvalidInput, dest);
return ecx.return_written_byte_count_or_error(result, dest);
} }
// If the addition does not let the counter to exceed the maximum value, update the counter. // If the addition does not let the counter to exceed the maximum value, update the counter.
// Else, block. // Else, block.
@ -153,8 +147,7 @@ impl FileDescription for Event {
} }
None | Some(u64::MAX) => None | Some(u64::MAX) =>
if self.is_nonblock { if self.is_nonblock {
let result = Err(Error::from(ErrorKind::WouldBlock)); return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
return ecx.return_written_byte_count_or_error(result, dest);
} else { } else {
throw_unsup_format!("eventfd: blocking is unsupported"); throw_unsup_format!("eventfd: blocking is unsupported");
}, },

View File

@ -122,19 +122,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
id if id == sys_getrandom => { id if id == sys_getrandom => {
// Used by getrandom 0.1 // Used by getrandom 0.1
// The first argument is the syscall id, so skip over it. // The first argument is the syscall id, so skip over it.
if args.len() < 4 { let [_, ptr, len, flags, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for `getrandom` syscall: got {}, expected at least 4", "incorrect number of arguments for `getrandom` syscall: got {}, expected at least 4",
args.len() args.len()
); );
} };
let ptr = this.read_pointer(&args[1])?; let ptr = this.read_pointer(ptr)?;
let len = this.read_target_usize(&args[2])?; let len = this.read_target_usize(len)?;
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK, // The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
// neither of which have any effect on our current PRNG. // neither of which have any effect on our current PRNG.
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes. // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
let _flags = this.read_scalar(&args[3])?.to_i32()?; let _flags = this.read_scalar(flags)?.to_i32()?;
this.gen_random(ptr, len)?; this.gen_random(ptr, len)?;
this.write_scalar(Scalar::from_target_usize(len, this), dest)?; this.write_scalar(Scalar::from_target_usize(len, this), dest)?;

View File

@ -15,19 +15,19 @@ pub fn futex<'tcx>(
// may or may not be left out from the `syscall()` call. // may or may not be left out from the `syscall()` call.
// Therefore we don't use `check_arg_count` here, but only check for the // Therefore we don't use `check_arg_count` here, but only check for the
// number of arguments to fall within a range. // number of arguments to fall within a range.
if args.len() < 3 { let [addr, op, val, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for `futex` syscall: got {}, expected at least 3", "incorrect number of arguments for `futex` syscall: got {}, expected at least 3",
args.len() args.len()
); );
} };
// The first three arguments (after the syscall number itself) are the same to all futex operations: // The first three arguments (after the syscall number itself) are the same to all futex operations:
// (int *addr, int op, int val). // (int *addr, int op, int val).
// We checked above that these definitely exist. // We checked above that these definitely exist.
let addr = this.read_pointer(&args[0])?; let addr = this.read_pointer(addr)?;
let op = this.read_scalar(&args[1])?.to_i32()?; let op = this.read_scalar(op)?.to_i32()?;
let val = this.read_scalar(&args[2])?.to_i32()?; let val = this.read_scalar(val)?.to_i32()?;
// This is a vararg function so we have to bring our own type for this pointer. // This is a vararg function so we have to bring our own type for this pointer.
let addr = this.ptr_to_mplace(addr, this.machine.layouts.i32); let addr = this.ptr_to_mplace(addr, this.machine.layouts.i32);
@ -55,15 +55,15 @@ pub fn futex<'tcx>(
let wait_bitset = op & !futex_realtime == futex_wait_bitset; let wait_bitset = op & !futex_realtime == futex_wait_bitset;
let bitset = if wait_bitset { let bitset = if wait_bitset {
if args.len() < 6 { let [_, _, _, timeout, uaddr2, bitset, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6", "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAIT_BITSET`: got {}, expected at least 6",
args.len() args.len()
); );
} };
let _timeout = this.read_pointer(&args[3])?; let _timeout = this.read_pointer(timeout)?;
let _uaddr2 = this.read_pointer(&args[4])?; let _uaddr2 = this.read_pointer(uaddr2)?;
this.read_scalar(&args[5])?.to_u32()? this.read_scalar(bitset)?.to_u32()?
} else { } else {
if args.len() < 4 { if args.len() < 4 {
throw_ub_format!( throw_ub_format!(
@ -75,8 +75,7 @@ pub fn futex<'tcx>(
}; };
if bitset == 0 { if bitset == 0 {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; this.write_scalar(Scalar::from_target_isize(-1, this), dest)?;
return interp_ok(()); return interp_ok(());
} }
@ -88,8 +87,7 @@ pub fn futex<'tcx>(
let duration = match this.read_timespec(&timeout)? { let duration = match this.read_timespec(&timeout)? {
Some(duration) => duration, Some(duration) => duration,
None => { None => {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; this.write_scalar(Scalar::from_target_isize(-1, this), dest)?;
return interp_ok(()); return interp_ok(());
} }
@ -185,21 +183,20 @@ pub fn futex<'tcx>(
// Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up. // Same as FUTEX_WAKE, but allows you to specify a bitset to select which threads to wake up.
op if op == futex_wake || op == futex_wake_bitset => { op if op == futex_wake || op == futex_wake_bitset => {
let bitset = if op == futex_wake_bitset { let bitset = if op == futex_wake_bitset {
if args.len() < 6 { let [_, _, _, timeout, uaddr2, bitset, ..] = args else {
throw_ub_format!( throw_ub_format!(
"incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6", "incorrect number of arguments for `futex` syscall with `op=FUTEX_WAKE_BITSET`: got {}, expected at least 6",
args.len() args.len()
); );
} };
let _timeout = this.read_pointer(&args[3])?; let _timeout = this.read_pointer(timeout)?;
let _uaddr2 = this.read_pointer(&args[4])?; let _uaddr2 = this.read_pointer(uaddr2)?;
this.read_scalar(&args[5])?.to_u32()? this.read_scalar(bitset)?.to_u32()?
} else { } else {
u32::MAX u32::MAX
}; };
if bitset == 0 { if bitset == 0 {
let einval = this.eval_libc("EINVAL"); this.set_last_error(LibcError("EINVAL"))?;
this.set_last_error(einval)?;
this.write_scalar(Scalar::from_target_isize(-1, this), dest)?; this.write_scalar(Scalar::from_target_isize(-1, this), dest)?;
return interp_ok(()); return interp_ok(());
} }

View File

@ -483,7 +483,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Trying to acquire the same mutex again. // Trying to acquire the same mutex again.
match kind { match kind {
MutexKind::Default => MutexKind::Default =>
throw_ub_format!("trying to acquire already locked default mutex"), throw_ub_format!(
"trying to acquire default mutex already locked by the current thread"
),
MutexKind::Normal => throw_machine_stop!(TerminationInfo::Deadlock), MutexKind::Normal => throw_machine_stop!(TerminationInfo::Deadlock),
MutexKind::ErrorCheck => this.eval_libc_i32("EDEADLK"), MutexKind::ErrorCheck => this.eval_libc_i32("EDEADLK"),
MutexKind::Recursive => { MutexKind::Recursive => {

View File

@ -5,7 +5,7 @@
use std::cell::{Cell, OnceCell, RefCell}; use std::cell::{Cell, OnceCell, RefCell};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io; use std::io;
use std::io::{Error, ErrorKind, Read}; use std::io::{ErrorKind, Read};
use rustc_target::abi::Size; use rustc_target::abi::Size;
@ -138,8 +138,7 @@ impl FileDescription for AnonSocket {
// Always succeed on read size 0. // Always succeed on read size 0.
if len == 0 { if len == 0 {
let result = Ok(0); return ecx.return_read_success(ptr, &bytes, 0, dest);
return ecx.return_read_bytes_and_count(ptr, &bytes, result, dest);
} }
let Some(readbuf) = &self.readbuf else { let Some(readbuf) = &self.readbuf else {
@ -152,8 +151,7 @@ impl FileDescription for AnonSocket {
if self.peer_fd().upgrade().is_none() { if self.peer_fd().upgrade().is_none() {
// Socketpair with no peer and empty buffer. // Socketpair with no peer and empty buffer.
// 0 bytes successfully read indicates end-of-file. // 0 bytes successfully read indicates end-of-file.
let result = Ok(0); return ecx.return_read_success(ptr, &bytes, 0, dest);
return ecx.return_read_bytes_and_count(ptr, &bytes, result, dest);
} else { } else {
if self.is_nonblock { if self.is_nonblock {
// Non-blocking socketpair with writer and empty buffer. // Non-blocking socketpair with writer and empty buffer.
@ -161,8 +159,7 @@ impl FileDescription for AnonSocket {
// EAGAIN or EWOULDBLOCK can be returned for socket, // EAGAIN or EWOULDBLOCK can be returned for socket,
// POSIX.1-2001 allows either error to be returned for this case. // POSIX.1-2001 allows either error to be returned for this case.
// Since there is no ErrorKind for EAGAIN, WouldBlock is used. // Since there is no ErrorKind for EAGAIN, WouldBlock is used.
let result = Err(Error::from(ErrorKind::WouldBlock)); return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
return ecx.return_read_bytes_and_count(ptr, &bytes, result, dest);
} else { } else {
// Blocking socketpair with writer and empty buffer. // Blocking socketpair with writer and empty buffer.
// FIXME: blocking is currently not supported // FIXME: blocking is currently not supported
@ -194,8 +191,7 @@ impl FileDescription for AnonSocket {
ecx.check_and_update_readiness(&peer_fd)?; ecx.check_and_update_readiness(&peer_fd)?;
} }
let result = Ok(actual_read_size); ecx.return_read_success(ptr, &bytes, actual_read_size, dest)
ecx.return_read_bytes_and_count(ptr, &bytes, result, dest)
} }
fn write<'tcx>( fn write<'tcx>(
@ -210,16 +206,14 @@ impl FileDescription for AnonSocket {
// Always succeed on write size 0. // Always succeed on write size 0.
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.") // ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
if len == 0 { if len == 0 {
let result = Ok(0); return ecx.return_write_success(0, dest);
return ecx.return_written_byte_count_or_error(result, dest);
} }
// We are writing to our peer's readbuf. // We are writing to our peer's readbuf.
let Some(peer_fd) = self.peer_fd().upgrade() else { let Some(peer_fd) = self.peer_fd().upgrade() else {
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been // If the upgrade from Weak to Rc fails, it indicates that all read ends have been
// closed. // closed.
let result = Err(Error::from(ErrorKind::BrokenPipe)); return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest);
return ecx.return_written_byte_count_or_error(result, dest);
}; };
let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else { let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else {
@ -233,8 +227,7 @@ impl FileDescription for AnonSocket {
if available_space == 0 { if available_space == 0 {
if self.is_nonblock { if self.is_nonblock {
// Non-blocking socketpair with a full buffer. // Non-blocking socketpair with a full buffer.
let result = Err(Error::from(ErrorKind::WouldBlock)); return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
return ecx.return_written_byte_count_or_error(result, dest);
} else { } else {
// Blocking socketpair with a full buffer. // Blocking socketpair with a full buffer.
throw_unsup_format!("socketpair write: blocking isn't supported yet"); throw_unsup_format!("socketpair write: blocking isn't supported yet");
@ -256,8 +249,7 @@ impl FileDescription for AnonSocket {
// The kernel does this even if the fd was already readable before, so we follow suit. // The kernel does this even if the fd was already readable before, so we follow suit.
ecx.check_and_update_readiness(&peer_fd)?; ecx.check_and_update_readiness(&peer_fd)?;
let result = Ok(actual_write_size); ecx.return_write_success(actual_write_size, dest)
ecx.return_written_byte_count_or_error(result, dest)
} }
} }

View File

@ -150,7 +150,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?; this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(Scalar::from_u32(0)); return interp_ok(Scalar::from_u32(0));
} }
@ -163,7 +163,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_path_to_wide_str(&cwd, buf, size)?, this.write_path_to_wide_str(&cwd, buf, size)?,
))); )));
} }
Err(e) => this.set_last_error_from_io_error(e)?, Err(e) => this.set_last_error(e)?,
} }
interp_ok(Scalar::from_u32(0)) interp_ok(Scalar::from_u32(0))
} }
@ -182,7 +182,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?; this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied.into())?; this.set_last_error(ErrorKind::PermissionDenied)?;
return interp_ok(this.eval_windows("c", "FALSE")); return interp_ok(this.eval_windows("c", "FALSE"));
} }
@ -190,7 +190,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
match env::set_current_dir(path) { match env::set_current_dir(path) {
Ok(()) => interp_ok(this.eval_windows("c", "TRUE")), Ok(()) => interp_ok(this.eval_windows("c", "TRUE")),
Err(e) => { Err(e) => {
this.set_last_error_from_io_error(e)?; this.set_last_error(e)?;
interp_ok(this.eval_windows("c", "FALSE")) interp_ok(this.eval_windows("c", "FALSE"))
} }
} }

View File

@ -227,7 +227,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let filename = this.read_path_from_wide_str(filename)?; let filename = this.read_path_from_wide_str(filename)?;
let result = match win_absolute(&filename)? { let result = match win_absolute(&filename)? {
Err(err) => { Err(err) => {
this.set_last_error_from_io_error(err)?; this.set_last_error(err)?;
Scalar::from_u32(0) // return zero upon failure Scalar::from_u32(0) // return zero upon failure
} }
Ok(abs_filename) => { Ok(abs_filename) => {

View File

@ -0,0 +1,196 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use crate::*;
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn emulate_x86_gfni_intrinsic(
&mut self,
link_name: Symbol,
abi: Abi,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx, EmulateItemResult> {
let this = self.eval_context_mut();
// Prefix should have already been checked.
let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.").unwrap();
this.expect_target_feature_for_intrinsic(link_name, "gfni")?;
if unprefixed_name.ends_with(".256") {
this.expect_target_feature_for_intrinsic(link_name, "avx")?;
} else if unprefixed_name.ends_with(".512") {
this.expect_target_feature_for_intrinsic(link_name, "avx512f")?;
}
match unprefixed_name {
// Used to implement the `_mm{, 256, 512}_gf2p8affine_epi64_epi8` functions.
// See `affine_transform` for details.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8affine_
"vgf2p8affineqb.128" | "vgf2p8affineqb.256" | "vgf2p8affineqb.512" => {
let [left, right, imm8] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
affine_transform(this, left, right, imm8, dest, /* inverse */ false)?;
}
// Used to implement the `_mm{, 256, 512}_gf2p8affineinv_epi64_epi8` functions.
// See `affine_transform` for details.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8affineinv
"vgf2p8affineinvqb.128" | "vgf2p8affineinvqb.256" | "vgf2p8affineinvqb.512" => {
let [left, right, imm8] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
affine_transform(this, left, right, imm8, dest, /* inverse */ true)?;
}
// Used to implement the `_mm{, 256, 512}_gf2p8mul_epi8` functions.
// Multiplies packed 8-bit integers in `left` and `right` in the finite field GF(2^8)
// and store the results in `dst`. The field GF(2^8) is represented in
// polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1.
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul
"vgf2p8mulb.128" | "vgf2p8mulb.256" | "vgf2p8mulb.512" => {
let [left, right] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let (left, left_len) = this.project_to_simd(left)?;
let (right, right_len) = this.project_to_simd(right)?;
let (dest, dest_len) = this.project_to_simd(dest)?;
assert_eq!(left_len, right_len);
assert_eq!(dest_len, right_len);
for i in 0..dest_len {
let left = this.read_scalar(&this.project_index(&left, i)?)?.to_u8()?;
let right = this.read_scalar(&this.project_index(&right, i)?)?.to_u8()?;
let dest = this.project_index(&dest, i)?;
this.write_scalar(Scalar::from_u8(gf2p8_mul(left, right)), &dest)?;
}
}
_ => return interp_ok(EmulateItemResult::NotSupported),
}
interp_ok(EmulateItemResult::NeedsReturn)
}
}
/// Calculates the affine transformation `right * left + imm8` inside the finite field GF(2^8).
/// `right` is an 8x8 bit matrix, `left` and `imm8` are bit vectors.
/// If `inverse` is set, then the inverse transformation with respect to the reduction polynomial
/// x^8 + x^4 + x^3 + x + 1 is performed instead.
fn affine_transform<'tcx>(
this: &mut MiriInterpCx<'tcx>,
left: &OpTy<'tcx>,
right: &OpTy<'tcx>,
imm8: &OpTy<'tcx>,
dest: &MPlaceTy<'tcx>,
inverse: bool,
) -> InterpResult<'tcx, ()> {
let (left, left_len) = this.project_to_simd(left)?;
let (right, right_len) = this.project_to_simd(right)?;
let (dest, dest_len) = this.project_to_simd(dest)?;
assert_eq!(dest_len, right_len);
assert_eq!(dest_len, left_len);
let imm8 = this.read_scalar(imm8)?.to_u8()?;
// Each 8x8 bit matrix gets multiplied with eight bit vectors.
// Therefore, the iteration is done in chunks of eight.
for i in (0..dest_len).step_by(8) {
// Get the bit matrix.
let mut matrix = [0u8; 8];
for j in 0..8 {
matrix[usize::try_from(j).unwrap()] =
this.read_scalar(&this.project_index(&right, i.wrapping_add(j))?)?.to_u8()?;
}
// Multiply the matrix with the vector and perform the addition.
for j in 0..8 {
let index = i.wrapping_add(j);
let left = this.read_scalar(&this.project_index(&left, index)?)?.to_u8()?;
let left = if inverse { TABLE[usize::from(left)] } else { left };
let mut res = 0;
// Do the matrix multiplication.
for bit in 0u8..8 {
let mut b = matrix[usize::from(bit)] & left;
// Calculate the parity bit.
b = (b & 0b1111) ^ (b >> 4);
b = (b & 0b11) ^ (b >> 2);
b = (b & 0b1) ^ (b >> 1);
res |= b << 7u8.wrapping_sub(bit);
}
// Perform the addition.
res ^= imm8;
let dest = this.project_index(&dest, index)?;
this.write_scalar(Scalar::from_u8(res), &dest)?;
}
}
interp_ok(())
}
/// A lookup table for computing the inverse byte for the inverse affine transformation.
// This is a evaluated at compile time. Trait based conversion is not available.
/// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> for the
/// definition of `gf_inv` which was used for the creation of this table.
#[allow(clippy::cast_possible_truncation)]
static TABLE: [u8; 256] = {
let mut array = [0; 256];
let mut i = 1;
while i < 256 {
let mut x = i as u8;
let mut y = gf2p8_mul(x, x);
x = y;
let mut j = 2;
while j < 8 {
x = gf2p8_mul(x, x);
y = gf2p8_mul(x, y);
j += 1;
}
array[i] = y;
i += 1;
}
array
};
/// Multiplies packed 8-bit integers in `left` and `right` in the finite field GF(2^8)
/// and store the results in `dst`. The field GF(2^8) is represented in
/// polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1.
/// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> for details.
// This is a const function. Trait based conversion is not available.
#[allow(clippy::cast_possible_truncation)]
const fn gf2p8_mul(left: u8, right: u8) -> u8 {
// This implementation is based on the `gf2p8mul_byte` definition found inside the Intel intrinsics guide.
// See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul
// for more information.
const POLYNOMIAL: u32 = 0x11b;
let left = left as u32;
let right = right as u32;
let mut result = 0u32;
let mut i = 0u32;
while i < 8 {
if left & (1 << i) != 0 {
result ^= right << i;
}
i = i.wrapping_add(1);
}
let mut i = 14u32;
while i >= 8 {
if result & (1 << i) != 0 {
result ^= POLYNOMIAL << i.wrapping_sub(8);
}
i = i.wrapping_sub(1);
}
result as u8
}

View File

@ -15,6 +15,7 @@ mod aesni;
mod avx; mod avx;
mod avx2; mod avx2;
mod bmi; mod bmi;
mod gfni;
mod sha; mod sha;
mod sse; mod sse;
mod sse2; mod sse2;
@ -106,6 +107,13 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this, link_name, abi, args, dest, this, link_name, abi, args, dest,
); );
} }
// The GFNI extension does not get its own namespace.
// Check for instruction names instead.
name if name.starts_with("vgf2p8affine") || name.starts_with("vgf2p8mulb") => {
return gfni::EvalContextExt::emulate_x86_gfni_intrinsic(
this, link_name, abi, args, dest,
);
}
name if name.starts_with("sha") => { name if name.starts_with("sha") => {
return sha::EvalContextExt::emulate_x86_sha_intrinsic( return sha::EvalContextExt::emulate_x86_sha_intrinsic(
this, link_name, abi, args, dest, this, link_name, abi, args, dest,

View File

@ -1,12 +1,12 @@
//@ignore-target: windows # No pthreads on Windows //@ignore-target: windows # No pthreads on Windows
// //
// Check that if we pass NULL attribute, then we get the default mutex type. // Check that if we pass NULL attribute, then reentrant locking is UB.
fn main() { fn main() {
unsafe { unsafe {
let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, std::ptr::null() as *const _), 0); assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, std::ptr::null() as *const _), 0);
assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: Undefined Behavior: trying to acquire already locked default mutex libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: already locked by the current thread
} }
} }

View File

@ -1,13 +1,13 @@
error: Undefined Behavior: trying to acquire already locked default mutex error: Undefined Behavior: trying to acquire default mutex already locked by the current thread
--> tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.rs:LL:CC --> tests/fail-dep/concurrency/libc_pthread_mutex_NULL_reentrant.rs:LL:CC
| |
LL | libc::pthread_mutex_lock(&mut mutex as *mut _); LL | libc::pthread_mutex_lock(&mut mutex as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire already locked default mutex | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire default mutex already locked by the current thread
| |
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE: = note: BACKTRACE:
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_NULL_deadlock.rs:LL:CC = note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_NULL_reentrant.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View File

@ -1,6 +1,11 @@
//@ignore-target: windows # No pthreads on Windows //@ignore-target: windows # No pthreads on Windows
// //
// Check that if we do not set the mutex type, it is the default. // Check that if we do not set the mutex type, it is UB to do reentrant locking. glibc apparently
// actually exploits this, see
// <https://github.molgen.mpg.de/git-mirror/glibc/blob/master/nptl/pthread_mutexattr_settype.c#L31>:
// one must actively call pthread_mutexattr_settype to disable lock elision. This means a call to
// pthread_mutexattr_settype(PTHREAD_MUTEX_NORMAL) makes a difference even if
// PTHREAD_MUTEX_NORMAL == PTHREAD_MUTEX_DEFAULT!
fn main() { fn main() {
unsafe { unsafe {
@ -9,6 +14,6 @@ fn main() {
let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: Undefined Behavior: trying to acquire already locked default mutex libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: already locked by the current thread
} }
} }

View File

@ -1,13 +1,13 @@
error: Undefined Behavior: trying to acquire already locked default mutex error: Undefined Behavior: trying to acquire default mutex already locked by the current thread
--> tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.rs:LL:CC --> tests/fail-dep/concurrency/libc_pthread_mutex_default_reentrant.rs:LL:CC
| |
LL | libc::pthread_mutex_lock(&mut mutex as *mut _); LL | libc::pthread_mutex_lock(&mut mutex as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire already locked default mutex | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire default mutex already locked by the current thread
| |
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE: = note: BACKTRACE:
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_default_deadlock.rs:LL:CC = note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_default_reentrant.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View File

@ -10,6 +10,8 @@ fn main() {
let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); let mut mutex: libc::pthread_mutex_t = std::mem::zeroed();
assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0);
assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
// A "normal" mutex properly tries to acquire the lock even if its is already held
// by the current thread -- and then we deadlock.
libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: deadlock: the evaluated program deadlocked libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: deadlock: the evaluated program deadlocked
} }
} }

View File

@ -1,11 +1,11 @@
error: deadlock: the evaluated program deadlocked error: deadlock: the evaluated program deadlocked
--> tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.rs:LL:CC --> tests/fail-dep/concurrency/libc_pthread_mutex_normal_reentrant.rs:LL:CC
| |
LL | libc::pthread_mutex_lock(&mut mutex as *mut _); LL | libc::pthread_mutex_lock(&mut mutex as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program deadlocked
| |
= note: BACKTRACE: = note: BACKTRACE:
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_normal_deadlock.rs:LL:CC = note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_normal_reentrant.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View File

@ -0,0 +1,12 @@
//@ignore-target: windows # No pthreads on Windows
//
// Check that if we use PTHREAD_MUTEX_INITIALIZER, then reentrant locking is UB.
// glibc apparently actually exploits this so we better catch it!
fn main() {
unsafe {
let mut mutex: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR: already locked by the current thread
}
}

View File

@ -0,0 +1,15 @@
error: Undefined Behavior: trying to acquire default mutex already locked by the current thread
--> tests/fail-dep/concurrency/libc_pthread_mutex_staticinit_reentrant.rs:LL:CC
|
LL | libc::pthread_mutex_lock(&mut mutex as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to acquire default mutex already locked by the current thread
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at tests/fail-dep/concurrency/libc_pthread_mutex_staticinit_reentrant.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

View File

@ -4,7 +4,7 @@
// Two variants: the atomic store matches the size of the first or second atomic load. // Two variants: the atomic store matches the size of the first or second atomic load.
//@revisions: match_first_load match_second_load //@revisions: match_first_load match_second_load
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, AtomicU16, Ordering};
use std::thread; use std::thread;
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] { fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {

View File

@ -4,7 +4,7 @@
// Two revisions, depending on which access goes first. // Two revisions, depending on which access goes first.
//@revisions: read_write write_read //@revisions: read_write write_read
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, AtomicU16, Ordering};
use std::thread; use std::thread;
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] { fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {

View File

@ -0,0 +1,22 @@
error: Undefined Behavior: Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
--> tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
LL | a8[idx].store(1, Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
LL | a16.store(1, Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: overlapping unsynchronized atomic accesses must use the same access size
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
= note: inside closure at tests/fail/data_race/mixed_size_write_write.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

View File

@ -1,6 +1,7 @@
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation //@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
// Avoid accidental synchronization via address reuse inside `thread::spawn`. // Avoid accidental synchronization via address reuse inside `thread::spawn`.
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0 //@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
//@revisions: fst snd
use std::sync::atomic::{AtomicU8, AtomicU16, Ordering}; use std::sync::atomic::{AtomicU8, AtomicU16, Ordering};
use std::thread; use std::thread;
@ -21,7 +22,8 @@ fn main() {
a16.store(1, Ordering::SeqCst); a16.store(1, Ordering::SeqCst);
}); });
s.spawn(|| { s.spawn(|| {
a8[0].store(1, Ordering::SeqCst); let idx = if cfg!(fst) { 0 } else { 1 };
a8[idx].store(1, Ordering::SeqCst);
//~^ ERROR: Race condition detected between (1) 2-byte atomic store on thread `unnamed-1` and (2) 1-byte atomic store on thread `unnamed-2` //~^ ERROR: Race condition detected between (1) 2-byte atomic store on thread `unnamed-1` and (2) 1-byte atomic store on thread `unnamed-2`
}); });
}); });

View File

@ -0,0 +1,22 @@
error: Undefined Behavior: Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC+0x1. (2) just happened here
--> tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
LL | a8[idx].store(1, Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC+0x1. (2) just happened here
|
help: and (1) occurred earlier here
--> tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
LL | a16.store(1, Ordering::SeqCst);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: overlapping unsynchronized atomic accesses must use the same access size
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
= note: inside closure at tests/fail/data_race/mixed_size_write_write.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

View File

@ -1,5 +1,5 @@
//@only-target: linux //@only-target: linux
// test_epoll_block_then_unblock depends on a deterministic schedule. // test_epoll_block_then_unblock and test_epoll_race depend on a deterministic schedule.
//@compile-flags: -Zmiri-preemption-rate=0 //@compile-flags: -Zmiri-preemption-rate=0
use std::convert::TryInto; use std::convert::TryInto;
@ -12,6 +12,7 @@ fn main() {
test_epoll_block_without_notification(); test_epoll_block_without_notification();
test_epoll_block_then_unblock(); test_epoll_block_then_unblock();
test_notification_after_timeout(); test_notification_after_timeout();
test_epoll_race();
} }
// Using `as` cast since `EPOLLET` wraps around // Using `as` cast since `EPOLLET` wraps around
@ -137,3 +138,41 @@ fn test_notification_after_timeout() {
let expected_value = fds[0] as u64; let expected_value = fds[0] as u64;
check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 10); check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 10);
} }
// This test shows a data_race before epoll had vector clocks added.
fn test_epoll_race() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create an eventfd instance.
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
let fd = unsafe { libc::eventfd(0, flags) };
// Register eventfd with the epoll instance.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd as u64 };
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
assert_eq!(res, 0);
static mut VAL: u8 = 0;
let thread1 = thread::spawn(move || {
// Write to the static mut variable.
unsafe { VAL = 1 };
// Write to the eventfd instance.
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
// read returns number of bytes that have been read, which is always 8.
assert_eq!(res, 8);
});
thread::yield_now();
// epoll_wait for the event to happen.
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
let expected_value = u64::try_from(fd).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)], -1);
// Read from the static mut variable.
#[allow(static_mut_refs)]
unsafe {
assert_eq!(VAL, 1)
};
thread1.join().unwrap();
}

View File

@ -0,0 +1,518 @@
// We're testing x86 target specific features
//@only-target: x86_64 i686
//@compile-flags: -C target-feature=+gfni,+avx512f
// The constants in the tests below are just bit patterns. They should not
// be interpreted as integers; signedness does not make sense for them, but
// __mXXXi happens to be defined in terms of signed integers.
#![allow(overflowing_literals)]
#![feature(avx512_target_feature)]
#![feature(stdarch_x86_avx512)]
#[cfg(target_arch = "x86")]
use std::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use std::hint::black_box;
use std::mem::{size_of, transmute};
const IDENTITY_BYTE: i32 = 0;
const CONSTANT_BYTE: i32 = 0x63;
fn main() {
// Mostly copied from library/stdarch/crates/core_arch/src/x86/gfni.rs
assert!(is_x86_feature_detected!("avx512f"));
assert!(is_x86_feature_detected!("gfni"));
unsafe {
let byte_mul_test_data = generate_byte_mul_test_data();
let affine_mul_test_data_identity = generate_affine_mul_test_data(IDENTITY_BYTE as u8);
let affine_mul_test_data_constant = generate_affine_mul_test_data(CONSTANT_BYTE as u8);
let inv_tests_data = generate_inv_tests_data();
test_mm512_gf2p8mul_epi8(&byte_mul_test_data);
test_mm256_gf2p8mul_epi8(&byte_mul_test_data);
test_mm_gf2p8mul_epi8(&byte_mul_test_data);
test_mm512_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity);
test_mm256_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity);
test_mm_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity);
test_mm512_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant);
test_mm256_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant);
test_mm_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant);
}
}
#[target_feature(enable = "gfni,avx512f")]
unsafe fn test_mm512_gf2p8mul_epi8(
byte_mul_test_data: &([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]),
) {
let (left, right, expected) = byte_mul_test_data;
for i in 0..NUM_TEST_WORDS_512 {
let left = load_m512i_word(left, i);
let right = load_m512i_word(right, i);
let expected = load_m512i_word(expected, i);
let result = _mm512_gf2p8mul_epi8(left, right);
assert_eq_m512i(result, expected);
}
}
#[target_feature(enable = "gfni,avx")]
unsafe fn test_mm256_gf2p8mul_epi8(
byte_mul_test_data: &([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]),
) {
let (left, right, expected) = byte_mul_test_data;
for i in 0..NUM_TEST_WORDS_256 {
let left = load_m256i_word(left, i);
let right = load_m256i_word(right, i);
let expected = load_m256i_word(expected, i);
let result = _mm256_gf2p8mul_epi8(left, right);
assert_eq_m256i(result, expected);
}
}
#[target_feature(enable = "gfni")]
unsafe fn test_mm_gf2p8mul_epi8(
byte_mul_test_data: &([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]),
) {
let (left, right, expected) = byte_mul_test_data;
for i in 0..NUM_TEST_WORDS_128 {
let left = load_m128i_word(left, i);
let right = load_m128i_word(right, i);
let expected = load_m128i_word(expected, i);
let result = _mm_gf2p8mul_epi8(left, right);
assert_eq_m128i(result, expected);
}
}
#[target_feature(enable = "gfni,avx512f")]
unsafe fn test_mm512_gf2p8affine_epi64_epi8(
byte_mul_test_data: &([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]),
affine_mul_test_data_identity: &(
[u64; NUM_TEST_WORDS_64],
[u8; NUM_TEST_ENTRIES],
[u8; NUM_TEST_ENTRIES],
),
) {
let identity: i64 = 0x01_02_04_08_10_20_40_80;
let constant: i64 = 0;
let identity = _mm512_set1_epi64(identity);
let constant = _mm512_set1_epi64(constant);
let constant_reference = _mm512_set1_epi8(CONSTANT_BYTE as i8);
let (bytes, more_bytes, _) = byte_mul_test_data;
let (matrices, vectors, references) = affine_mul_test_data_identity;
for i in 0..NUM_TEST_WORDS_512 {
let data = load_m512i_word(bytes, i);
let result = _mm512_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(data, identity);
assert_eq_m512i(result, data);
let result = _mm512_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(data, constant);
assert_eq_m512i(result, constant_reference);
let data = load_m512i_word(more_bytes, i);
let result = _mm512_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(data, identity);
assert_eq_m512i(result, data);
let result = _mm512_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(data, constant);
assert_eq_m512i(result, constant_reference);
let matrix = load_m512i_word(matrices, i);
let vector = load_m512i_word(vectors, i);
let reference = load_m512i_word(references, i);
let result = _mm512_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(vector, matrix);
assert_eq_m512i(result, reference);
}
}
#[target_feature(enable = "gfni,avx")]
unsafe fn test_mm256_gf2p8affine_epi64_epi8(
byte_mul_test_data: &([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]),
affine_mul_test_data_identity: &(
[u64; NUM_TEST_WORDS_64],
[u8; NUM_TEST_ENTRIES],
[u8; NUM_TEST_ENTRIES],
),
) {
let identity: i64 = 0x01_02_04_08_10_20_40_80;
let constant: i64 = 0;
let identity = _mm256_set1_epi64x(identity);
let constant = _mm256_set1_epi64x(constant);
let constant_reference = _mm256_set1_epi8(CONSTANT_BYTE as i8);
let (bytes, more_bytes, _) = byte_mul_test_data;
let (matrices, vectors, references) = affine_mul_test_data_identity;
for i in 0..NUM_TEST_WORDS_256 {
let data = load_m256i_word(bytes, i);
let result = _mm256_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(data, identity);
assert_eq_m256i(result, data);
let result = _mm256_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(data, constant);
assert_eq_m256i(result, constant_reference);
let data = load_m256i_word(more_bytes, i);
let result = _mm256_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(data, identity);
assert_eq_m256i(result, data);
let result = _mm256_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(data, constant);
assert_eq_m256i(result, constant_reference);
let matrix = load_m256i_word(matrices, i);
let vector = load_m256i_word(vectors, i);
let reference = load_m256i_word(references, i);
let result = _mm256_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(vector, matrix);
assert_eq_m256i(result, reference);
}
}
#[target_feature(enable = "gfni")]
unsafe fn test_mm_gf2p8affine_epi64_epi8(
byte_mul_test_data: &([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]),
affine_mul_test_data_identity: &(
[u64; NUM_TEST_WORDS_64],
[u8; NUM_TEST_ENTRIES],
[u8; NUM_TEST_ENTRIES],
),
) {
let identity: i64 = 0x01_02_04_08_10_20_40_80;
let constant: i64 = 0;
let identity = _mm_set1_epi64x(identity);
let constant = _mm_set1_epi64x(constant);
let constant_reference = _mm_set1_epi8(CONSTANT_BYTE as i8);
let (bytes, more_bytes, _) = byte_mul_test_data;
let (matrices, vectors, references) = affine_mul_test_data_identity;
for i in 0..NUM_TEST_WORDS_128 {
let data = load_m128i_word(bytes, i);
let result = _mm_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(data, identity);
assert_eq_m128i(result, data);
let result = _mm_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(data, constant);
assert_eq_m128i(result, constant_reference);
let data = load_m128i_word(more_bytes, i);
let result = _mm_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(data, identity);
assert_eq_m128i(result, data);
let result = _mm_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(data, constant);
assert_eq_m128i(result, constant_reference);
let matrix = load_m128i_word(matrices, i);
let vector = load_m128i_word(vectors, i);
let reference = load_m128i_word(references, i);
let result = _mm_gf2p8affine_epi64_epi8::<IDENTITY_BYTE>(vector, matrix);
assert_eq_m128i(result, reference);
}
}
#[target_feature(enable = "gfni,avx512f")]
unsafe fn test_mm512_gf2p8affineinv_epi64_epi8(
inv_tests_data: &([u8; NUM_BYTES], [u8; NUM_BYTES]),
affine_mul_test_data_constant: &(
[u64; NUM_TEST_WORDS_64],
[u8; NUM_TEST_ENTRIES],
[u8; NUM_TEST_ENTRIES],
),
) {
let identity: i64 = 0x01_02_04_08_10_20_40_80;
let identity = _mm512_set1_epi64(identity);
// validate inversion
let (inputs, results) = inv_tests_data;
for i in 0..NUM_BYTES_WORDS_512 {
let input = load_m512i_word(inputs, i);
let reference = load_m512i_word(results, i);
let result = _mm512_gf2p8affineinv_epi64_epi8::<IDENTITY_BYTE>(input, identity);
let remultiplied = _mm512_gf2p8mul_epi8(result, input);
assert_eq_m512i(remultiplied, reference);
}
// validate subsequent affine operation
let (matrices, vectors, _affine_expected) = affine_mul_test_data_constant;
for i in 0..NUM_TEST_WORDS_512 {
let vector = load_m512i_word(vectors, i);
let matrix = load_m512i_word(matrices, i);
let inv_vec = _mm512_gf2p8affineinv_epi64_epi8::<IDENTITY_BYTE>(vector, identity);
let reference = _mm512_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(inv_vec, matrix);
let result = _mm512_gf2p8affineinv_epi64_epi8::<CONSTANT_BYTE>(vector, matrix);
assert_eq_m512i(result, reference);
}
// validate everything by virtue of checking against the AES SBox
const AES_S_BOX_MATRIX: i64 = 0xF1_E3_C7_8F_1F_3E_7C_F8;
let sbox_matrix = _mm512_set1_epi64(AES_S_BOX_MATRIX);
for i in 0..NUM_BYTES_WORDS_512 {
let reference = load_m512i_word(&AES_S_BOX, i);
let input = load_m512i_word(inputs, i);
let result = _mm512_gf2p8affineinv_epi64_epi8::<CONSTANT_BYTE>(input, sbox_matrix);
assert_eq_m512i(result, reference);
}
}
#[target_feature(enable = "gfni,avx")]
unsafe fn test_mm256_gf2p8affineinv_epi64_epi8(
inv_tests_data: &([u8; NUM_BYTES], [u8; NUM_BYTES]),
affine_mul_test_data_constant: &(
[u64; NUM_TEST_WORDS_64],
[u8; NUM_TEST_ENTRIES],
[u8; NUM_TEST_ENTRIES],
),
) {
let identity: i64 = 0x01_02_04_08_10_20_40_80;
let identity = _mm256_set1_epi64x(identity);
// validate inversion
let (inputs, results) = inv_tests_data;
for i in 0..NUM_BYTES_WORDS_256 {
let input = load_m256i_word(inputs, i);
let reference = load_m256i_word(results, i);
let result = _mm256_gf2p8affineinv_epi64_epi8::<IDENTITY_BYTE>(input, identity);
let remultiplied = _mm256_gf2p8mul_epi8(result, input);
assert_eq_m256i(remultiplied, reference);
}
// validate subsequent affine operation
let (matrices, vectors, _affine_expected) = affine_mul_test_data_constant;
for i in 0..NUM_TEST_WORDS_256 {
let vector = load_m256i_word(vectors, i);
let matrix = load_m256i_word(matrices, i);
let inv_vec = _mm256_gf2p8affineinv_epi64_epi8::<IDENTITY_BYTE>(vector, identity);
let reference = _mm256_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(inv_vec, matrix);
let result = _mm256_gf2p8affineinv_epi64_epi8::<CONSTANT_BYTE>(vector, matrix);
assert_eq_m256i(result, reference);
}
// validate everything by virtue of checking against the AES SBox
const AES_S_BOX_MATRIX: i64 = 0xF1_E3_C7_8F_1F_3E_7C_F8;
let sbox_matrix = _mm256_set1_epi64x(AES_S_BOX_MATRIX);
for i in 0..NUM_BYTES_WORDS_256 {
let reference = load_m256i_word(&AES_S_BOX, i);
let input = load_m256i_word(inputs, i);
let result = _mm256_gf2p8affineinv_epi64_epi8::<CONSTANT_BYTE>(input, sbox_matrix);
assert_eq_m256i(result, reference);
}
}
#[target_feature(enable = "gfni")]
unsafe fn test_mm_gf2p8affineinv_epi64_epi8(
inv_tests_data: &([u8; NUM_BYTES], [u8; NUM_BYTES]),
affine_mul_test_data_constant: &(
[u64; NUM_TEST_WORDS_64],
[u8; NUM_TEST_ENTRIES],
[u8; NUM_TEST_ENTRIES],
),
) {
let identity: i64 = 0x01_02_04_08_10_20_40_80;
let identity = _mm_set1_epi64x(identity);
// validate inversion
let (inputs, results) = inv_tests_data;
for i in 0..NUM_BYTES_WORDS_128 {
let input = load_m128i_word(inputs, i);
let reference = load_m128i_word(results, i);
let result = _mm_gf2p8affineinv_epi64_epi8::<IDENTITY_BYTE>(input, identity);
let remultiplied = _mm_gf2p8mul_epi8(result, input);
assert_eq_m128i(remultiplied, reference);
}
// validate subsequent affine operation
let (matrices, vectors, _affine_expected) = affine_mul_test_data_constant;
for i in 0..NUM_TEST_WORDS_128 {
let vector = load_m128i_word(vectors, i);
let matrix = load_m128i_word(matrices, i);
let inv_vec = _mm_gf2p8affineinv_epi64_epi8::<IDENTITY_BYTE>(vector, identity);
let reference = _mm_gf2p8affine_epi64_epi8::<CONSTANT_BYTE>(inv_vec, matrix);
let result = _mm_gf2p8affineinv_epi64_epi8::<CONSTANT_BYTE>(vector, matrix);
assert_eq_m128i(result, reference);
}
// validate everything by virtue of checking against the AES SBox
const AES_S_BOX_MATRIX: i64 = 0xF1_E3_C7_8F_1F_3E_7C_F8;
let sbox_matrix = _mm_set1_epi64x(AES_S_BOX_MATRIX);
for i in 0..NUM_BYTES_WORDS_128 {
let reference = load_m128i_word(&AES_S_BOX, i);
let input = load_m128i_word(inputs, i);
let result = _mm_gf2p8affineinv_epi64_epi8::<CONSTANT_BYTE>(input, sbox_matrix);
assert_eq_m128i(result, reference);
}
}
/* Various utilities for processing SIMD values. */
#[target_feature(enable = "sse2")]
unsafe fn load_m128i_word<T>(data: &[T], word_index: usize) -> __m128i {
let byte_offset = word_index * 16 / size_of::<T>();
let pointer = data.as_ptr().add(byte_offset) as *const __m128i;
_mm_loadu_si128(black_box(pointer))
}
#[target_feature(enable = "avx")]
unsafe fn load_m256i_word<T>(data: &[T], word_index: usize) -> __m256i {
let byte_offset = word_index * 32 / size_of::<T>();
let pointer = data.as_ptr().add(byte_offset) as *const __m256i;
_mm256_loadu_si256(black_box(pointer))
}
#[target_feature(enable = "avx512f")]
unsafe fn load_m512i_word<T>(data: &[T], word_index: usize) -> __m512i {
let byte_offset = word_index * 64 / size_of::<T>();
let pointer = data.as_ptr().add(byte_offset) as *const i32;
_mm512_loadu_si512(black_box(pointer))
}
#[track_caller]
#[target_feature(enable = "sse2")]
unsafe fn assert_eq_m128i(a: __m128i, b: __m128i) {
assert_eq!(transmute::<_, [u64; 2]>(a), transmute::<_, [u64; 2]>(b))
}
#[track_caller]
#[target_feature(enable = "avx")]
unsafe fn assert_eq_m256i(a: __m256i, b: __m256i) {
assert_eq!(transmute::<_, [u64; 4]>(a), transmute::<_, [u64; 4]>(b))
}
#[track_caller]
#[target_feature(enable = "avx512f")]
unsafe fn assert_eq_m512i(a: __m512i, b: __m512i) {
assert_eq!(transmute::<_, [u64; 8]>(a), transmute::<_, [u64; 8]>(b))
}
/* Software implementation of the hardware intrinsics. */
fn mulbyte(left: u8, right: u8) -> u8 {
// this implementation follows the description in
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm512_gf2p8mul_epi8
const REDUCTION_POLYNOMIAL: u16 = 0x11b;
let left: u16 = left.into();
let right: u16 = right.into();
let mut carryless_product: u16 = 0;
// Carryless multiplication
for i in 0..8 {
if ((left >> i) & 0x01) != 0 {
carryless_product ^= right << i;
}
}
// reduction, adding in "0" where appropriate to clear out high bits
// note that REDUCTION_POLYNOMIAL is zero in this context
for i in (8..=14).rev() {
if ((carryless_product >> i) & 0x01) != 0 {
carryless_product ^= REDUCTION_POLYNOMIAL << (i - 8);
}
}
carryless_product as u8
}
/// Calculates the bitwise XOR of all bits inside a byte.
fn parity(input: u8) -> u8 {
let mut accumulator = 0;
for i in 0..8 {
accumulator ^= (input >> i) & 0x01;
}
accumulator
}
/// Calculates `matrix * x + b` inside the finite field GF(2).
fn mat_vec_multiply_affine(matrix: u64, x: u8, b: u8) -> u8 {
// this implementation follows the description in
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_gf2p8affine_epi64_epi8
let mut accumulator = 0;
for bit in 0..8 {
accumulator |= parity(x & matrix.to_le_bytes()[bit]) << (7 - bit);
}
accumulator ^ b
}
/* Test data generation. */
const NUM_TEST_WORDS_512: usize = 4;
const NUM_TEST_WORDS_256: usize = NUM_TEST_WORDS_512 * 2;
const NUM_TEST_WORDS_128: usize = NUM_TEST_WORDS_256 * 2;
const NUM_TEST_ENTRIES: usize = NUM_TEST_WORDS_512 * 64;
const NUM_TEST_WORDS_64: usize = NUM_TEST_WORDS_128 * 2;
const NUM_BYTES: usize = 256;
const NUM_BYTES_WORDS_128: usize = NUM_BYTES / 16;
const NUM_BYTES_WORDS_256: usize = NUM_BYTES_WORDS_128 / 2;
const NUM_BYTES_WORDS_512: usize = NUM_BYTES_WORDS_256 / 2;
fn generate_affine_mul_test_data(
immediate: u8,
) -> ([u64; NUM_TEST_WORDS_64], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]) {
let mut left: [u64; NUM_TEST_WORDS_64] = [0; NUM_TEST_WORDS_64];
let mut right: [u8; NUM_TEST_ENTRIES] = [0; NUM_TEST_ENTRIES];
let mut result: [u8; NUM_TEST_ENTRIES] = [0; NUM_TEST_ENTRIES];
for i in 0..NUM_TEST_WORDS_64 {
left[i] = (i as u64) * 103 * 101;
for j in 0..8 {
let j64 = j as u64;
right[i * 8 + j] = ((left[i] + j64) % 256) as u8;
result[i * 8 + j] = mat_vec_multiply_affine(left[i], right[i * 8 + j], immediate);
}
}
(left, right, result)
}
fn generate_inv_tests_data() -> ([u8; NUM_BYTES], [u8; NUM_BYTES]) {
let mut input: [u8; NUM_BYTES] = [0; NUM_BYTES];
let mut result: [u8; NUM_BYTES] = [0; NUM_BYTES];
for i in 0..NUM_BYTES {
input[i] = (i % 256) as u8;
result[i] = if i == 0 { 0 } else { 1 };
}
(input, result)
}
const AES_S_BOX: [u8; NUM_BYTES] = [
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
];
fn generate_byte_mul_test_data()
-> ([u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES], [u8; NUM_TEST_ENTRIES]) {
let mut left: [u8; NUM_TEST_ENTRIES] = [0; NUM_TEST_ENTRIES];
let mut right: [u8; NUM_TEST_ENTRIES] = [0; NUM_TEST_ENTRIES];
let mut result: [u8; NUM_TEST_ENTRIES] = [0; NUM_TEST_ENTRIES];
for i in 0..NUM_TEST_ENTRIES {
left[i] = (i % 256) as u8;
right[i] = left[i].wrapping_mul(101);
result[i] = mulbyte(left[i], right[i]);
}
(left, right, result)
}