Rollup merge of #150671 - RalfJung:miri, r=RalfJung

miri subtree update

Subtree update of `miri` to 5d149f2282.

Created using https://github.com/rust-lang/josh-sync.

r? `@ghost`
This commit is contained in:
Matthias Krüger 2026-01-04 16:16:11 +01:00 committed by GitHub
commit eeb4431881
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 748 additions and 382 deletions

View file

@ -445,22 +445,21 @@ dependencies = [
[[package]]
name = "capstone"
version = "0.13.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a"
checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4"
dependencies = [
"capstone-sys",
"libc",
"static_assertions",
]
[[package]]
name = "capstone-sys"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0"
checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05"
dependencies = [
"cc",
"libc",
]
[[package]]
@ -2232,9 +2231,9 @@ dependencies = [
[[package]]
name = "libffi"
version = "5.0.0"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5"
checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b"
dependencies = [
"libc",
"libffi-sys",
@ -2242,9 +2241,9 @@ dependencies = [
[[package]]
name = "libffi-sys"
version = "4.0.0"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7"
checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb"
dependencies = [
"cc",
]

View file

@ -123,22 +123,21 @@ dependencies = [
[[package]]
name = "capstone"
version = "0.13.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a"
checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4"
dependencies = [
"capstone-sys",
"libc",
"static_assertions",
]
[[package]]
name = "capstone-sys"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0"
checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05"
dependencies = [
"cc",
"libc",
]
[[package]]
@ -786,9 +785,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libffi"
version = "5.0.0"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5"
checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b"
dependencies = [
"libc",
"libffi-sys",
@ -796,9 +795,9 @@ dependencies = [
[[package]]
name = "libffi-sys"
version = "4.0.0"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7"
checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb"
dependencies = [
"cc",
]
@ -1404,6 +1403,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.11.1"

View file

@ -32,14 +32,14 @@ serde_json = { version = "1.0", optional = true }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
# native-lib dependencies
libffi = { version = "5.0.0", optional = true }
libffi = { version = "5.1.0", optional = true }
libloading = { version = "0.9", optional = true }
serde = { version = "1.0.219", features = ["derive"], optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
nix = { version = "0.30.1", features = ["mman", "ptrace", "signal"], optional = true }
ipc-channel = { version = "0.20.0", optional = true }
capstone = { version = "0.13", optional = true }
capstone = { version = "0.14", optional = true }
[target.'cfg(all(target_os = "linux", target_pointer_width = "64", target_endian = "little"))'.dependencies]
genmc-sys = { path = "./genmc-sys/", version = "0.1.0", optional = true }
@ -68,6 +68,7 @@ expensive-consistency-checks = ["stack-cache"]
tracing = ["serde_json"]
native-lib = ["dep:libffi", "dep:libloading", "dep:capstone", "dep:ipc-channel", "dep:nix", "dep:serde"]
jemalloc = []
check_only = ["libffi?/check_only", "capstone?/check_only", "genmc-sys?/check_only"]
[lints.rust.unexpected_cfgs]
level = "warn"

View file

@ -219,7 +219,7 @@ degree documented below):
- We have unofficial support (not maintained by the Miri team itself) for some further operating systems.
- `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite.
- `freebsd`: maintained by @YohDeadfall and @LorrensP-2158466. Supports the entire test suite.
- `android`: **maintainer wanted**. Basic OS APIs and concurrency work, but file system access is not supported.
- `android`: **maintainer wanted**. Supports the entire test suite.
- For targets on other operating systems, Miri might fail before even reaching the `main` function.
However, even for targets that we do support, the degree of support for accessing platform APIs

View file

@ -30,14 +30,15 @@ export CARGO_INCREMENTAL=0
export CARGO_EXTRA_FLAGS="--locked"
# Determine configuration for installed build (used by test-cargo-miri and `./miri bench`).
# We use the default set of features for this.
echo "Installing release version of Miri"
time ./miri install
# Prepare debug build for direct `./miri` invocations.
# We enable all features to make sure the Stacked Borrows consistency check runs.
# Here we enable some more features and checks.
echo "Building debug version of Miri"
export CARGO_EXTRA_FLAGS="$CARGO_EXTRA_FLAGS --all-features"
time ./miri build # the build that all the `./miri test` below will use
export FEATURES="--features=expensive-consistency-checks,genmc"
time ./miri build $FEATURES # the build that all the `./miri test` below will use
endgroup
@ -63,7 +64,7 @@ function run_tests {
if [ -n "${GC_STRESS-}" ]; then
time MIRIFLAGS="${MIRIFLAGS-} -Zmiri-provenance-gc=1" ./miri test $TARGET_FLAG
else
time ./miri test $TARGET_FLAG
time ./miri test $FEATURES $TARGET_FLAG
fi
## advanced tests
@ -74,20 +75,20 @@ function run_tests {
# them. Also error locations change so we don't run the failing tests.
# We explicitly enable debug-assertions here, they are disabled by -O but we have tests
# which exist to check that we panic on debug assertion failures.
time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test $TARGET_FLAG tests/{pass,panic}
time MIRIFLAGS="${MIRIFLAGS-} -O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 ./miri test $FEATURES $TARGET_FLAG tests/{pass,panic}
fi
if [ -n "${MANY_SEEDS-}" ]; then
# Run many-seeds tests. (Also tests `./miri run`.)
time for FILE in tests/many-seeds/*.rs; do
./miri run "-Zmiri-many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE"
./miri run $FEATURES "-Zmiri-many-seeds=0..$MANY_SEEDS" $TARGET_FLAG "$FILE"
done
# Smoke-test `./miri run --dep`.
./miri run $FEATURES $TARGET_FLAG --dep tests/pass-dep/getrandom.rs
fi
if [ -n "${TEST_BENCH-}" ]; then
# Check that the benchmarks build and run, but only once.
time HYPERFINE="hyperfine -w0 -r1 --show-output" ./miri bench $TARGET_FLAG --no-install
fi
# Smoke-test `./miri run --dep`.
./miri run $TARGET_FLAG --dep tests/pass-dep/getrandom.rs
## test-cargo-miri
# On Windows, there is always "python", not "python3" or "python2".
@ -149,10 +150,11 @@ case $HOST_TARGET in
i686-unknown-linux-gnu)
# Host
MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Fully, but not officially, supported tier 2
MANY_SEEDS=16 TEST_TARGET=aarch64-linux-android run_tests
# Partially supported targets (tier 2)
BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator
UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap random thread sync concurrency epoll eventfd prctl
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std
;;

View file

@ -27,7 +27,6 @@ invocationStrategy = "once"
overrideCommand = [
"./miri",
"check",
"--no-default-features",
"-Zunstable-options",
"--compile-time-deps",
"--message-format=json",

View file

@ -21,7 +21,6 @@
"rust-analyzer.cargo.buildScripts.overrideCommand": [
"./miri",
"check",
"--no-default-features",
"-Zunstable-options",
"--compile-time-deps",
"--message-format=json",

View file

@ -30,7 +30,6 @@
"overrideCommand": [
"./miri",
"check",
"--no-default-features",
"-Zunstable-options",
"--compile-time-deps",
"--message-format=json"

View file

@ -13,3 +13,6 @@ cc = "1.2.16"
cmake = "0.1.54"
git2 = { version = "0.20.2", default-features = false, features = ["https"] }
cxx-build = { version = "1.0.173", features = ["parallel"] }
[features]
check_only = []

View file

@ -202,6 +202,11 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
}
fn main() {
// For check-only builds, we don't need to do anything.
if cfg!(feature = "check_only") {
return;
}
// Select which path to use for the GenMC repo:
let (genmc_path, always_configure) = if let Some(genmc_src_path) = option_env!("GENMC_SRC_PATH")
{

View file

@ -391,7 +391,8 @@ impl Command {
Ok(())
}
fn check(features: Vec<String>, flags: Vec<String>) -> Result<()> {
fn check(mut features: Vec<String>, flags: Vec<String>) -> Result<()> {
features.push("check_only".into());
let e = MiriEnv::new()?;
e.check(".", &features, &flags)?;
e.check("cargo-miri", &[], &flags)?;
@ -405,7 +406,8 @@ impl Command {
Ok(())
}
fn clippy(features: Vec<String>, flags: Vec<String>) -> Result<()> {
fn clippy(mut features: Vec<String>, flags: Vec<String>) -> Result<()> {
features.push("check_only".into());
let e = MiriEnv::new()?;
e.clippy(".", &features, &flags)?;
e.clippy("cargo-miri", &[], &flags)?;

View file

@ -1 +1 @@
7fefa09b90ca57b8a0e0e4717d672d38a0ae58b5
f57b9e6f565a1847e83a63f3e90faa3870536c1f

View file

@ -12,6 +12,7 @@ use rustc_middle::ty::TyCtxt;
pub use self::address_generator::AddressGenerator;
use self::reuse_pool::ReusePool;
use crate::alloc::MiriAllocParams;
use crate::concurrency::VClock;
use crate::diagnostics::SpanDedupDiagnostic;
use crate::*;
@ -162,18 +163,28 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.get_alloc_bytes_unchecked_raw(alloc_id)?
}
}
AllocKind::Function | AllocKind::VTable => {
// Allocate some dummy memory to get a unique address for this function/vtable.
let alloc_bytes = MiriAllocBytes::from_bytes(
&[0u8; 1],
Align::from_bytes(1).unwrap(),
params,
);
let ptr = alloc_bytes.as_ptr();
// Leak the underlying memory to ensure it remains unique.
std::mem::forget(alloc_bytes);
ptr
#[cfg(all(unix, feature = "native-lib"))]
AllocKind::Function => {
if let Some(GlobalAlloc::Function { instance, .. }) =
this.tcx.try_get_global_alloc(alloc_id)
{
let fn_sig = this.tcx.fn_sig(instance.def_id()).skip_binder().skip_binder();
let fn_ptr = crate::shims::native_lib::build_libffi_closure(this, fn_sig)?;
#[expect(
clippy::as_conversions,
reason = "No better way to cast a function ptr to a ptr"
)]
{
fn_ptr as *const _
}
} else {
dummy_alloc(params)
}
}
#[cfg(not(all(unix, feature = "native-lib")))]
AllocKind::Function => dummy_alloc(params),
AllocKind::VTable => dummy_alloc(params),
AllocKind::TypeId | AllocKind::Dead => unreachable!(),
};
// We don't have to expose this pointer yet, we do that in `prepare_for_native_call`.
@ -205,6 +216,15 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
}
fn dummy_alloc(params: MiriAllocParams) -> *const u8 {
// Allocate some dummy memory to get a unique address for this function/vtable.
let alloc_bytes = MiriAllocBytes::from_bytes(&[0u8; 1], Align::from_bytes(1).unwrap(), params);
let ptr = alloc_bytes.as_ptr();
// Leak the underlying memory to ensure it remains unique.
std::mem::forget(alloc_bytes);
ptr
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Returns the `AllocId` that corresponds to the specified addr,

View file

@ -138,7 +138,6 @@ pub enum NonHaltingDiagnostic {
NativeCallSharedMem {
tracing: bool,
},
NativeCallFnPtr,
WeakMemoryOutdatedLoad {
ptr: Pointer,
},
@ -643,11 +642,6 @@ impl<'tcx> MiriMachine<'tcx> {
Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
NativeCallSharedMem { .. } =>
("sharing memory with a native function".to_string(), DiagLevel::Warning),
NativeCallFnPtr =>
(
"sharing a function pointer with a native function".to_string(),
DiagLevel::Warning,
),
ExternTypeReborrow =>
("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
GenmcCompareExchangeWeak | GenmcCompareExchangeOrderingMismatch { .. } =>
@ -686,8 +680,6 @@ impl<'tcx> MiriMachine<'tcx> {
Int2Ptr { .. } => format!("integer-to-pointer cast"),
NativeCallSharedMem { .. } =>
format!("sharing memory with a native function called via FFI"),
NativeCallFnPtr =>
format!("sharing a function pointer with a native function called via FFI"),
WeakMemoryOutdatedLoad { ptr } =>
format!("weak memory emulation: outdated value returned from load at {ptr}"),
ExternTypeReborrow =>
@ -785,11 +777,6 @@ impl<'tcx> MiriMachine<'tcx> {
),
]
},
NativeCallFnPtr => {
vec![note!(
"calling Rust functions from C is not supported and will, in the best case, crash the program"
)]
}
ExternTypeReborrow => {
assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
matches!(

View file

@ -599,6 +599,9 @@ pub struct MiriMachine<'tcx> {
pub native_lib: Vec<(libloading::Library, std::path::PathBuf)>,
#[cfg(not(all(unix, feature = "native-lib")))]
pub native_lib: Vec<!>,
/// A memory location for exchanging the current `ecx` pointer with native code.
#[cfg(all(unix, feature = "native-lib"))]
pub native_lib_ecx_interchange: &'static Cell<usize>,
/// Run a garbage collector for BorTags every N basic blocks.
pub(crate) gc_interval: u32,
@ -790,6 +793,8 @@ impl<'tcx> MiriMachine<'tcx> {
lib_file_path.clone(),
)
}).collect(),
#[cfg(all(unix, feature = "native-lib"))]
native_lib_ecx_interchange: Box::leak(Box::new(Cell::new(0))),
#[cfg(not(all(unix, feature = "native-lib")))]
native_lib: config.native_lib.iter().map(|_| {
panic!("calling functions from native libraries via FFI is not supported in this build of Miri")
@ -1026,6 +1031,8 @@ impl VisitProvenance for MiriMachine<'_> {
report_progress: _,
basic_block_count: _,
native_lib: _,
#[cfg(all(unix, feature = "native-lib"))]
native_lib_ecx_interchange: _,
gc_interval: _,
since_gc: _,
num_cpus: _,

View file

@ -119,9 +119,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let this = self.eval_context_ref();
let index = thread.to_u32();
let target_os = &this.tcx.sess.target.os;
if matches!(target_os, Os::Linux | Os::NetBsd) {
// On Linux, the main thread has PID == TID so we uphold this. NetBSD also appears
// to exhibit the same behavior, though I can't find a citation.
if matches!(target_os, Os::Linux | Os::Android) {
// On Linux, the main thread has PID == TID so we uphold this.
this.get_pid().strict_add(index)
} else {
// Other platforms do not display any relationship between PID and TID.

View file

@ -6,7 +6,7 @@ mod backtrace;
mod files;
mod math;
#[cfg(all(unix, feature = "native-lib"))]
mod native_lib;
pub mod native_lib;
mod unix;
mod windows;
mod x86;

View file

@ -1,32 +0,0 @@
//! Support code for dealing with libffi.
use libffi::low::CodePtr;
use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType};
/// Perform the actual FFI call.
///
/// # Safety
///
/// The safety invariants of the foreign function being called must be upheld (if any).
pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &mut [OwnedArg]) -> R {
let cif = Cif::new(args.iter_mut().map(|arg| arg.ty.take().unwrap()), R::reify().into_middle());
let arg_ptrs: Vec<_> = args.iter().map(|arg| ArgPtr::new(&*arg.bytes)).collect();
// SAFETY: Caller upholds that the function is safe to call.
unsafe { cif.call(fun, &arg_ptrs) }
}
/// An argument for an FFI call.
#[derive(Debug, Clone)]
pub struct OwnedArg {
/// The type descriptor for this argument.
ty: Option<FfiType>,
/// Corresponding bytes for the value.
bytes: Box<[u8]>,
}
impl OwnedArg {
/// Instantiates an argument from a type descriptor and bytes.
pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self {
Self { ty: Some(ty), bytes }
}
}

View file

@ -1,20 +1,22 @@
//! Implements calling functions from a native library.
use std::cell::Cell;
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::c_void;
use std::ptr;
use std::sync::atomic::AtomicBool;
use libffi::low::CodePtr;
use libffi::middle::Type as FfiType;
use rustc_abi::{HasDataLayout, Size};
use rustc_data_structures::either;
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout};
use rustc_middle::ty::{self, FloatTy, IntTy, Ty, UintTy};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{self, Ty};
use rustc_span::Symbol;
use serde::{Deserialize, Serialize};
use self::helpers::ToSoft;
mod ffi;
use crate::*;
#[cfg_attr(
not(all(
@ -26,8 +28,21 @@ mod ffi;
)]
pub mod trace;
use self::ffi::OwnedArg;
use crate::*;
/// An argument for an FFI call.
#[derive(Debug, Clone)]
pub struct OwnedArg {
/// The type descriptor for this argument.
ty: Option<FfiType>,
/// Corresponding bytes for the value.
bytes: Box<[u8]>,
}
impl OwnedArg {
/// Instantiates an argument from a type descriptor and bytes.
pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self {
Self { ty: Some(ty), bytes }
}
}
/// The final results of an FFI trace, containing every relevant event detected
/// by the tracer.
@ -76,98 +91,38 @@ impl AccessRange {
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Call native host function and return the output as an immediate.
fn call_native_with_args(
/// Call native host function and return the output and the memory accesses
/// that occurred during the call.
fn call_native_raw(
&mut self,
link_name: Symbol,
dest: &MPlaceTy<'tcx>,
fun: CodePtr,
libffi_args: &mut [OwnedArg],
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
args: &mut [OwnedArg],
ret: (FfiType, Size),
) -> InterpResult<'tcx, (Box<[u8]>, Option<MemEvents>)> {
let this = self.eval_context_mut();
#[cfg(target_os = "linux")]
let alloc = this.machine.allocator.as_ref().unwrap();
let alloc = this.machine.allocator.as_ref().unwrap().clone();
#[cfg(not(target_os = "linux"))]
// Placeholder value.
let alloc = ();
trace::Supervisor::do_ffi(alloc, || {
// Call the function (`ptr`) with arguments `libffi_args`, and obtain the return value
// as the specified primitive integer type
let scalar = match dest.layout.ty.kind() {
// ints
ty::Int(IntTy::I8) => {
// Unsafe because of the call to native code.
// Because this is calling a C function it is not necessarily sound,
// but there is no way around this and we've checked as much as we can.
let x = unsafe { ffi::call::<i8>(fun, libffi_args) };
Scalar::from_i8(x)
}
ty::Int(IntTy::I16) => {
let x = unsafe { ffi::call::<i16>(fun, libffi_args) };
Scalar::from_i16(x)
}
ty::Int(IntTy::I32) => {
let x = unsafe { ffi::call::<i32>(fun, libffi_args) };
Scalar::from_i32(x)
}
ty::Int(IntTy::I64) => {
let x = unsafe { ffi::call::<i64>(fun, libffi_args) };
Scalar::from_i64(x)
}
ty::Int(IntTy::Isize) => {
let x = unsafe { ffi::call::<isize>(fun, libffi_args) };
Scalar::from_target_isize(x.try_into().unwrap(), this)
}
// uints
ty::Uint(UintTy::U8) => {
let x = unsafe { ffi::call::<u8>(fun, libffi_args) };
Scalar::from_u8(x)
}
ty::Uint(UintTy::U16) => {
let x = unsafe { ffi::call::<u16>(fun, libffi_args) };
Scalar::from_u16(x)
}
ty::Uint(UintTy::U32) => {
let x = unsafe { ffi::call::<u32>(fun, libffi_args) };
Scalar::from_u32(x)
}
ty::Uint(UintTy::U64) => {
let x = unsafe { ffi::call::<u64>(fun, libffi_args) };
Scalar::from_u64(x)
}
ty::Uint(UintTy::Usize) => {
let x = unsafe { ffi::call::<usize>(fun, libffi_args) };
Scalar::from_target_usize(x.try_into().unwrap(), this)
}
ty::Float(FloatTy::F32) => {
let x = unsafe { ffi::call::<f32>(fun, libffi_args) };
Scalar::from_f32(x.to_soft())
}
ty::Float(FloatTy::F64) => {
let x = unsafe { ffi::call::<f64>(fun, libffi_args) };
Scalar::from_f64(x.to_soft())
}
// Functions with no declared return type (i.e., the default return)
// have the output_type `Tuple([])`.
ty::Tuple(t_list) if (*t_list).deref().is_empty() => {
unsafe { ffi::call::<()>(fun, libffi_args) };
return interp_ok(ImmTy::uninit(dest.layout));
}
ty::RawPtr(ty, ..) if ty.is_sized(*this.tcx, this.typing_env()) => {
let x = unsafe { ffi::call::<*const ()>(fun, libffi_args) };
let ptr = StrictPointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
Scalar::from_pointer(ptr, this)
}
_ =>
return Err(err_unsup_format!(
"unsupported return type for native call: {:?}",
link_name
))
.into(),
};
interp_ok(ImmTy::from_scalar(scalar, dest.layout))
})
// Expose InterpCx for use by closure callbacks.
this.machine.native_lib_ecx_interchange.set(ptr::from_mut(this).expose_provenance());
let res = trace::Supervisor::do_ffi(&alloc, || {
use libffi::middle::{Arg, Cif, Ret};
let cif = Cif::new(args.iter_mut().map(|arg| arg.ty.take().unwrap()), ret.0);
let arg_ptrs: Vec<_> = args.iter().map(|arg| Arg::new(&*arg.bytes)).collect();
let mut ret = vec![0u8; ret.1.bytes_usize()];
unsafe { cif.call_return_into(fun, &arg_ptrs, Ret::new::<[u8]>(&mut *ret)) };
ret.into()
});
this.machine.native_lib_ecx_interchange.set(0);
res
}
/// Get the pointer to the function of the specified name in the shared object file,
@ -381,6 +336,30 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(OwnedArg::new(ty, bytes))
}
fn ffi_ret_to_mem(&mut self, v: Box<[u8]>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let len = v.len();
this.write_bytes_ptr(dest.ptr(), v)?;
if len == 0 {
return interp_ok(());
}
// We have no idea which provenance these bytes have, so we reset it to wildcard.
let tcx = this.tcx;
let (alloc_id, offset, _) = this.ptr_try_get_alloc_id(dest.ptr(), 0).unwrap();
let alloc = this.get_alloc_raw_mut(alloc_id)?.0;
alloc.process_native_write(&tcx, Some(alloc_range(offset, dest.layout.size)));
// Run the validation that would usually be part of `return`, also to reset
// any provenance and padding that would not survive the return.
if MiriMachine::enforce_validity(this, dest.layout) {
this.validate_operand(
&dest.clone().into(),
MiriMachine::enforce_validity_recursively(this, dest.layout),
/*reset_provenance_and_padding*/ true,
)?;
}
interp_ok(())
}
/// Parses an ADT to construct the matching libffi type.
fn adt_to_ffitype(
&self,
@ -388,6 +367,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
adt_def: ty::AdtDef<'tcx>,
args: &'tcx ty::List<ty::GenericArg<'tcx>>,
) -> InterpResult<'tcx, FfiType> {
let this = self.eval_context_ref();
// TODO: unions, etc.
if !adt_def.is_struct() {
throw_unsup_format!("passing an enum or union over FFI: {orig_ty}");
@ -397,7 +377,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
throw_unsup_format!("passing a non-#[repr(C)] {} over FFI: {orig_ty}", adt_def.descr())
}
let this = self.eval_context_ref();
let mut fields = vec![];
for field in &adt_def.non_enum_variant().fields {
let layout = this.layout_of(field.ty(*this.tcx, args))?;
@ -429,21 +408,92 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
Primitive::Float(Float::F32) => FfiType::f32(),
Primitive::Float(Float::F64) => FfiType::f64(),
Primitive::Pointer(AddressSpace::ZERO) => FfiType::pointer(),
_ =>
throw_unsup_format!(
"unsupported scalar argument type for native call: {}",
layout.ty
),
_ => throw_unsup_format!("unsupported scalar type for native call: {}", layout.ty),
});
}
interp_ok(match layout.ty.kind() {
// Scalar types have already been handled above.
ty::Adt(adt_def, args) => self.adt_to_ffitype(layout.ty, *adt_def, args)?,
_ => throw_unsup_format!("unsupported argument type for native call: {}", layout.ty),
// Rust uses `()` as return type for `void` function, which becomes `Tuple([])`.
ty::Tuple(t_list) if t_list.len() == 0 => FfiType::void(),
_ => {
throw_unsup_format!("unsupported type for native call: {}", layout.ty)
}
})
}
}
/// The data passed to the closure shim function used to intercept function pointer calls from
/// native code.
struct LibffiClosureData<'tcx> {
ecx_interchange: &'static Cell<usize>,
marker: PhantomData<MiriInterpCx<'tcx>>,
}
/// This function sets up a new libffi closure to intercept
/// calls to rust code via function pointers passed to native code.
///
/// Calling this function leaks the data passed into the libffi closure as
/// these need to be available until the execution terminates as the native
/// code side could store a function pointer and only call it at a later point.
pub fn build_libffi_closure<'tcx, 'this>(
this: &'this MiriInterpCx<'tcx>,
fn_sig: rustc_middle::ty::FnSig<'tcx>,
) -> InterpResult<'tcx, unsafe extern "C" fn()> {
// Compute argument and return types in libffi representation.
let mut args = Vec::new();
for input in fn_sig.inputs().iter() {
let layout = this.layout_of(*input)?;
let ty = this.ty_to_ffitype(layout)?;
args.push(ty);
}
let res_type = fn_sig.output();
let res_type = {
let layout = this.layout_of(res_type)?;
this.ty_to_ffitype(layout)?
};
// Build the actual closure.
let closure_builder = libffi::middle::Builder::new().args(args).res(res_type);
let data = LibffiClosureData {
ecx_interchange: this.machine.native_lib_ecx_interchange,
marker: PhantomData,
};
let data = Box::leak(Box::new(data));
let closure = closure_builder.into_closure(libffi_closure_callback, data);
let closure = Box::leak(Box::new(closure));
// The actual argument/return type doesn't matter.
let fn_ptr = unsafe { closure.instantiate_code_ptr::<unsafe extern "C" fn()>() };
// Libffi returns a **reference** to a function ptr here.
// Therefore we need to dereference the reference to get the actual function pointer.
interp_ok(*fn_ptr)
}
/// A shim function to intercept calls back from native code into the interpreter
/// via function pointers passed to the native code.
///
/// For now this shim only reports that such constructs are not supported by miri.
/// As future improvement we might continue execution in the interpreter here.
unsafe extern "C" fn libffi_closure_callback<'tcx>(
_cif: &libffi::low::ffi_cif,
_result: &mut c_void,
_args: *const *const c_void,
data: &LibffiClosureData<'tcx>,
) {
let ecx = unsafe {
ptr::with_exposed_provenance_mut::<MiriInterpCx<'tcx>>(data.ecx_interchange.get())
.as_mut()
.expect("libffi closure called while no FFI call is active")
};
let err = err_unsup_format!("calling a function pointer through the FFI boundary");
crate::diagnostics::report_result(ecx, err.into());
// We abort the execution at this point as we cannot return the
// expected value here.
std::process::exit(1);
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Call the native host function, with supplied arguments.
@ -451,6 +501,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// a native form (through `libffi` call).
/// Then, convert the return value from the native form into something that
/// can be stored in Miri's internal memory.
///
/// Returns `true` if a call has been made, `false` if no functions of this name was found.
fn call_native_fn(
&mut self,
link_name: Symbol,
@ -472,18 +524,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
for arg in args.iter() {
libffi_args.push(this.op_to_ffi_arg(arg, tracing)?);
}
let ret_ty = this.ty_to_ffitype(dest.layout)?;
// Prepare all exposed memory (both previously exposed, and just newly exposed since a
// pointer was passed as argument). Uninitialised memory is left as-is, but any data
// exposed this way is garbage anyway.
this.visit_reachable_allocs(this.exposed_allocs(), |this, alloc_id, info| {
if matches!(info.kind, AllocKind::Function) {
static DEDUP: AtomicBool = AtomicBool::new(false);
if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
// Newly set, so first time we get here.
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallFnPtr);
}
}
// If there is no data behind this pointer, skip this.
if !matches!(info.kind, AllocKind::LiveData) {
return interp_ok(());
@ -521,15 +567,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(())
})?;
// Call the function and store output, depending on return type in the function signature.
// Call the function and store its output.
let (ret, maybe_memevents) =
this.call_native_with_args(link_name, dest, code_ptr, &mut libffi_args)?;
this.call_native_raw(code_ptr, &mut libffi_args, (ret_ty, dest.layout.size))?;
if tracing {
this.tracing_apply_accesses(maybe_memevents.unwrap())?;
}
this.write_immediate(*ret, dest)?;
this.ffi_ret_to_mem(ret, dest)?;
interp_ok(true)
}
}

View file

@ -5,7 +5,7 @@ use std::rc::Rc;
use ipc_channel::ipc;
use nix::sys::{mman, ptrace, signal};
use nix::unistd;
use rustc_const_eval::interpret::InterpResult;
use rustc_const_eval::interpret::{InterpResult, interp_ok};
use super::CALLBACK_STACK_SIZE;
use super::messages::{Confirmation, StartFfiInfo, TraceRequest};
@ -58,16 +58,16 @@ impl Supervisor {
/// Performs an arbitrary FFI call, enabling tracing from the supervisor.
/// As this locks the supervisor via a mutex, no other threads may enter FFI
/// until this function returns.
pub fn do_ffi<'tcx>(
pub fn do_ffi<'tcx, T>(
alloc: &Rc<RefCell<IsolatedAlloc>>,
f: impl FnOnce() -> InterpResult<'tcx, crate::ImmTy<'tcx>>,
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
f: impl FnOnce() -> T,
) -> InterpResult<'tcx, (T, Option<MemEvents>)> {
let mut sv_guard = SUPERVISOR.lock().unwrap();
// If the supervisor is not initialised for whatever reason, fast-return.
// As a side-effect, even on platforms where ptracing
// is not implemented, we enforce that only one FFI call
// happens at a time.
let Some(sv) = sv_guard.as_mut() else { return f().map(|v| (v, None)) };
let Some(sv) = sv_guard.as_mut() else { return interp_ok((f(), None)) };
// Get pointers to all the pages the supervisor must allow accesses in
// and prepare the callback stack.
@ -147,7 +147,7 @@ impl Supervisor {
})
.ok();
res.map(|v| (v, events))
interp_ok((res, events))
}
}

View file

@ -1,4 +1,4 @@
use rustc_const_eval::interpret::InterpResult;
use rustc_const_eval::interpret::{InterpResult, interp_ok};
static SUPERVISOR: std::sync::Mutex<()> = std::sync::Mutex::new(());
@ -13,13 +13,13 @@ impl Supervisor {
false
}
pub fn do_ffi<'tcx, T>(
pub fn do_ffi<'tcx, T, U>(
_: T,
f: impl FnOnce() -> InterpResult<'tcx, crate::ImmTy<'tcx>>,
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<super::MemEvents>)> {
f: impl FnOnce() -> U,
) -> InterpResult<'tcx, (U, Option<super::MemEvents>)> {
// We acquire the lock to ensure that no two FFI calls run concurrently.
let _g = SUPERVISOR.lock().unwrap();
f().map(|v| (v, None))
interp_ok((f(), None))
}
}

View file

@ -8,6 +8,7 @@ use crate::shims::unix::env::EvalContextExt as _;
use crate::shims::unix::linux_like::epoll::EvalContextExt as _;
use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
use crate::shims::unix::linux_like::syscall::syscall;
use crate::shims::unix::*;
use crate::*;
pub fn is_dyn_sym(name: &str) -> bool {
@ -25,6 +26,74 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
) -> InterpResult<'tcx, EmulateItemResult> {
let this = self.eval_context_mut();
match link_name.as_str() {
// File related shims
"stat" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.stat(path, buf)?;
this.write_scalar(result, dest)?;
}
"lstat" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.lstat(path, buf)?;
this.write_scalar(result, dest)?;
}
"readdir" => {
let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.readdir64("dirent", dirp)?;
this.write_scalar(result, dest)?;
}
"pread64" => {
let [fd, buf, count, offset] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off64_t) -> isize),
link_name,
abi,
args,
)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let buf = this.read_pointer(buf)?;
let count = this.read_target_usize(count)?;
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
this.read(fd, buf, count, Some(offset), dest)?;
}
"pwrite64" => {
let [fd, buf, n, offset] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *const _, usize, libc::off64_t) -> isize),
link_name,
abi,
args,
)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let buf = this.read_pointer(buf)?;
let count = this.read_target_usize(n)?;
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
this.write(fd, buf, count, Some(offset), dest)?;
}
"lseek64" => {
let [fd, offset, whence] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, libc::off64_t, i32) -> libc::off64_t),
link_name,
abi,
args,
)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
let whence = this.read_scalar(whence)?.to_i32()?;
this.lseek64(fd, offset, whence, dest)?;
}
"ftruncate64" => {
let [fd, length] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, libc::off64_t) -> i32),
link_name,
abi,
args,
)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let length = this.read_scalar(length)?.to_int(length.layout.size)?;
let result = this.ftruncate64(fd, length)?;
this.write_scalar(result, dest)?;
}
// epoll, eventfd
"epoll_create1" => {
let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;

View file

@ -510,7 +510,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
"pipe2" => {
// Currently this function does not exist on all Unixes, e.g. on macOS.
this.check_target_os(
&[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos],
&[Os::Linux, Os::Android, Os::FreeBsd, Os::Solaris, Os::Illumos],
link_name,
)?;
let [pipefd, flags] = this.check_shim_sig(

View file

@ -140,12 +140,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// since freebsd 12 the former form can be expected.
"stat" | "stat@FBSD_1.0" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.macos_fbsd_solarish_stat(path, buf)?;
let result = this.stat(path, buf)?;
this.write_scalar(result, dest)?;
}
"lstat" | "lstat@FBSD_1.0" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.macos_fbsd_solarish_lstat(path, buf)?;
let result = this.lstat(path, buf)?;
this.write_scalar(result, dest)?;
}
"fstat@FBSD_1.0" => {

View file

@ -527,15 +527,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
}
fn macos_fbsd_solarish_stat(
&mut self,
path_op: &OpTy<'tcx>,
buf_op: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
{
if !matches!(
&this.tcx.sess.target.os,
Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
) {
panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
}
@ -558,15 +556,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
// `lstat` is used to get symlink metadata.
fn macos_fbsd_solarish_lstat(
&mut self,
path_op: &OpTy<'tcx>,
buf_op: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
fn lstat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
{
if !matches!(
&this.tcx.sess.target.os,
Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android
) {
panic!(
"`macos_fbsd_solaris_lstat` should not be called on {}",
this.tcx.sess.target.os
@ -595,7 +591,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if !matches!(
&this.tcx.sess.target.os,
Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux
Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android
) {
panic!("`fstat` should not be called on {}", this.tcx.sess.target.os);
}
@ -906,9 +902,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
if !matches!(&this.tcx.sess.target.os, Os::Linux | Os::Solaris | Os::Illumos | Os::FreeBsd)
{
panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
if !matches!(
&this.tcx.sess.target.os,
Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd
) {
panic!("`readdir64` should not be called on {}", this.tcx.sess.target.os);
}
let dirp = this.read_target_usize(dirp_op)?;

View file

@ -48,12 +48,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
"stat" | "stat$INODE64" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.macos_fbsd_solarish_stat(path, buf)?;
let result = this.stat(path, buf)?;
this.write_scalar(result, dest)?;
}
"lstat" | "lstat$INODE64" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.macos_fbsd_solarish_lstat(path, buf)?;
let result = this.lstat(path, buf)?;
this.write_scalar(result, dest)?;
}
"fstat$INODE64" => {

View file

@ -92,12 +92,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// File related shims
"stat" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.macos_fbsd_solarish_stat(path, buf)?;
let result = this.stat(path, buf)?;
this.write_scalar(result, dest)?;
}
"lstat" => {
let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let result = this.macos_fbsd_solarish_lstat(path, buf)?;
let result = this.lstat(path, buf)?;
this.write_scalar(result, dest)?;
}
"readdir" => {

View file

@ -459,7 +459,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
// if there is anything left at the end, that's an unsupported flag.
if this.tcx.sess.target.os == Os::Linux {
if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
// SOCK_NONBLOCK only exists on Linux.
let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");

View file

@ -109,8 +109,66 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
pshufb(this, left, right, dest)?;
}
// Used to implement the _mm512_dpbusd_epi32 function.
"vpdpbusd.512" | "vpdpbusd.256" | "vpdpbusd.128" => {
this.expect_target_feature_for_intrinsic(link_name, "avx512vnni")?;
if matches!(unprefixed_name, "vpdpbusd.128" | "vpdpbusd.256") {
this.expect_target_feature_for_intrinsic(link_name, "avx512vl")?;
}
let [src, a, b] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
vpdpbusd(this, src, a, b, dest)?;
}
_ => return interp_ok(EmulateItemResult::NotSupported),
}
interp_ok(EmulateItemResult::NeedsReturn)
}
}
/// Multiply groups of 4 adjacent pairs of unsigned 8-bit integers in `a` with corresponding signed
/// 8-bit integers in `b`, producing 4 intermediate signed 16-bit results. Sum these 4 results with
/// the corresponding 32-bit integer in `src` (using wrapping arighmetic), and store the packed
/// 32-bit results in `dst`.
///
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_dpbusd_epi32>
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_dpbusd_epi32>
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm512_dpbusd_epi32>
fn vpdpbusd<'tcx>(
ecx: &mut crate::MiriInterpCx<'tcx>,
src: &OpTy<'tcx>,
a: &OpTy<'tcx>,
b: &OpTy<'tcx>,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx, ()> {
let (src, src_len) = ecx.project_to_simd(src)?;
let (a, a_len) = ecx.project_to_simd(a)?;
let (b, b_len) = ecx.project_to_simd(b)?;
let (dest, dest_len) = ecx.project_to_simd(dest)?;
// fn vpdpbusd(src: i32x16, a: i32x16, b: i32x16) -> i32x16;
// fn vpdpbusd256(src: i32x8, a: i32x8, b: i32x8) -> i32x8;
// fn vpdpbusd128(src: i32x4, a: i32x4, b: i32x4) -> i32x4;
assert_eq!(dest_len, src_len);
assert_eq!(dest_len, a_len);
assert_eq!(dest_len, b_len);
for i in 0..dest_len {
let src = ecx.read_scalar(&ecx.project_index(&src, i)?)?.to_i32()?;
let a = ecx.read_scalar(&ecx.project_index(&a, i)?)?.to_u32()?;
let b = ecx.read_scalar(&ecx.project_index(&b, i)?)?.to_u32()?;
let dest = ecx.project_index(&dest, i)?;
let zipped = a.to_le_bytes().into_iter().zip(b.to_le_bytes());
let intermediate_sum: i32 = zipped
.map(|(a, b)| i32::from(a).strict_mul(i32::from(b.cast_signed())))
.fold(0, |x, y| x.strict_add(y));
// Use `wrapping_add` because `src` is an arbitrary i32 and the addition can overflow.
let res = Scalar::from_i32(intermediate_sum.wrapping_add(src));
ecx.write_scalar(res, &dest)?;
}
interp_ok(())
}

View file

@ -24,6 +24,14 @@ EXPORT int64_t pass_struct(const PassMe pass_me) {
return pass_me.value + pass_me.other_value;
}
/* Test: test_return_struct */
EXPORT PassMe return_struct(int32_t value, int64_t other_value) {
struct PassMe ret;
ret.value = value;
ret.other_value = other_value;
return ret;
}
/* Test: test_pass_struct_complex */
typedef struct Part1 {

View file

@ -0,0 +1,31 @@
warning: sharing memory with a native function called via FFI
--> tests/native-lib/fail/call_function_ptr.rs:LL:CC
|
LL | call_fn_ptr(Some(nop));
| ^^^^^^^^^^^^^^^^^^^^^^ sharing memory with a native function
|
= help: when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory
= help: in particular, Miri assumes that the native call initializes all memory it has access to
= help: Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory
= help: what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free
= note: stack backtrace:
0: pass_fn_ptr
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
1: main
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
error: unsupported operation: calling a function pointer through the FFI boundary
--> tests/native-lib/fail/call_function_ptr.rs:LL:CC
|
LL | call_fn_ptr(Some(nop));
| ^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
= note: stack backtrace:
0: pass_fn_ptr
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
1: main
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View file

@ -0,0 +1,21 @@
//@revisions: trace notrace
//@[trace] only-target: x86_64-unknown-linux-gnu i686-unknown-linux-gnu
//@[trace] compile-flags: -Zmiri-native-lib-enable-tracing
//@compile-flags: -Zmiri-permissive-provenance
fn main() {
pass_fn_ptr()
}
fn pass_fn_ptr() {
extern "C" {
fn call_fn_ptr(s: Option<extern "C" fn()>);
}
extern "C" fn nop() {}
unsafe {
call_fn_ptr(None); // this one is fine
call_fn_ptr(Some(nop)); //~ ERROR: unsupported operation: calling a function pointer through the FFI boundary
}
}

View file

@ -0,0 +1,32 @@
warning: sharing memory with a native function called via FFI
--> tests/native-lib/fail/call_function_ptr.rs:LL:CC
|
LL | call_fn_ptr(Some(nop));
| ^^^^^^^^^^^^^^^^^^^^^^ sharing memory with a native function
|
= help: when memory is shared with a native function call, Miri can only track initialisation and provenance on a best-effort basis
= help: in particular, Miri assumes that the native call initializes all memory it has written to
= help: Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory
= help: what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free
= help: tracing memory accesses in native code is not yet fully implemented, so there can be further imprecisions beyond what is documented here
= note: stack backtrace:
0: pass_fn_ptr
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
1: main
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
error: unsupported operation: calling a function pointer through the FFI boundary
--> tests/native-lib/fail/call_function_ptr.rs:LL:CC
|
LL | call_fn_ptr(Some(nop));
| ^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
= note: stack backtrace:
0: pass_fn_ptr
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
1: main
at tests/native-lib/fail/call_function_ptr.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View file

@ -0,0 +1,14 @@
// Only works on Unix targets
//@ignore-target: windows wasm
//@only-on-host
//@normalize-stderr-test: "OS `.*`" -> "$$OS"
extern "C" {
fn u8_id(x: u8) -> bool;
}
fn main() {
unsafe {
u8_id(2); //~ ERROR: invalid value: encountered 0x02, but expected a boolean
}
}

View file

@ -0,0 +1,13 @@
error: Undefined Behavior: constructing invalid value: encountered 0x02, but expected a boolean
--> tests/native-lib/fail/invalid_retval.rs:LL:CC
|
LL | u8_id(2);
| ^^^^^^^^ Undefined Behavior occurred here
|
= 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: 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 @@
fn main() {
test_pass_struct();
test_pass_struct_complex();
test_return_struct();
}
/// Test passing a basic struct as an argument.
@ -20,6 +21,23 @@ fn test_pass_struct() {
assert_eq!(unsafe { pass_struct(pass_me) }, 42 + 1337);
}
fn test_return_struct() {
// Exactly two fields, so that we hit the ScalarPair case.
#[repr(C)]
struct PassMe {
value: i32,
other_value: i64,
}
extern "C" {
fn return_struct(v: i32, ov: i64) -> PassMe;
}
let pass_me = unsafe { return_struct(1, 2) };
assert_eq!(pass_me.value, 1);
assert_eq!(pass_me.other_value, 2);
}
/// Test passing a more complex struct as an argument.
fn test_pass_struct_complex() {
#[repr(C)]

View file

@ -14,16 +14,3 @@ LL | unsafe { print_pointer(&x) };
1: main
at tests/native-lib/pass/ptr_read_access.rs:LL:CC
warning: sharing a function pointer with a native function called via FFI
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
|
LL | pass_fn_ptr(Some(nop)); // this one is not
| ^^^^^^^^^^^^^^^^^^^^^^ sharing a function pointer with a native function
|
= help: calling Rust functions from C is not supported and will, in the best case, crash the program
= note: stack backtrace:
0: pass_fn_ptr
at tests/native-lib/pass/ptr_read_access.rs:LL:CC
1: main
at tests/native-lib/pass/ptr_read_access.rs:LL:CC

View file

@ -15,16 +15,3 @@ LL | unsafe { print_pointer(&x) };
1: main
at tests/native-lib/pass/ptr_read_access.rs:LL:CC
warning: sharing a function pointer with a native function called via FFI
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
|
LL | pass_fn_ptr(Some(nop)); // this one is not
| ^^^^^^^^^^^^^^^^^^^^^^ sharing a function pointer with a native function
|
= help: calling Rust functions from C is not supported and will, in the best case, crash the program
= note: stack backtrace:
0: pass_fn_ptr
at tests/native-lib/pass/ptr_read_access.rs:LL:CC
1: main
at tests/native-lib/pass/ptr_read_access.rs:LL:CC

View file

@ -68,3 +68,10 @@ EXPORT uintptr_t do_one_deref(const int32_t ***ptr) {
EXPORT void pass_fn_ptr(void f(void)) {
(void)f; // suppress unused warning
}
/* Test: function_ptrs */
EXPORT void call_fn_ptr(void f(void)) {
if (f != NULL) {
f();
}
}

View file

@ -34,6 +34,10 @@ EXPORT float add_float(float x) {
return x + 1.5f;
}
EXPORT uint8_t u8_id(uint8_t x) {
return x;
}
// To test that functions not marked with EXPORT cannot be called by Miri.
int32_t not_exported(void) {
return 0;

View file

@ -1,5 +1,6 @@
//@ignore-target: windows # File handling is not implemented yet
//@ignore-target: solaris # Does not have flock
//@ignore-target: android # Does not (always?) have flock
//@compile-flags: -Zmiri-disable-isolation
use std::fs::File;

View file

@ -5,6 +5,7 @@ use std::thread;
#[path = "../../utils/libc.rs"]
mod libc_utils;
use libc_utils::*;
fn main() {
test_pipe();
@ -13,6 +14,7 @@ fn main() {
test_pipe_array();
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "illumos",
target_os = "freebsd",
target_os = "solaris"
@ -25,69 +27,44 @@ fn main() {
fn test_pipe() {
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) });
// Read size == data available in buffer.
let data = "12345".as_bytes().as_ptr();
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
let mut buf3: [u8; 5] = [0; 5];
let res = unsafe {
libc_utils::read_all(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t)
};
assert_eq!(res, 5);
assert_eq!(buf3, "12345".as_bytes());
let data = b"12345";
write_all_from_slice(fds[1], data).unwrap();
let buf3 = read_all_into_array::<5>(fds[0]).unwrap();
assert_eq!(&buf3, data);
// Read size > data available in buffer.
let data = "123".as_bytes();
let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 3) };
assert_eq!(res, 3);
let data = b"123";
write_all_from_slice(fds[1], data).unwrap();
let mut buf4: [u8; 5] = [0; 5];
let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
assert!(res > 0 && res <= 3);
let res = res as usize;
assert_eq!(buf4[..res], data[..res]);
if res < 3 {
// Drain the rest from the read end.
let res = unsafe { libc_utils::read_all(fds[0], buf4[res..].as_mut_ptr().cast(), 3 - res) };
assert!(res > 0);
}
let (part1, rest) = read_into_slice(fds[0], &mut buf4).unwrap();
assert_eq!(part1[..], data[..part1.len()]);
// Write 2 more bytes so we can exactly fill the `rest`.
write_all_from_slice(fds[1], b"34").unwrap();
read_all_into_slice(fds[0], rest).unwrap();
}
fn test_pipe_threaded() {
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) });
let thread1 = thread::spawn(move || {
let mut buf: [u8; 5] = [0; 5];
let res: i64 = unsafe {
libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
.try_into()
.unwrap()
};
assert_eq!(res, 5);
assert_eq!(buf, "abcde".as_bytes());
let buf = read_all_into_array::<5>(fds[0]).unwrap();
assert_eq!(&buf, b"abcde");
});
thread::yield_now();
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
write_all_from_slice(fds[1], b"abcde").unwrap();
thread1.join().unwrap();
// Read and write from different direction
let thread2 = thread::spawn(move || {
thread::yield_now();
let data = "12345".as_bytes().as_ptr();
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
write_all_from_slice(fds[1], b"12345").unwrap();
});
let mut buf: [u8; 5] = [0; 5];
let res =
unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
assert_eq!(res, 5);
assert_eq!(buf, "12345".as_bytes());
let buf = read_all_into_array::<5>(fds[0]).unwrap();
assert_eq!(&buf, b"12345");
thread2.join().unwrap();
}
@ -96,26 +73,17 @@ fn test_pipe_threaded() {
fn test_race() {
static mut VAL: u8 = 0;
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) });
let thread1 = thread::spawn(move || {
let mut buf: [u8; 1] = [0; 1];
// write() from the main thread will occur before the read() here
// because preemption is disabled and the main thread yields after write().
let res: i32 = unsafe {
libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
.try_into()
.unwrap()
};
assert_eq!(res, 1);
assert_eq!(buf, "a".as_bytes());
let buf = read_all_into_array::<1>(fds[0]).unwrap();
assert_eq!(&buf, b"a");
// The read above establishes a happens-before so it is now safe to access this global variable.
unsafe { assert_eq!(VAL, 1) };
});
unsafe { VAL = 1 };
let data = "a".as_bytes().as_ptr();
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 1) };
assert_eq!(res, 1);
write_all_from_slice(fds[1], b"a").unwrap();
thread::yield_now();
thread1.join().unwrap();
}
@ -133,46 +101,53 @@ fn test_pipe_array() {
/// Test if pipe2 (including the O_NONBLOCK flag) is supported.
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "illumos",
target_os = "freebsd",
target_os = "solaris"
))]
fn test_pipe2() {
let mut fds = [-1, -1];
let res = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) };
assert_eq!(res, 0);
errno_check(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) });
}
/// Basic test for pipe fcntl's F_SETFL and F_GETFL flag.
fn test_pipe_setfl_getfl() {
// Initialise pipe fds.
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) });
// Both sides should either have O_RONLY or O_WRONLY.
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
assert_eq!(res, libc::O_RDONLY);
let res = unsafe { libc::fcntl(fds[1], libc::F_GETFL) };
assert_eq!(res, libc::O_WRONLY);
assert_eq!(
errno_result(unsafe { libc::fcntl(fds[0], libc::F_GETFL) }).unwrap(),
libc::O_RDONLY
);
assert_eq!(
errno_result(unsafe { libc::fcntl(fds[1], libc::F_GETFL) }).unwrap(),
libc::O_WRONLY
);
// Add the O_NONBLOCK flag with F_SETFL.
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) };
assert_eq!(res, 0);
errno_check(unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) });
// Test if the O_NONBLOCK flag is successfully added.
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
assert_eq!(res, libc::O_RDONLY | libc::O_NONBLOCK);
assert_eq!(
errno_result(unsafe { libc::fcntl(fds[0], libc::F_GETFL) }).unwrap(),
libc::O_RDONLY | libc::O_NONBLOCK
);
// The other side remains unchanged.
let res = unsafe { libc::fcntl(fds[1], libc::F_GETFL) };
assert_eq!(res, libc::O_WRONLY);
assert_eq!(
errno_result(unsafe { libc::fcntl(fds[1], libc::F_GETFL) }).unwrap(),
libc::O_WRONLY
);
// Test if O_NONBLOCK flag can be unset.
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, 0) };
assert_eq!(res, 0);
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
assert_eq!(res, libc::O_RDONLY);
errno_check(unsafe { libc::fcntl(fds[0], libc::F_SETFL, 0) });
assert_eq!(
errno_result(unsafe { libc::fcntl(fds[0], libc::F_GETFL) }).unwrap(),
libc::O_RDONLY
);
}
/// Test the behaviour of F_SETFL/F_GETFL when a fd is blocking.
@ -183,28 +158,24 @@ fn test_pipe_setfl_getfl() {
/// then writes to fds[1] to unblock main thread's `read`.
fn test_pipe_fcntl_threaded() {
let mut fds = [-1, -1];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
assert_eq!(res, 0);
let mut buf: [u8; 5] = [0; 5];
errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) });
let thread1 = thread::spawn(move || {
// Add O_NONBLOCK flag while pipe is still blocked on read.
let res = unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) };
assert_eq!(res, 0);
errno_check(unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) });
// Check the new flag value while the main thread is still blocked on fds[0].
let res = unsafe { libc::fcntl(fds[0], libc::F_GETFL) };
assert_eq!(res, libc::O_NONBLOCK);
assert_eq!(
errno_result(unsafe { libc::fcntl(fds[0], libc::F_GETFL) }).unwrap(),
libc::O_NONBLOCK
);
// The write below will unblock the `read` in main thread: even though
// the socket is now "non-blocking", the shim needs to deal correctly
// with threads that were blocked before the socket was made non-blocking.
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
write_all_from_slice(fds[1], b"abcde").unwrap();
});
// The `read` below will block.
let res =
unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
let buf = read_all_into_array::<5>(fds[0]).unwrap();
thread1.join().unwrap();
assert_eq!(res, 5);
assert_eq!(&buf, b"abcde");
}

View file

@ -165,7 +165,7 @@ fn main() {
// The value is not important, we only care that whatever the value is,
// won't change from execution to execution.
if cfg!(with_isolation) {
if cfg!(target_os = "linux") {
if cfg!(any(target_os = "linux", target_os = "android")) {
// Linux starts the TID at the PID, which is 1000.
assert_eq!(tid, 1000);
} else {
@ -174,8 +174,8 @@ fn main() {
}
}
// On Linux and NetBSD, the first TID is the PID.
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
// On Linux, the first TID is the PID.
#[cfg(any(target_os = "linux", target_os = "android"))]
assert_eq!(tid, unsafe { libc::getpid() } as u64);
#[cfg(any(target_vendor = "apple", windows))]

View file

@ -7,15 +7,8 @@ mod utils;
/// Test that the [`tempfile`] crate is compatible with miri for UNIX hosts and targets
fn main() {
test_tempfile();
test_tempfile_in();
}
fn test_tempfile() {
tempfile::tempfile().unwrap();
}
fn test_tempfile_in() {
// Only create a file in our own tmp folder; the "host" temp folder
// can be nonsensical for cross-tests.
let dir_path = utils::tmp();
tempfile::tempfile_in(dir_path).unwrap();
}

View file

@ -37,7 +37,7 @@ fn main() {
test_canonicalize();
#[cfg(unix)]
test_pread_pwrite();
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
#[cfg(not(any(target_os = "solaris", target_os = "android")))]
test_flock();
}
}
@ -399,8 +399,8 @@ fn test_pread_pwrite() {
assert_eq!(&buf1, b" m");
}
// This function does seem to exist on Illumos but std does not expose it there.
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
// The standard library does not support this operation on Solaris, Android
#[cfg(not(any(target_os = "solaris", target_os = "android")))]
fn test_flock() {
let bytes = b"Hello, World!\n";
let path = utils::prepare_with_content("miri_test_fs_flock.txt", bytes);

View file

@ -1,6 +1,6 @@
// We're testing x86 target specific features
//@only-target: x86_64 i686
//@compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bitalg,+avx512vpopcntdq
//@compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bitalg,+avx512vpopcntdq,+avx512vnni
#[cfg(target_arch = "x86")]
use std::arch::x86::*;
@ -13,12 +13,14 @@ fn main() {
assert!(is_x86_feature_detected!("avx512vl"));
assert!(is_x86_feature_detected!("avx512bitalg"));
assert!(is_x86_feature_detected!("avx512vpopcntdq"));
assert!(is_x86_feature_detected!("avx512vnni"));
unsafe {
test_avx512();
test_avx512bitalg();
test_avx512vpopcntdq();
test_avx512ternarylogic();
test_avx512vnni();
}
}
@ -411,6 +413,101 @@ unsafe fn test_avx512ternarylogic() {
test_mm_ternarylogic_epi32();
}
#[target_feature(enable = "avx512vnni")]
unsafe fn test_avx512vnni() {
#[target_feature(enable = "avx512vnni")]
unsafe fn test_mm512_dpbusd_epi32() {
const SRC: [i32; 16] = [
1,
// Test that addition with the `src` element uses wrapping arithmetic.
i32::MAX,
i32::MIN,
0,
0,
7,
12345,
-9876,
0x01020304,
-1,
42,
0,
1_000_000_000,
-1_000_000_000,
17,
-17,
];
// The `A` array must be interpreted as a sequence of unsigned 8-bit integers. Setting
// the high bit of a byte tests that this is implemented correctly.
const A: [i32; 16] = [
0x01010101,
i32::from_le_bytes([1; 4]),
i32::from_le_bytes([1; 4]),
i32::from_le_bytes([u8::MAX; 4]),
i32::from_le_bytes([u8::MAX; 4]),
0x02_80_01_FF,
0x00_FF_00_FF,
0x7F_80_FF_01,
0x10_20_30_40,
0xDE_AD_BE_EFu32 as i32,
0x00_00_00_FF,
0x12_34_56_78,
0xFF_00_FF_00u32 as i32,
0x01_02_03_04,
0xAA_55_AA_55u32 as i32,
0x11_22_33_44,
];
// The `B` array must be interpreted as a sequence of signed 8-bit integers. Setting
// the high bit of a byte tests that this is implemented correctly.
const B: [i32; 16] = [
0x01010101,
i32::from_le_bytes([1; 4]),
i32::from_le_bytes([(-1i8).cast_unsigned(); 4]),
i32::from_le_bytes([i8::MAX.cast_unsigned(); 4]),
i32::from_le_bytes([i8::MIN.cast_unsigned(); 4]),
0xFF_01_80_7Fu32 as i32,
0x01_FF_01_FF,
0x80_7F_00_FFu32 as i32,
0x7F_01_FF_80u32 as i32,
0x01_02_03_04,
0xFF_FF_FF_FFu32 as i32,
0x80_00_7F_FFu32 as i32,
0x7F_80_7F_80u32 as i32,
0x40_C0_20_E0u32 as i32,
0x00_01_02_03,
0x7F_7E_80_81u32 as i32,
];
const DST: [i32; 16] = [
5,
i32::MAX.wrapping_add(4),
i32::MIN.wrapping_add(-4),
129540,
-130560,
32390,
11835,
-9877,
16902884,
2093,
-213,
8498,
1000064770,
-1000000096,
697,
-8738,
];
let src = _mm512_loadu_si512(SRC.as_ptr().cast::<__m512i>());
let a = _mm512_loadu_si512(A.as_ptr().cast::<__m512i>());
let b = _mm512_loadu_si512(B.as_ptr().cast::<__m512i>());
let dst = _mm512_loadu_si512(DST.as_ptr().cast::<__m512i>());
assert_eq_m512i(_mm512_dpbusd_epi32(src, a, b), dst);
}
test_mm512_dpbusd_epi32();
}
#[track_caller]
unsafe fn assert_eq_m512i(a: __m512i, b: __m512i) {
assert_eq!(transmute::<_, [i32; 16]>(a), transmute::<_, [i32; 16]>(b))

View file

@ -40,17 +40,35 @@ pub unsafe fn read_all(
return read_so_far as libc::ssize_t;
}
/// Try to fill the given slice by reading from `fd`. Error if that many bytes could not be read.
#[track_caller]
pub fn read_all_into_slice(fd: libc::c_int, buf: &mut [u8]) -> Result<(), libc::ssize_t> {
let res = unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) };
if res >= 0 {
assert_eq!(res as usize, buf.len());
Ok(())
} else {
Err(res)
}
}
/// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read.
#[track_caller]
pub fn read_all_into_array<const N: usize>(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> {
let mut buf = [0; N];
let res = unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) };
if res >= 0 {
assert_eq!(res as usize, buf.len());
Ok(buf)
} else {
Err(res)
}
read_all_into_slice(fd, &mut buf)?;
Ok(buf)
}
/// Do a single read from `fd` and return the part of the buffer that was written into,
/// and the rest.
#[track_caller]
pub fn read_into_slice(
fd: libc::c_int,
buf: &mut [u8],
) -> Result<(&mut [u8], &mut [u8]), libc::ssize_t> {
let res = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) };
if res >= 0 { Ok(buf.split_at_mut(res as usize)) } else { Err(res) }
}
pub unsafe fn write_all(