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:
commit
eeb4431881
47 changed files with 748 additions and 382 deletions
19
Cargo.lock
19
Cargo.lock
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ invocationStrategy = "once"
|
|||
overrideCommand = [
|
||||
"./miri",
|
||||
"check",
|
||||
"--no-default-features",
|
||||
"-Zunstable-options",
|
||||
"--compile-time-deps",
|
||||
"--message-format=json",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
"rust-analyzer.cargo.buildScripts.overrideCommand": [
|
||||
"./miri",
|
||||
"check",
|
||||
"--no-default-features",
|
||||
"-Zunstable-options",
|
||||
"--compile-time-deps",
|
||||
"--message-format=json",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
"overrideCommand": [
|
||||
"./miri",
|
||||
"check",
|
||||
"--no-default-features",
|
||||
"-Zunstable-options",
|
||||
"--compile-time-deps",
|
||||
"--message-format=json"
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
7fefa09b90ca57b8a0e0e4717d672d38a0ae58b5
|
||||
f57b9e6f565a1847e83a63f3e90faa3870536c1f
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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: _,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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" => {
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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" => {
|
||||
|
|
|
|||
|
|
@ -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" => {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
21
src/tools/miri/tests/native-lib/fail/call_function_ptr.rs
Normal file
21
src/tools/miri/tests/native-lib/fail/call_function_ptr.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
14
src/tools/miri/tests/native-lib/fail/invalid_retval.rs
Normal file
14
src/tools/miri/tests/native-lib/fail/invalid_retval.rs
Normal 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
|
||||
}
|
||||
}
|
||||
13
src/tools/miri/tests/native-lib/fail/invalid_retval.stderr
Normal file
13
src/tools/miri/tests/native-lib/fail/invalid_retval.stderr
Normal 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
|
||||
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue