diff --git a/Cargo.lock b/Cargo.lock index d3637d1f4b3e0..816bb1a37859f 100644 --- a/Cargo.lock +++ b/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", ] diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 395c37e9ade8b..b314aaafbdf0c 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -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" diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 4a54a7e0eb712..e7c90e45eba59 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -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" diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 2832ef50adef9..d5ef9c7674147 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -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 diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 2dd8fc77459a0..c8e359cf23851 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -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 ;; diff --git a/src/tools/miri/etc/rust_analyzer_helix.toml b/src/tools/miri/etc/rust_analyzer_helix.toml index dd222c50431a7..1015b4baa6b09 100644 --- a/src/tools/miri/etc/rust_analyzer_helix.toml +++ b/src/tools/miri/etc/rust_analyzer_helix.toml @@ -27,7 +27,6 @@ invocationStrategy = "once" overrideCommand = [ "./miri", "check", - "--no-default-features", "-Zunstable-options", "--compile-time-deps", "--message-format=json", diff --git a/src/tools/miri/etc/rust_analyzer_vscode.json b/src/tools/miri/etc/rust_analyzer_vscode.json index 97ba212f8ef97..e82c648f59ab4 100644 --- a/src/tools/miri/etc/rust_analyzer_vscode.json +++ b/src/tools/miri/etc/rust_analyzer_vscode.json @@ -21,7 +21,6 @@ "rust-analyzer.cargo.buildScripts.overrideCommand": [ "./miri", "check", - "--no-default-features", "-Zunstable-options", "--compile-time-deps", "--message-format=json", diff --git a/src/tools/miri/etc/rust_analyzer_zed.json b/src/tools/miri/etc/rust_analyzer_zed.json index 7f60a931c46fd..e5ff400e989e7 100644 --- a/src/tools/miri/etc/rust_analyzer_zed.json +++ b/src/tools/miri/etc/rust_analyzer_zed.json @@ -30,7 +30,6 @@ "overrideCommand": [ "./miri", "check", - "--no-default-features", "-Zunstable-options", "--compile-time-deps", "--message-format=json" diff --git a/src/tools/miri/genmc-sys/Cargo.toml b/src/tools/miri/genmc-sys/Cargo.toml index 6443ecd969df4..37fcc58070a38 100644 --- a/src/tools/miri/genmc-sys/Cargo.toml +++ b/src/tools/miri/genmc-sys/Cargo.toml @@ -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 = [] diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index a22e3341d67ad..4fc3ce94fb8be 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -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") { diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs index 5a8bf76befd6e..86f6253d4558a 100644 --- a/src/tools/miri/miri-script/src/commands.rs +++ b/src/tools/miri/miri-script/src/commands.rs @@ -391,7 +391,8 @@ impl Command { Ok(()) } - fn check(features: Vec, flags: Vec) -> Result<()> { + fn check(mut features: Vec, flags: Vec) -> 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, flags: Vec) -> Result<()> { + fn clippy(mut features: Vec, flags: Vec) -> Result<()> { + features.push("check_only".into()); let e = MiriEnv::new()?; e.clippy(".", &features, &flags)?; e.clippy("cargo-miri", &[], &flags)?; diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index d32b6d0d2fc73..c44422d758c57 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -7fefa09b90ca57b8a0e0e4717d672d38a0ae58b5 +f57b9e6f565a1847e83a63f3e90faa3870536c1f diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 05d3444a4eb1a..fed51ed86433c 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -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, diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 1f87ac60c17a8..64c7096fc5c2e 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -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!( diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 502cdbdc57b37..f17bd5ac4319c 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -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, /// 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: _, diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs index 6915924f2a48a..7c463983320bb 100644 --- a/src/tools/miri/src/shims/env.rs +++ b/src/tools/miri/src/shims/env.rs @@ -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. diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index e51ace2fd9072..345e16b8da710 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -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; diff --git a/src/tools/miri/src/shims/native_lib/ffi.rs b/src/tools/miri/src/shims/native_lib/ffi.rs deleted file mode 100644 index 196f43c6f6a6c..0000000000000 --- a/src/tools/miri/src/shims/native_lib/ffi.rs +++ /dev/null @@ -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(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, - /// 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 } - } -} diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index 12abe841c0528..8a761855c4322 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -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, + /// 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)> { + args: &mut [OwnedArg], + ret: (FfiType, Size), + ) -> InterpResult<'tcx, (Box<[u8]>, Option)> { 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::(fun, libffi_args) }; - Scalar::from_i8(x) - } - ty::Int(IntTy::I16) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_i16(x) - } - ty::Int(IntTy::I32) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_i32(x) - } - ty::Int(IntTy::I64) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_i64(x) - } - ty::Int(IntTy::Isize) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_target_isize(x.try_into().unwrap(), this) - } - // uints - ty::Uint(UintTy::U8) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_u8(x) - } - ty::Uint(UintTy::U16) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_u16(x) - } - ty::Uint(UintTy::U32) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_u32(x) - } - ty::Uint(UintTy::U64) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_u64(x) - } - ty::Uint(UintTy::Usize) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_target_usize(x.try_into().unwrap(), this) - } - ty::Float(FloatTy::F32) => { - let x = unsafe { ffi::call::(fun, libffi_args) }; - Scalar::from_f32(x.to_soft()) - } - ty::Float(FloatTy::F64) => { - let x = unsafe { ffi::call::(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>, ) -> 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, + marker: PhantomData>, +} + +/// 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::() }; + // 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::>(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) } } diff --git a/src/tools/miri/src/shims/native_lib/trace/child.rs b/src/tools/miri/src/shims/native_lib/trace/child.rs index 795ad4a32076d..021ec2e9aeb3b 100644 --- a/src/tools/miri/src/shims/native_lib/trace/child.rs +++ b/src/tools/miri/src/shims/native_lib/trace/child.rs @@ -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>, - f: impl FnOnce() -> InterpResult<'tcx, crate::ImmTy<'tcx>>, - ) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option)> { + f: impl FnOnce() -> T, + ) -> InterpResult<'tcx, (T, Option)> { 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)) } } diff --git a/src/tools/miri/src/shims/native_lib/trace/stub.rs b/src/tools/miri/src/shims/native_lib/trace/stub.rs index 22787a6f6fa78..a3f6c616301c5 100644 --- a/src/tools/miri/src/shims/native_lib/trace/stub.rs +++ b/src/tools/miri/src/shims/native_lib/trace/stub.rs @@ -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)> { + f: impl FnOnce() -> U, + ) -> InterpResult<'tcx, (U, Option)> { // 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)) } } diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index 6cb0d221fc03d..2b290b68c78c6 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -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)?; diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 64b8376ff4aaa..8eacdc3583d44 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -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( diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index c48301c724167..fb2d3f7584209 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -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" => { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index e17456c3bc0e3..f43fd3fe2d188 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -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)?; diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index dd7b95bdc82be..f798f64441b1b 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -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" => { diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index ae7230877a71b..fa8c86b025a7a 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -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" => { diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs index a320ac1316891..cc371b43a6815 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs @@ -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"); diff --git a/src/tools/miri/src/shims/x86/avx512.rs b/src/tools/miri/src/shims/x86/avx512.rs index a886f5622ceda..0466ba1bd6c01 100644 --- a/src/tools/miri/src/shims/x86/avx512.rs +++ b/src/tools/miri/src/shims/x86/avx512.rs @@ -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`. +/// +/// +/// +/// +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(()) +} diff --git a/src/tools/miri/tests/native-lib/aggregate_arguments.c b/src/tools/miri/tests/native-lib/aggregate_arguments.c index 8ad687f2aec9f..e315642c13a9b 100644 --- a/src/tools/miri/tests/native-lib/aggregate_arguments.c +++ b/src/tools/miri/tests/native-lib/aggregate_arguments.c @@ -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 { diff --git a/src/tools/miri/tests/native-lib/fail/call_function_ptr.notrace.stderr b/src/tools/miri/tests/native-lib/fail/call_function_ptr.notrace.stderr new file mode 100644 index 0000000000000..faabba9ca7257 --- /dev/null +++ b/src/tools/miri/tests/native-lib/fail/call_function_ptr.notrace.stderr @@ -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 + diff --git a/src/tools/miri/tests/native-lib/fail/call_function_ptr.rs b/src/tools/miri/tests/native-lib/fail/call_function_ptr.rs new file mode 100644 index 0000000000000..b68c4d4062b7b --- /dev/null +++ b/src/tools/miri/tests/native-lib/fail/call_function_ptr.rs @@ -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 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 + } +} diff --git a/src/tools/miri/tests/native-lib/fail/call_function_ptr.trace.stderr b/src/tools/miri/tests/native-lib/fail/call_function_ptr.trace.stderr new file mode 100644 index 0000000000000..e56a5ece782b5 --- /dev/null +++ b/src/tools/miri/tests/native-lib/fail/call_function_ptr.trace.stderr @@ -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 + diff --git a/src/tools/miri/tests/native-lib/fail/invalid_retval.rs b/src/tools/miri/tests/native-lib/fail/invalid_retval.rs new file mode 100644 index 0000000000000..4967866b7e7fe --- /dev/null +++ b/src/tools/miri/tests/native-lib/fail/invalid_retval.rs @@ -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 + } +} diff --git a/src/tools/miri/tests/native-lib/fail/invalid_retval.stderr b/src/tools/miri/tests/native-lib/fail/invalid_retval.stderr new file mode 100644 index 0000000000000..9db29822d4f53 --- /dev/null +++ b/src/tools/miri/tests/native-lib/fail/invalid_retval.stderr @@ -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 + diff --git a/src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs b/src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs index 55acb240612f8..730e9d89441ab 100644 --- a/src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs +++ b/src/tools/miri/tests/native-lib/pass/aggregate_arguments.rs @@ -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)] diff --git a/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr b/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr index b6bbb4342b77e..bc2fcac08f014 100644 --- a/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr +++ b/src/tools/miri/tests/native-lib/pass/ptr_read_access.notrace.stderr @@ -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 - diff --git a/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr b/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr index 0d86ea066099a..c7f30c114f16a 100644 --- a/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr +++ b/src/tools/miri/tests/native-lib/pass/ptr_read_access.trace.stderr @@ -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 - diff --git a/src/tools/miri/tests/native-lib/ptr_read_access.c b/src/tools/miri/tests/native-lib/ptr_read_access.c index 5f071ca3d424d..44ba13aa54a62 100644 --- a/src/tools/miri/tests/native-lib/ptr_read_access.c +++ b/src/tools/miri/tests/native-lib/ptr_read_access.c @@ -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(); + } +} diff --git a/src/tools/miri/tests/native-lib/scalar_arguments.c b/src/tools/miri/tests/native-lib/scalar_arguments.c index 10b6244bdeb43..720f1982178c8 100644 --- a/src/tools/miri/tests/native-lib/scalar_arguments.c +++ b/src/tools/miri/tests/native-lib/scalar_arguments.c @@ -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; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs index 0500ba05046ce..52de8c7104c23 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs @@ -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; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs index ffbcf633b9873..db68daed53961 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs @@ -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"); } diff --git a/src/tools/miri/tests/pass-dep/shims/gettid.rs b/src/tools/miri/tests/pass-dep/shims/gettid.rs index b7a2fa49ef862..9d5ff0dc5dae4 100644 --- a/src/tools/miri/tests/pass-dep/shims/gettid.rs +++ b/src/tools/miri/tests/pass-dep/shims/gettid.rs @@ -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))] diff --git a/src/tools/miri/tests/pass-dep/tempfile.rs b/src/tools/miri/tests/pass-dep/tempfile.rs index a44a7e7d9244d..885190bd776a8 100644 --- a/src/tools/miri/tests/pass-dep/tempfile.rs +++ b/src/tools/miri/tests/pass-dep/tempfile.rs @@ -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(); } diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 648c90b5dd971..50b5dbfba1cdd 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -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); diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs index f95429d59ebec..42acb6c3fb374 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs @@ -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)) diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index e42f39c64eb6a..0765bacb6bd86 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -40,19 +40,37 @@ pub unsafe fn read_all( return read_so_far as libc::ssize_t; } -/// Read exactly `N` bytes from `fd`. Error if that many bytes could not be read. +/// 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_array(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> { - let mut buf = [0; N]; +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(buf) + 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(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> { + let mut buf = [0; N]; + 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( fd: libc::c_int, buf: *const libc::c_void,