From 3f57fc170aab2da57ff0c695051bfd26571b9391 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Mon, 3 Feb 2025 04:55:59 +0000 Subject: [PATCH 01/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 6e84524c8aa86..b629597207648 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -6dd75f0d6802f56564f5f9c947a85ded286d3986 +613bdd49978298648ed05ace086bd1ecad54b44a From 9a858faf691e8627a96b0413106d8b6dd44401eb Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 4 Feb 2025 05:04:47 +0000 Subject: [PATCH 02/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index b629597207648..7f1aa78169d8d 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -613bdd49978298648ed05ace086bd1ecad54b44a +affdb59607566c1615c829eea9e7b27a093994ec From 8f1c4a33b7b95e923924b97a5d7bb9fe6c2c913b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 4 Feb 2025 12:05:02 +0100 Subject: [PATCH 03/37] update JSON target spec to declare softfloat ABI --- src/tools/miri/tests/x86_64-unknown-kernel.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/miri/tests/x86_64-unknown-kernel.json b/src/tools/miri/tests/x86_64-unknown-kernel.json index 8135b618d0d36..8da67d3a1c6b2 100644 --- a/src/tools/miri/tests/x86_64-unknown-kernel.json +++ b/src/tools/miri/tests/x86_64-unknown-kernel.json @@ -10,6 +10,7 @@ "vendor": "unknown", "linker": "rust-lld", "linker-flavor": "gnu-lld", + "rustc-abi": "x86-softfloat", "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float", "dynamic-linking": false, "executables": true, From 9673d8d543c5fa8f14b168beed8280b98baf64cf Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Tue, 4 Feb 2025 11:51:08 +0000 Subject: [PATCH 04/37] Fix build on Solaris where is no flock(). --- src/tools/miri/src/shims/unix/fs.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index c7399b00d3fe2..924dbb8879999 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -178,7 +178,7 @@ impl UnixFileDescription for FileHandle { op: FlockOp, ) -> InterpResult<'tcx, io::Result<()>> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); - #[cfg(target_family = "unix")] + #[cfg(all(target_family = "unix", not(target_os = "solaris")))] { use std::os::fd::AsRawFd; @@ -260,10 +260,15 @@ impl UnixFileDescription for FileHandle { interp_ok(res) } - #[cfg(not(any(target_family = "unix", target_family = "windows")))] + #[cfg(not(any( + all(target_family = "unix", not(target_os = "solaris")), + target_family = "windows" + )))] { let _ = op; - compile_error!("flock is supported only on UNIX and Windows hosts"); + throw_unsup_format!( + "flock is supported only on UNIX (except Solaris) and Windows hosts" + ); } } } From a058e1a5df545f3ea0b8cf4c46e06760816b571a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Feb 2025 09:02:16 +0100 Subject: [PATCH 05/37] use cfg_match --- src/tools/miri/src/lib.rs | 1 + src/tools/miri/src/shims/unix/fs.rs | 158 +++++++++++++--------------- 2 files changed, 77 insertions(+), 82 deletions(-) diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 45054c37c40e9..feab5a9a2f7fc 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +#![feature(cfg_match)] #![feature(cell_update)] #![feature(float_gamma)] #![feature(map_try_insert)] diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 924dbb8879999..f8e0c638c90d5 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -178,97 +178,91 @@ impl UnixFileDescription for FileHandle { op: FlockOp, ) -> InterpResult<'tcx, io::Result<()>> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); - #[cfg(all(target_family = "unix", not(target_os = "solaris")))] - { - use std::os::fd::AsRawFd; - - use FlockOp::*; - // We always use non-blocking call to prevent interpreter from being blocked - let (host_op, lock_nb) = match op { - SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking), - ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking), - Unlock => (libc::LOCK_UN, false), - }; + cfg_match! { + all(target_family = "unix", not(target_os = "solaris")) => { + use std::os::fd::AsRawFd; + + use FlockOp::*; + // We always use non-blocking call to prevent interpreter from being blocked + let (host_op, lock_nb) = match op { + SharedLock { nonblocking } => (libc::LOCK_SH | libc::LOCK_NB, nonblocking), + ExclusiveLock { nonblocking } => (libc::LOCK_EX | libc::LOCK_NB, nonblocking), + Unlock => (libc::LOCK_UN, false), + }; - let fd = self.file.as_raw_fd(); - let ret = unsafe { libc::flock(fd, host_op) }; - let res = match ret { - 0 => Ok(()), - -1 => { - let err = io::Error::last_os_error(); - if !lock_nb && err.kind() == io::ErrorKind::WouldBlock { - throw_unsup_format!("blocking `flock` is not currently supported"); + let fd = self.file.as_raw_fd(); + let ret = unsafe { libc::flock(fd, host_op) }; + let res = match ret { + 0 => Ok(()), + -1 => { + let err = io::Error::last_os_error(); + if !lock_nb && err.kind() == io::ErrorKind::WouldBlock { + throw_unsup_format!("blocking `flock` is not currently supported"); + } + Err(err) } - Err(err) - } - ret => panic!("Unexpected return value from flock: {ret}"), - }; - interp_ok(res) - } - - #[cfg(target_family = "windows")] - { - use std::os::windows::io::AsRawHandle; + ret => panic!("Unexpected return value from flock: {ret}"), + }; + interp_ok(res) + } + target_family = "windows" => { + use std::os::windows::io::AsRawHandle; - use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE, - }; - use windows_sys::Win32::Storage::FileSystem::{ - LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile, - }; + use windows_sys::Win32::Foundation::{ + ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, FALSE, HANDLE, TRUE, + }; + use windows_sys::Win32::Storage::FileSystem::{ + LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, UnlockFile, + }; - let fh = self.file.as_raw_handle() as HANDLE; + let fh = self.file.as_raw_handle() as HANDLE; - use FlockOp::*; - let (ret, lock_nb) = match op { - SharedLock { nonblocking } | ExclusiveLock { nonblocking } => { - // We always use non-blocking call to prevent interpreter from being blocked - let mut flags = LOCKFILE_FAIL_IMMEDIATELY; - if matches!(op, ExclusiveLock { .. }) { - flags |= LOCKFILE_EXCLUSIVE_LOCK; + use FlockOp::*; + let (ret, lock_nb) = match op { + SharedLock { nonblocking } | ExclusiveLock { nonblocking } => { + // We always use non-blocking call to prevent interpreter from being blocked + let mut flags = LOCKFILE_FAIL_IMMEDIATELY; + if matches!(op, ExclusiveLock { .. }) { + flags |= LOCKFILE_EXCLUSIVE_LOCK; + } + let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) }; + (ret, nonblocking) } - let ret = unsafe { LockFileEx(fh, flags, 0, !0, !0, &mut std::mem::zeroed()) }; - (ret, nonblocking) - } - Unlock => { - let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) }; - (ret, false) - } - }; + Unlock => { + let ret = unsafe { UnlockFile(fh, 0, 0, !0, !0) }; + (ret, false) + } + }; - let res = match ret { - TRUE => Ok(()), - FALSE => { - let mut err = io::Error::last_os_error(); - // This only runs on Windows hosts so we can use `raw_os_error`. - // We have to be careful not to forward that error code to target code. - let code: u32 = err.raw_os_error().unwrap().try_into().unwrap(); - if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) { - if lock_nb { - // The io error mapping does not know about these error codes, - // so we translate it to `WouldBlock` manually. - let desc = format!("LockFileEx wouldblock error: {err}"); - err = io::Error::new(io::ErrorKind::WouldBlock, desc); - } else { - throw_unsup_format!("blocking `flock` is not currently supported"); + let res = match ret { + TRUE => Ok(()), + FALSE => { + let mut err = io::Error::last_os_error(); + // This only runs on Windows hosts so we can use `raw_os_error`. + // We have to be careful not to forward that error code to target code. + let code: u32 = err.raw_os_error().unwrap().try_into().unwrap(); + if matches!(code, ERROR_IO_PENDING | ERROR_LOCK_VIOLATION) { + if lock_nb { + // The io error mapping does not know about these error codes, + // so we translate it to `WouldBlock` manually. + let desc = format!("LockFileEx wouldblock error: {err}"); + err = io::Error::new(io::ErrorKind::WouldBlock, desc); + } else { + throw_unsup_format!("blocking `flock` is not currently supported"); + } } + Err(err) } - Err(err) - } - _ => panic!("Unexpected return value: {ret}"), - }; - interp_ok(res) - } - - #[cfg(not(any( - all(target_family = "unix", not(target_os = "solaris")), - target_family = "windows" - )))] - { - let _ = op; - throw_unsup_format!( - "flock is supported only on UNIX (except Solaris) and Windows hosts" - ); + _ => panic!("Unexpected return value: {ret}"), + }; + interp_ok(res) + } + _ => { + let _ = op; + throw_unsup_format!( + "flock is supported only on UNIX (except Solaris) and Windows hosts" + ); + } } } } From 6b969d799cc783fdcb63b08e88abb5d0d5f8c4a3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Feb 2025 09:36:22 +0100 Subject: [PATCH 06/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 7f1aa78169d8d..e695fc4004b56 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -affdb59607566c1615c829eea9e7b27a093994ec +8df89d1cb077cd76013d3f9f5a4e92c5b5a9280c From e58e3d5182959e3d78d0494c4186dde46249c477 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Feb 2025 09:44:43 +0100 Subject: [PATCH 07/37] bump default max parallelism up to 20 --- src/tools/miri/src/bin/miri.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 685f5670ab4e7..71a225faf9dea 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -721,8 +721,8 @@ fn main() { // Ensure we have parallelism for many-seeds mode. if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) { - // Clamp to 10 threads; things get a lot less efficient beyond that due to lock contention. - let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(10); + // Clamp to 20 threads; things get a less efficient beyond that due to lock contention. + let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(20); rustc_args.push(format!("-Zthreads={threads}")); } let many_seeds = From 2d7f2ff4b304f0fad8a5116c7155608b8c5f14eb Mon Sep 17 00:00:00 2001 From: Slava Barinov Date: Thu, 6 Feb 2025 13:43:54 +0900 Subject: [PATCH 08/37] allow code to call geteuid() --- src/tools/miri/src/shims/unix/foreign_items.rs | 2 +- src/tools/miri/tests/pass-dep/libc/libc-misc.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index d459ec7cb774c..09757071075ad 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -815,7 +815,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.handle_miri_start_unwind(payload)?; return interp_ok(EmulateItemResult::NeedsUnwind); } - "getuid" => { + "getuid" | "geteuid" => { let [] = this.check_shim(abi, Conv::C, link_name, args)?; // For now, just pretend we always have this fixed UID. this.write_int(UID, dest)?; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-misc.rs b/src/tools/miri/tests/pass-dep/libc/libc-misc.rs index f07007fa70546..d1c0085b024a5 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-misc.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-misc.rs @@ -78,11 +78,16 @@ fn test_getuid() { let _val = unsafe { libc::getuid() }; } +fn test_geteuid() { + let _val = unsafe { libc::geteuid() }; +} + fn main() { test_thread_local_errno(); test_environ(); test_dlsym(); test_getuid(); + test_geteuid(); #[cfg(target_os = "linux")] test_sigrt(); From eb414585d2e92fd684e734ef681ca2e57ede1ddb Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 6 Feb 2025 04:54:51 +0000 Subject: [PATCH 09/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index e695fc4004b56..6e2344a92eecf 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -8df89d1cb077cd76013d3f9f5a4e92c5b5a9280c +30865107cb8942ab8eaf9baf8d3aa2a6dec2643f From 046451d0b4c3bf098afd0ea3bfa497ef665005c1 Mon Sep 17 00:00:00 2001 From: tiif Date: Thu, 6 Feb 2025 14:45:35 +0800 Subject: [PATCH 10/37] Throw ub error when invoking non-vararg shim with vararg import --- src/tools/miri/src/helpers.rs | 5 +++++ .../miri/src/shims/unix/linux/foreign_items.rs | 4 ++-- .../shims/vararg_caller_signature_mismatch.rs | 14 ++++++++++++++ .../shims/vararg_caller_signature_mismatch.stderr | 15 +++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.rs create mode 100644 src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.stderr diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index a26f12cdfb1e2..3815da9ad0663 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -999,6 +999,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, { self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; + if abi.c_variadic { + throw_ub_format!( + "calling a non-variadic function with a variadic caller-side signature" + ); + } check_arg_count(args) } diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 10af245dcc087..f5da7b0170b6a 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -133,8 +133,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(ptr, dest)?; } "mremap" => { - let [old_address, old_size, new_size, flags] = - this.check_shim(abi, Conv::C, link_name, args)?; + let ([old_address, old_size, new_size, flags], _) = + this.check_shim_variadic(abi, Conv::C, link_name, args)?; let ptr = this.mremap(old_address, old_size, new_size, flags)?; this.write_scalar(ptr, dest)?; } diff --git a/src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.rs b/src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.rs new file mode 100644 index 0000000000000..515e467fb54d3 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.rs @@ -0,0 +1,14 @@ +//@ignore-target: windows # No libc pipe on Windows + +// Declare a non-variadic function as variadic. +extern "C" { + fn pipe(fds: *mut std::ffi::c_int, ...) -> std::ffi::c_int; +} + +// Test the error caused by invoking non-vararg shim with a vararg import. +fn main() { + let mut fds = [-1, -1]; + let res = unsafe { pipe(fds.as_mut_ptr()) }; + //~^ ERROR: calling a non-variadic function with a variadic caller-side signature + assert_eq!(res, 0); +} diff --git a/src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.stderr b/src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.stderr new file mode 100644 index 0000000000000..2782f3b3269cf --- /dev/null +++ b/src/tools/miri/tests/fail/shims/vararg_caller_signature_mismatch.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a non-variadic function with a variadic caller-side signature + --> tests/fail/shims/vararg_caller_signature_mismatch.rs:LL:CC + | +LL | let res = unsafe { pipe(fds.as_mut_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^ calling a non-variadic function with a variadic caller-side signature + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/shims/vararg_caller_signature_mismatch.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + From 550d2175500b806827afcd13e190ad3e1e2bdaa8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 6 Feb 2025 17:20:51 +0100 Subject: [PATCH 11/37] inline a once-used function --- src/tools/miri/src/helpers.rs | 45 +++++++++++++---------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 3815da9ad0663..c1fed82f52883 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1020,7 +1020,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, { self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; - check_vargarg_fixed_arg_count(link_name, abi, args) + if !abi.c_variadic { + throw_ub_format!( + "calling a variadic function with a non-variadic caller-side signature" + ); + } + if abi.fixed_count != u32::try_from(N).unwrap() { + throw_ub_format!( + "incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}", + link_name.as_str(), + abi.fixed_count + ) + } + if let Some(args) = args.split_first_chunk() { + return interp_ok(args); + } + panic!("mismatch between signature and `args` slice"); } /// Mark a machine allocation that was just created as immutable. @@ -1233,34 +1248,6 @@ pub fn check_min_vararg_count<'a, 'tcx, const N: usize>( ) } -/// Check the number of fixed args of a vararg function. -/// Returns a tuple that consisting of an array of fixed args, and a slice of varargs. -fn check_vargarg_fixed_arg_count<'a, 'tcx, const N: usize>( - link_name: Symbol, - abi: &FnAbi<'tcx, Ty<'tcx>>, - args: &'a [OpTy<'tcx>], -) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> { - if !abi.c_variadic { - throw_ub_format!("calling a variadic function with a non-variadic caller-side signature"); - } - if abi.fixed_count != u32::try_from(N).unwrap() { - throw_ub_format!( - "incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}", - link_name.as_str(), - abi.fixed_count - ) - } - if let Some(args) = args.split_first_chunk() { - return interp_ok(args); - } - throw_ub_format!( - "incorrect number of arguments for `{}`: got {}, expected at least {}", - link_name.as_str(), - args.len(), - N - ) -} - pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> { throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!( "{name} not available when isolation is enabled", From af04c0dfa1c32799762935c25554909b1ea53b23 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 6 Feb 2025 17:24:44 +0100 Subject: [PATCH 12/37] some more argument checking cleanup --- src/tools/miri/src/helpers.rs | 19 ++++++-- src/tools/miri/src/intrinsics/atomic.rs | 16 +++---- src/tools/miri/src/intrinsics/mod.rs | 48 +++++++++---------- src/tools/miri/src/intrinsics/simd.rs | 34 ++++++------- src/tools/miri/src/shims/panic.rs | 4 +- .../miri/src/shims/unix/linux_like/sync.rs | 8 ---- .../function_calls/check_arg_count_abort.rs | 2 +- .../check_arg_count_abort.stderr | 4 +- .../check_arg_count_too_few_args.rs | 2 +- .../check_arg_count_too_few_args.stderr | 4 +- .../check_arg_count_too_many_args.rs | 2 +- .../check_arg_count_too_many_args.stderr | 4 +- 12 files changed, 77 insertions(+), 70 deletions(-) diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index c1fed82f52883..12e7d0f1a62cf 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -999,12 +999,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, { self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; + if abi.c_variadic { throw_ub_format!( "calling a non-variadic function with a variadic caller-side signature" ); } - check_arg_count(args) + if let Ok(ops) = args.try_into() { + return interp_ok(ops); + } + throw_ub_format!( + "incorrect number of arguments for `{link_name}`: got {}, expected {}", + args.len(), + N + ) } /// Check shim for variadic function. @@ -1020,6 +1028,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, { self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?; + if !abi.c_variadic { throw_ub_format!( "calling a variadic function with a non-variadic caller-side signature" @@ -1219,7 +1228,7 @@ impl<'tcx> MiriMachine<'tcx> { } /// Check that the number of args is what we expect. -pub fn check_arg_count<'a, 'tcx, const N: usize>( +pub fn check_intrinsic_arg_count<'a, 'tcx, const N: usize>( args: &'a [OpTy<'tcx>], ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> where @@ -1228,7 +1237,11 @@ where if let Ok(ops) = args.try_into() { return interp_ok(ops); } - throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N) + throw_ub_format!( + "incorrect number of arguments for intrinsic: got {}, expected {}", + args.len(), + N + ) } /// Check that the number of varargs is at least the minimum what we expect. diff --git a/src/tools/miri/src/intrinsics/atomic.rs b/src/tools/miri/src/intrinsics/atomic.rs index 8507b0f49ded8..e1e9ebb4e9d9e 100644 --- a/src/tools/miri/src/intrinsics/atomic.rs +++ b/src/tools/miri/src/intrinsics/atomic.rs @@ -1,7 +1,7 @@ use rustc_middle::mir::BinOp; use rustc_middle::{mir, ty}; -use self::helpers::check_arg_count; +use self::helpers::check_intrinsic_arg_count; use crate::*; pub enum AtomicOp { @@ -131,7 +131,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [place] = check_arg_count(args)?; + let [place] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; // Perform atomic load. @@ -144,7 +144,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { fn atomic_store(&mut self, args: &[OpTy<'tcx>], atomic: AtomicWriteOrd) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [place, val] = check_arg_count(args)?; + let [place, val] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; // Perform regular load. @@ -159,7 +159,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { args: &[OpTy<'tcx>], atomic: AtomicFenceOrd, ) -> InterpResult<'tcx> { - let [] = check_arg_count(args)?; + let [] = check_intrinsic_arg_count(args)?; let _ = atomic; //FIXME: compiler fences are currently ignored interp_ok(()) @@ -171,7 +171,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { atomic: AtomicFenceOrd, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [] = check_arg_count(args)?; + let [] = check_intrinsic_arg_count(args)?; this.atomic_fence(atomic)?; interp_ok(()) } @@ -185,7 +185,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [place, rhs] = check_arg_count(args)?; + let [place, rhs] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; let rhs = this.read_immediate(rhs)?; @@ -226,7 +226,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [place, new] = check_arg_count(args)?; + let [place, new] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; let new = this.read_scalar(new)?; @@ -245,7 +245,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [place, expect_old, new] = check_arg_count(args)?; + let [place, expect_old, new] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; let expect_old = this.read_immediate(expect_old)?; // read as immediate for the sake of `binary_op()` let new = this.read_scalar(new)?; diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index bce78adcaea45..ec4fdfe0bacfe 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -11,7 +11,7 @@ use rustc_middle::ty::{self, FloatTy}; use rustc_span::{Symbol, sym}; use self::atomic::EvalContextExt as _; -use self::helpers::{ToHost, ToSoft, check_arg_count}; +use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count}; use self::simd::EvalContextExt as _; use crate::*; @@ -104,24 +104,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Raw memory accesses "volatile_load" => { - let [place] = check_arg_count(args)?; + let [place] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; this.copy_op(&place, dest)?; } "volatile_store" => { - let [place, dest] = check_arg_count(args)?; + let [place, dest] = check_intrinsic_arg_count(args)?; let place = this.deref_pointer(place)?; this.copy_op(dest, &place)?; } "volatile_set_memory" => { - let [ptr, val_byte, count] = check_arg_count(args)?; + let [ptr, val_byte, count] = check_intrinsic_arg_count(args)?; this.write_bytes_intrinsic(ptr, val_byte, count, "volatile_set_memory")?; } // Memory model / provenance manipulation "ptr_mask" => { - let [ptr, mask] = check_arg_count(args)?; + let [ptr, mask] = check_intrinsic_arg_count(args)?; let ptr = this.read_pointer(ptr)?; let mask = this.read_target_usize(mask)?; @@ -137,7 +137,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // ``` // Would not be considered UB, or the other way around (`is_val_statically_known(0)`). "is_val_statically_known" => { - let [_arg] = check_arg_count(args)?; + let [_arg] = check_intrinsic_arg_count(args)?; // FIXME: should we check for validity here? It's tricky because we do not have a // place. Codegen does not seem to set any attributes like `noundef` for intrinsic // calls, so we don't *have* to do anything. @@ -146,7 +146,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "floorf16" | "ceilf16" | "truncf16" | "roundf16" | "rintf16" => { - let [f] = check_arg_count(args)?; + let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f16()?; let mode = match intrinsic_name { "floorf16" => Round::TowardNegative, @@ -161,7 +161,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "floorf32" | "ceilf32" | "truncf32" | "roundf32" | "rintf32" => { - let [f] = check_arg_count(args)?; + let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let mode = match intrinsic_name { "floorf32" => Round::TowardNegative, @@ -176,7 +176,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "floorf64" | "ceilf64" | "truncf64" | "roundf64" | "rintf64" => { - let [f] = check_arg_count(args)?; + let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let mode = match intrinsic_name { "floorf64" => Round::TowardNegative, @@ -191,7 +191,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "floorf128" | "ceilf128" | "truncf128" | "roundf128" | "rintf128" => { - let [f] = check_arg_count(args)?; + let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f128()?; let mode = match intrinsic_name { "floorf128" => Round::TowardNegative, @@ -216,7 +216,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "log10f32" | "log2f32" => { - let [f] = check_arg_count(args)?; + let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; // Using host floats except for sqrt (but it's fine, these operations do not have // guaranteed precision). @@ -244,7 +244,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "log10f64" | "log2f64" => { - let [f] = check_arg_count(args)?; + let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; // Using host floats except for sqrt (but it's fine, these operations do not have // guaranteed precision). @@ -264,7 +264,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "fmaf32" => { - let [a, b, c] = check_arg_count(args)?; + let [a, b, c] = check_intrinsic_arg_count(args)?; let a = this.read_scalar(a)?.to_f32()?; let b = this.read_scalar(b)?.to_f32()?; let c = this.read_scalar(c)?.to_f32()?; @@ -274,7 +274,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "fmaf64" => { - let [a, b, c] = check_arg_count(args)?; + let [a, b, c] = check_intrinsic_arg_count(args)?; let a = this.read_scalar(a)?.to_f64()?; let b = this.read_scalar(b)?.to_f64()?; let c = this.read_scalar(c)?.to_f64()?; @@ -285,7 +285,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "fmuladdf32" => { - let [a, b, c] = check_arg_count(args)?; + let [a, b, c] = check_intrinsic_arg_count(args)?; let a = this.read_scalar(a)?.to_f32()?; let b = this.read_scalar(b)?.to_f32()?; let c = this.read_scalar(c)?.to_f32()?; @@ -300,7 +300,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "fmuladdf64" => { - let [a, b, c] = check_arg_count(args)?; + let [a, b, c] = check_intrinsic_arg_count(args)?; let a = this.read_scalar(a)?.to_f64()?; let b = this.read_scalar(b)?.to_f64()?; let c = this.read_scalar(c)?.to_f64()?; @@ -316,7 +316,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powf32" => { - let [f1, f2] = check_arg_count(args)?; + let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; // Using host floats (but it's fine, this operation does not have guaranteed precision). @@ -325,7 +325,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "powf64" => { - let [f1, f2] = check_arg_count(args)?; + let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; // Using host floats (but it's fine, this operation does not have guaranteed precision). @@ -335,7 +335,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powif32" => { - let [f, i] = check_arg_count(args)?; + let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; // Using host floats (but it's fine, this operation does not have guaranteed precision). @@ -344,7 +344,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "powif64" => { - let [f, i] = check_arg_count(args)?; + let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; // Using host floats (but it's fine, this operation does not have guaranteed precision). @@ -360,7 +360,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "fdiv_algebraic" | "frem_algebraic" => { - let [a, b] = check_arg_count(args)?; + let [a, b] = check_intrinsic_arg_count(args)?; let a = this.read_immediate(a)?; let b = this.read_immediate(b)?; let op = match intrinsic_name { @@ -383,7 +383,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "fdiv_fast" | "frem_fast" => { - let [a, b] = check_arg_count(args)?; + let [a, b] = check_intrinsic_arg_count(args)?; let a = this.read_immediate(a)?; let b = this.read_immediate(b)?; let op = match intrinsic_name { @@ -427,7 +427,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "float_to_int_unchecked" => { - let [val] = check_arg_count(args)?; + let [val] = check_intrinsic_arg_count(args)?; let val = this.read_immediate(val)?; let res = this @@ -444,7 +444,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Other "breakpoint" => { - let [] = check_arg_count(args)?; + let [] = check_intrinsic_arg_count(args)?; // normally this would raise a SIGTRAP, which aborts if no debugger is connected throw_machine_stop!(TerminationInfo::Abort(format!("trace/breakpoint trap"))) } diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 45e316b190a68..339d7161374b7 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -7,7 +7,9 @@ use rustc_middle::ty::layout::LayoutOf; use rustc_middle::{mir, ty}; use rustc_span::{Symbol, sym}; -use crate::helpers::{ToHost, ToSoft, bool_to_simd_element, check_arg_count, simd_element_to_bool}; +use crate::helpers::{ + ToHost, ToSoft, bool_to_simd_element, check_intrinsic_arg_count, simd_element_to_bool, +}; use crate::*; #[derive(Copy, Clone)] @@ -50,7 +52,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "bswap" | "bitreverse" => { - let [op] = check_arg_count(args)?; + let [op] = check_intrinsic_arg_count(args)?; let (op, op_len) = this.project_to_simd(op)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -197,7 +199,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { use mir::BinOp; - let [left, right] = check_arg_count(args)?; + let [left, right] = check_intrinsic_arg_count(args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -288,7 +290,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "fma" | "relaxed_fma" => { - let [a, b, c] = check_arg_count(args)?; + let [a, b, c] = check_intrinsic_arg_count(args)?; let (a, a_len) = this.project_to_simd(a)?; let (b, b_len) = this.project_to_simd(b)?; let (c, c_len) = this.project_to_simd(c)?; @@ -352,7 +354,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "reduce_min" => { use mir::BinOp; - let [op] = check_arg_count(args)?; + let [op] = check_intrinsic_arg_count(args)?; let (op, op_len) = this.project_to_simd(op)?; let imm_from_bool = @@ -415,7 +417,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "reduce_mul_ordered" => { use mir::BinOp; - let [op, init] = check_arg_count(args)?; + let [op, init] = check_intrinsic_arg_count(args)?; let (op, op_len) = this.project_to_simd(op)?; let init = this.read_immediate(init)?; @@ -433,7 +435,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_immediate(*res, dest)?; } "select" => { - let [mask, yes, no] = check_arg_count(args)?; + let [mask, yes, no] = check_intrinsic_arg_count(args)?; let (mask, mask_len) = this.project_to_simd(mask)?; let (yes, yes_len) = this.project_to_simd(yes)?; let (no, no_len) = this.project_to_simd(no)?; @@ -455,7 +457,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Variant of `select` that takes a bitmask rather than a "vector of bool". "select_bitmask" => { - let [mask, yes, no] = check_arg_count(args)?; + let [mask, yes, no] = check_intrinsic_arg_count(args)?; let (yes, yes_len) = this.project_to_simd(yes)?; let (no, no_len) = this.project_to_simd(no)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -529,7 +531,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Converts a "vector of bool" into a bitmask. "bitmask" => { - let [op] = check_arg_count(args)?; + let [op] = check_intrinsic_arg_count(args)?; let (op, op_len) = this.project_to_simd(op)?; let bitmask_len = op_len.next_multiple_of(8); if bitmask_len > 64 { @@ -577,7 +579,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "cast" | "as" | "cast_ptr" | "expose_provenance" | "with_exposed_provenance" => { - let [op] = check_arg_count(args)?; + let [op] = check_intrinsic_arg_count(args)?; let (op, op_len) = this.project_to_simd(op)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -634,7 +636,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "shuffle_generic" => { - let [left, right] = check_arg_count(args)?; + let [left, right] = check_intrinsic_arg_count(args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -664,7 +666,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "shuffle" => { - let [left, right, index] = check_arg_count(args)?; + let [left, right, index] = check_intrinsic_arg_count(args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; let (index, index_len) = this.project_to_simd(index)?; @@ -695,7 +697,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "gather" => { - let [passthru, ptrs, mask] = check_arg_count(args)?; + let [passthru, ptrs, mask] = check_intrinsic_arg_count(args)?; let (passthru, passthru_len) = this.project_to_simd(passthru)?; let (ptrs, ptrs_len) = this.project_to_simd(ptrs)?; let (mask, mask_len) = this.project_to_simd(mask)?; @@ -721,7 +723,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "scatter" => { - let [value, ptrs, mask] = check_arg_count(args)?; + let [value, ptrs, mask] = check_intrinsic_arg_count(args)?; let (value, value_len) = this.project_to_simd(value)?; let (ptrs, ptrs_len) = this.project_to_simd(ptrs)?; let (mask, mask_len) = this.project_to_simd(mask)?; @@ -741,7 +743,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "masked_load" => { - let [mask, ptr, default] = check_arg_count(args)?; + let [mask, ptr, default] = check_intrinsic_arg_count(args)?; let (mask, mask_len) = this.project_to_simd(mask)?; let ptr = this.read_pointer(ptr)?; let (default, default_len) = this.project_to_simd(default)?; @@ -767,7 +769,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "masked_store" => { - let [mask, ptr, vals] = check_arg_count(args)?; + let [mask, ptr, vals] = check_intrinsic_arg_count(args)?; let (mask, mask_len) = this.project_to_simd(mask)?; let ptr = this.read_pointer(ptr)?; let (vals, vals_len) = this.project_to_simd(vals)?; diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index 83f331bb173db..fc58d88591f81 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -15,7 +15,7 @@ use rustc_abi::ExternAbi; use rustc_middle::{mir, ty}; use rustc_target::spec::PanicStrategy; -use self::helpers::check_arg_count; +use self::helpers::check_intrinsic_arg_count; use crate::*; /// Holds all of the relevant data for when unwinding hits a `try` frame. @@ -77,7 +77,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // a pointer to `Box`. // Get all the arguments. - let [try_fn, data, catch_fn] = check_arg_count(args)?; + let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?; let try_fn = this.read_pointer(try_fn)?; let data = this.read_immediate(data)?; let catch_fn = this.read_pointer(catch_fn)?; diff --git a/src/tools/miri/src/shims/unix/linux_like/sync.rs b/src/tools/miri/src/shims/unix/linux_like/sync.rs index 280bee4800fe5..86e8b57824c27 100644 --- a/src/tools/miri/src/shims/unix/linux_like/sync.rs +++ b/src/tools/miri/src/shims/unix/linux_like/sync.rs @@ -13,14 +13,6 @@ pub fn futex<'tcx>( varargs: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - // The amount of arguments used depends on the type of futex operation. - // The full futex syscall takes six arguments (excluding the syscall - // number), which is also the maximum amount of arguments a linux syscall - // can take on most architectures. - // However, not all futex operations use all six arguments. The unused ones - // may or may not be left out from the `syscall()` call. - // Therefore we don't use `check_arg_count` here, but only check for the - // number of arguments to fall within a range. let [addr, op, val] = check_min_vararg_count("`syscall(SYS_futex, ...)`", varargs)?; // The first three arguments (after the syscall number itself) are the same to all futex operations: diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs index 967a78bf83187..db7bd223bd45a 100644 --- a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.rs @@ -5,6 +5,6 @@ fn main() { unsafe { abort(1); - //~^ ERROR: Undefined Behavior: incorrect number of arguments: got 1, expected 0 + //~^ ERROR: Undefined Behavior: incorrect number of arguments for `abort`: got 1, expected 0 } } diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr index 687d0538b3c70..3c81ba4e1417d 100644 --- a/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_abort.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: incorrect number of arguments: got 1, expected 0 +error: Undefined Behavior: incorrect number of arguments for `abort`: got 1, expected 0 --> tests/fail/function_calls/check_arg_count_abort.rs:LL:CC | LL | abort(1); - | ^^^^^^^^ incorrect number of arguments: got 1, expected 0 + | ^^^^^^^^ incorrect number of arguments for `abort`: got 1, expected 0 | = 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 diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs index 223c95ffca46b..41aebea2d8add 100644 --- a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.rs @@ -4,6 +4,6 @@ fn main() { } unsafe { - let _ = malloc(); //~ ERROR: Undefined Behavior: incorrect number of arguments: got 0, expected 1 + let _ = malloc(); //~ ERROR: Undefined Behavior: incorrect number of arguments for `malloc`: got 0, expected 1 }; } diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr index d778eae64fa4b..eacd4045ae05a 100644 --- a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_few_args.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: incorrect number of arguments: got 0, expected 1 +error: Undefined Behavior: incorrect number of arguments for `malloc`: got 0, expected 1 --> tests/fail/function_calls/check_arg_count_too_few_args.rs:LL:CC | LL | let _ = malloc(); - | ^^^^^^^^ incorrect number of arguments: got 0, expected 1 + | ^^^^^^^^ incorrect number of arguments for `malloc`: got 0, expected 1 | = 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 diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs index 7ee9c40bf7a4b..1f5c509c6666f 100644 --- a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.rs @@ -4,6 +4,6 @@ fn main() { } unsafe { - let _ = malloc(1, 2); //~ ERROR: Undefined Behavior: incorrect number of arguments: got 2, expected 1 + let _ = malloc(1, 2); //~ ERROR: Undefined Behavior: incorrect number of arguments for `malloc`: got 2, expected 1 }; } diff --git a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr index dfec2a8628706..42d5e98c01afc 100644 --- a/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr +++ b/src/tools/miri/tests/fail/function_calls/check_arg_count_too_many_args.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: incorrect number of arguments: got 2, expected 1 +error: Undefined Behavior: incorrect number of arguments for `malloc`: got 2, expected 1 --> tests/fail/function_calls/check_arg_count_too_many_args.rs:LL:CC | LL | let _ = malloc(1, 2); - | ^^^^^^^^^^^^ incorrect number of arguments: got 2, expected 1 + | ^^^^^^^^^^^^ incorrect number of arguments for `malloc`: got 2, expected 1 | = 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 From 89fd6d0e26fd198051b458501a349c4b439e8c32 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Fri, 7 Feb 2025 04:55:39 +0000 Subject: [PATCH 13/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 6e2344a92eecf..04b9f1c3fa3e4 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -30865107cb8942ab8eaf9baf8d3aa2a6dec2643f +942db6782f4a28c55b0b75b38fd4394d0483390f From 1a6fece5fe88864f1c2c2b283d4d08fe51d6b4c4 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Fri, 7 Feb 2025 05:03:43 +0000 Subject: [PATCH 14/37] fmt --- src/tools/miri/src/bin/miri.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 56658f9bd4ca0..56ee96502b3e7 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -29,8 +29,8 @@ use std::num::NonZero; use std::ops::Range; use std::path::PathBuf; use std::str::FromStr; -use std::sync::{Arc, Once}; use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; +use std::sync::{Arc, Once}; use miri::{ BacktraceStyle, BorrowTrackerMethod, MiriConfig, MiriEntryFnType, ProvenanceMode, RetagFields, From 2a60a25c5b93fc86bf3d5ec174c1b19ee5dca0e1 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 8 Feb 2025 04:56:56 +0000 Subject: [PATCH 15/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 04b9f1c3fa3e4..dd48038e07a06 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -942db6782f4a28c55b0b75b38fd4394d0483390f +e0607238c95df66e3d25a6c17aebe18c6726fc74 From 005508ef054a40a1437e61768b52fe1149d5b98d Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 11 Feb 2025 05:05:16 +0000 Subject: [PATCH 16/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index dd48038e07a06..48458cdcbc3d2 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -e0607238c95df66e3d25a6c17aebe18c6726fc74 +6171d944aea415a3023d4262e0895aa3b18c771f From 3cee0724eea6f99c674cae99e85588cd98d3db65 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 13 Feb 2025 04:56:10 +0000 Subject: [PATCH 17/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 48458cdcbc3d2..1f451b8c0caa4 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -6171d944aea415a3023d4262e0895aa3b18c771f +6dce9f8c2d8dde4c9ea20bab981cd70229c37fdc From f1b4a1d0d135f4aa8c338b1dc98b8296c972ce69 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:24:06 +0000 Subject: [PATCH 18/37] Remove the build script for miri Setting the TARGET env var can be replaced with using rustc_session::config::host_tuple at runtime. And the check-cfg can be replaced with using the lints section in Cargo.toml. --- src/tools/miri/Cargo.toml | 4 ++++ src/tools/miri/build.rs | 10 ---------- src/tools/miri/src/machine.rs | 5 +++-- 3 files changed, 7 insertions(+), 12 deletions(-) delete mode 100644 src/tools/miri/build.rs diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index de80722fc3df1..728a7552fd8cf 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -67,6 +67,10 @@ default = ["stack-cache"] stack-cache = [] stack-cache-consistency-check = ["stack-cache"] +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(bootstrap)'] + # Be aware that this file is inside a workspace when used via the # submodule in the rustc repo. That means there are many cargo features # we cannot use, such as profiles. diff --git a/src/tools/miri/build.rs b/src/tools/miri/build.rs deleted file mode 100644 index 0918c9b13214d..0000000000000 --- a/src/tools/miri/build.rs +++ /dev/null @@ -1,10 +0,0 @@ -fn main() { - // Don't rebuild miri when nothing changed. - println!("cargo:rerun-if-changed=build.rs"); - // Re-export the TARGET environment variable so it can be accessed by miri. Needed to know the - // "host" triple inside Miri. - let target = std::env::var("TARGET").unwrap(); - println!("cargo:rustc-env=TARGET={target}"); - // Allow some cfgs. - println!("cargo::rustc-check-cfg=cfg(bootstrap)"); -} diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 6bd1076a8a848..4ece8f7895dee 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -713,12 +713,13 @@ impl<'tcx> MiriMachine<'tcx> { clock: Clock::new(config.isolated_op == IsolatedOp::Allow), #[cfg(unix)] native_lib: config.native_lib.as_ref().map(|lib_file_path| { + let host_triple = rustc_session::config::host_tuple(); let target_triple = tcx.sess.opts.target_triple.tuple(); // Check if host target == the session target. - if env!("TARGET") != target_triple { + if host_triple != target_triple { panic!( "calling external C functions in linked .so file requires host and target to be the same: host={}, target={}", - env!("TARGET"), + host_triple, target_triple, ); } From 8b3506c7d1e1535811e3e7f48892fbab401d696c Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 15 Feb 2025 05:09:23 +0000 Subject: [PATCH 19/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 1f451b8c0caa4..9f84dda4397e3 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -6dce9f8c2d8dde4c9ea20bab981cd70229c37fdc +54a0f387ea8c7bcb79b8e40c074a484d31b51990 From acdf9133aca8c5684a7c16f0c7fa0a72cac6a4a3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 16 Feb 2025 08:37:44 +0100 Subject: [PATCH 20/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 9f84dda4397e3..dbf61312b5d33 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -54a0f387ea8c7bcb79b8e40c074a484d31b51990 +500a686ba8bb1b51df7e7f8f81d286b2e20209ff From 622e8f4812b8bcd9a3501ca7d299987fff733b68 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Sun, 12 Jan 2025 23:27:35 +0100 Subject: [PATCH 21/37] apply random float error to most floating-point operations --- src/tools/miri/src/intrinsics/mod.rs | 108 ++++++-- src/tools/miri/src/math.rs | 16 ++ src/tools/miri/src/shims/foreign_items.rs | 40 ++- src/tools/miri/tests/pass/float.rs | 323 ++++++++++++++++++---- 4 files changed, 403 insertions(+), 84 deletions(-) diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index ec4fdfe0bacfe..377f3a902ed76 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -7,12 +7,13 @@ use rand::Rng; use rustc_abi::Size; use rustc_apfloat::{Float, Round}; use rustc_middle::mir; -use rustc_middle::ty::{self, FloatTy}; +use rustc_middle::ty::{self, FloatTy, ScalarInt}; use rustc_span::{Symbol, sym}; use self::atomic::EvalContextExt as _; use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count}; use self::simd::EvalContextExt as _; +use crate::math::apply_random_float_error_ulp; use crate::*; impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} @@ -206,10 +207,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } + "sqrtf32" => { + let [f] = check_intrinsic_arg_count(args)?; + let f = this.read_scalar(f)?.to_f32()?; + // Sqrt is specified to be fully precise. + let res = math::sqrt(f); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } + "sqrtf64" => { + let [f] = check_intrinsic_arg_count(args)?; + let f = this.read_scalar(f)?.to_f64()?; + // Sqrt is specified to be fully precise. + let res = math::sqrt(f); + let res = this.adjust_nan(res, &[f]); + this.write_scalar(res, dest)?; + } + #[rustfmt::skip] | "sinf32" | "cosf32" - | "sqrtf32" | "expf32" | "exp2f32" | "logf32" @@ -218,26 +235,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; - // Using host floats except for sqrt (but it's fine, these operations do not have + // Using host floats (but it's fine, these operations do not have // guaranteed precision). + let host = f.to_host(); let res = match intrinsic_name { - "sinf32" => f.to_host().sin().to_soft(), - "cosf32" => f.to_host().cos().to_soft(), - "sqrtf32" => math::sqrt(f), - "expf32" => f.to_host().exp().to_soft(), - "exp2f32" => f.to_host().exp2().to_soft(), - "logf32" => f.to_host().ln().to_soft(), - "log10f32" => f.to_host().log10().to_soft(), - "log2f32" => f.to_host().log2().to_soft(), + "sinf32" => host.sin(), + "cosf32" => host.cos(), + "expf32" => host.exp(), + "exp2f32" => host.exp2(), + "logf32" => host.ln(), + "log10f32" => host.log10(), + "log2f32" => host.log2(), _ => bug!(), }; + let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = apply_random_float_error_ulp( + this, + res, + 4 + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } #[rustfmt::skip] | "sinf64" | "cosf64" - | "sqrtf64" | "expf64" | "exp2f64" | "logf64" @@ -246,19 +270,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; - // Using host floats except for sqrt (but it's fine, these operations do not have + // Using host floats (but it's fine, these operations do not have // guaranteed precision). + let host = f.to_host(); let res = match intrinsic_name { - "sinf64" => f.to_host().sin().to_soft(), - "cosf64" => f.to_host().cos().to_soft(), - "sqrtf64" => math::sqrt(f), - "expf64" => f.to_host().exp().to_soft(), - "exp2f64" => f.to_host().exp2().to_soft(), - "logf64" => f.to_host().ln().to_soft(), - "log10f64" => f.to_host().log10().to_soft(), - "log2f64" => f.to_host().log2().to_soft(), + "sinf64" => host.sin(), + "cosf64" => host.cos(), + "expf64" => host.exp(), + "exp2f64" => host.exp2(), + "logf64" => host.ln(), + "log10f64" => host.log10(), + "log2f64" => host.log2(), _ => bug!(), }; + let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = apply_random_float_error_ulp( + this, + res, + 4 + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -316,6 +348,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powf32" => { + // FIXME: apply random relative error but without altering behaviour of powf let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; @@ -325,6 +358,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "powf64" => { + // FIXME: apply random relative error but without altering behaviour of powf let [f1, f2] = check_intrinsic_arg_count(args)?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; @@ -335,6 +369,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "powif32" => { + // FIXME: apply random relative error but without altering behaviour of powi let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; let i = this.read_scalar(i)?.to_i32()?; @@ -344,6 +379,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "powif64" => { + // FIXME: apply random relative error but without altering behaviour of powi let [f, i] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f64()?; let i = this.read_scalar(i)?.to_i32()?; @@ -372,7 +408,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => bug!(), }; let res = this.binary_op(op, &a, &b)?; - // `binary_op` already called `generate_nan` if necessary. + // `binary_op` already called `generate_nan` if needed. + // Apply a relative error of 16ULP to simulate non-deterministic precision loss + // due to optimizations. + let res = apply_random_float_error_to_imm(this, res)?; this.write_immediate(*res, dest)?; } @@ -418,11 +457,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => {} } let res = this.binary_op(op, &a, &b)?; + // This cannot be a NaN so we also don't have to apply any non-determinism. + // (Also, `binary_op` already called `generate_nan` if needed.) if !float_finite(&res)? { throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result"); } - // This cannot be a NaN so we also don't have to apply any non-determinism. - // (Also, `binary_op` already called `generate_nan` if needed.) + // Apply a relative error of 16ULP to simulate non-deterministic precision loss + // due to optimizations. + let res = apply_random_float_error_to_imm(this, res)?; this.write_immediate(*res, dest)?; } @@ -455,3 +497,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(EmulateItemResult::NeedsReturn) } } + +/// Applies a random 16ULP floating point error to `val` and returns the new value. +/// Will fail if `val` is not a floating point number. +fn apply_random_float_error_to_imm<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + val: ImmTy<'tcx>, +) -> InterpResult<'tcx, ImmTy<'tcx>> { + let scalar = val.to_scalar_int()?; + let res: ScalarInt = match val.layout.ty.kind() { + ty::Float(FloatTy::F16) => apply_random_float_error_ulp(ecx, scalar.to_f16(), 4).into(), + ty::Float(FloatTy::F32) => apply_random_float_error_ulp(ecx, scalar.to_f32(), 4).into(), + ty::Float(FloatTy::F64) => apply_random_float_error_ulp(ecx, scalar.to_f64(), 4).into(), + ty::Float(FloatTy::F128) => apply_random_float_error_ulp(ecx, scalar.to_f128(), 4).into(), + _ => bug!("intrinsic called with non-float input type"), + }; + + interp_ok(ImmTy::from_scalar_int(res, val.layout)) +} diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs index 7117f722fee89..fdd021f85394b 100644 --- a/src/tools/miri/src/math.rs +++ b/src/tools/miri/src/math.rs @@ -27,6 +27,22 @@ pub(crate) fn apply_random_float_error( (val * (F::from_u128(1).value + err).value).value } +/// [`apply_random_float_error`] gives instructions to apply a 2^N ULP error. +/// This function implements these instructions such that applying a 2^N ULP error is less error prone. +/// So for a 2^N ULP error, you would pass N as the `ulp_exponent` argument. +pub(crate) fn apply_random_float_error_ulp( + ecx: &mut crate::MiriInterpCx<'_>, + val: F, + ulp_exponent: u32, +) -> F { + let n = i32::try_from(ulp_exponent) + .expect("`err_scale_for_ulp`: exponent is too large to create an error scale"); + // we know this fits + let prec = i32::try_from(F::PRECISION).unwrap(); + let err_scale = -(prec - n - 1); + apply_random_float_error(ecx, val, err_scale) +} + pub(crate) fn sqrt(x: IeeeFloat) -> IeeeFloat { match x.category() { // preserve zero sign diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index bedc1ebdc9509..f7746ca81f290 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -765,7 +765,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "erfcf" => f_host.erfc(), _ => bug!(), }; - let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res.to_soft(), + 4 + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -788,6 +794,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "fdimf" => f1.to_host().abs_sub(f2.to_host()).to_soft(), _ => bug!(), }; + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res, + 4 + ); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } @@ -826,7 +839,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "erfc" => f_host.erfc(), _ => bug!(), }; - let res = res.to_soft(); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res.to_soft(), + 4 + ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; } @@ -849,6 +868,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "fdim" => f1.to_host().abs_sub(f2.to_host()).to_soft(), _ => bug!(), }; + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp( + this, + res, + 4 + ); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; } @@ -874,7 +900,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; - let res = this.adjust_nan(res.to_soft(), &[x]); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4); + let res = this.adjust_nan(res, &[x]); this.write_scalar(res, dest)?; } "lgamma_r" => { @@ -885,7 +914,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Using host floats (but it's fine, these operations do not have guaranteed precision). let (res, sign) = x.to_host().ln_gamma(); this.write_int(sign, &signp)?; - let res = this.adjust_nan(res.to_soft(), &[x]); + // Apply a relative error of 16ULP to introduce some non-determinism + // simulating imprecise implementations and optimizations. + let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4); + let res = this.adjust_nan(res, &[x]); this.write_scalar(res, dest)?; } diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 51cafbb3f43bf..808495c73d379 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -13,11 +13,85 @@ use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; use std::{f32, f64}; +/// Another way of checking if 2 floating-point numbers are almost equal to eachother. +/// Using `a` and `b` as floating-point numbers: +/// +/// Instead of performing a simple EPSILON check (which we used at first): +/// The absolute difference between 'a' and 'b' must not be greater than some E (10^-6, ...) +/// +/// We will now use ULP: `Units in the Last Place` or `Units of Least Precision`, +/// more specific, the difference in ULP of `a` and `b`. +/// First: The ULP of a float 'a' is the smallest possible change at 'a', so the ULP difference represents how +/// many discrete floating-point steps are needed to reach 'b' from 'a'. +/// +/// ULP(a) is the distance between the 2 closest floating-point numbers `x` and `y` around `a`, satisfying x < a < y, x != y. +/// To use this to calculate the ULP difference we have to halve it (we need it at `a`, but we just went "up" and "down", halving it gives us this ULP). +/// Then take the difference of `b` and `a` and divide it by that ULP and finally round it. +/// We know now how many floating-point changes we have to apply to `a` to get to `b`. +/// +/// So if this ULP difference is less than or equal to our chosen upper bound +/// we can say that `a` and `b` are approximately equal, because they lie "close" enough to each other to be considered equal. +/// +/// Note: We can see that checking `a` and `b` with different signs has no meaning, but we should not forget +/// -0.0 and +0.0. +/// +/// Essentially ULP can be seen as a distance metric of floating-point numbers, but with +/// the same amount of "spacing" between all consecutive representable values. So even though 2 very large floating point numbers +/// have a large value difference, their ULP can still be 1, so they are still "approximatly equal", +/// but the EPSILON check would have failed. +/// +fn approx_eq_check( + actual: F, + expected: F, + allowed_ulp: F::Int, +) -> Result<(), NotApproxEq> +where + F::Int: PartialOrd, +{ + let actual_signum = actual.signum(); + let expected_signum = expected.signum(); + + if actual_signum != expected_signum { + // Floats with different signs must both be 0. + if actual != expected { + return Err(NotApproxEq::SignsDiffer); + } + } else { + let ulp = (expected.next_up() - expected.next_down()).halve(); + let ulp_diff = ((actual - expected) / ulp).round().as_int(); + + if ulp_diff > allowed_ulp { + return Err(NotApproxEq::UlpFail(ulp_diff)); + } + } + Ok(()) +} + +/// Give more context to execution and result of [`approx_eq_check`]. +enum NotApproxEq { + SignsDiffer, + + /// Contains the actual ulp value calculated. + UlpFail(F::Int), +} + macro_rules! assert_approx_eq { - ($a:expr, $b:expr) => {{ - let (a, b) = (&$a, &$b); - assert!((*a - *b).abs() < 1.0e-6, "{} is not approximately equal to {}", *a, *b); + ($a:expr, $b:expr, $ulp:expr) => {{ + let (a, b) = ($a, $b); + let allowed_ulp = $ulp; + match approx_eq_check(a, b, allowed_ulp) { + Err(NotApproxEq::SignsDiffer) => + panic!("{a:?} is not approximately equal to {b:?}: signs differ"), + Err(NotApproxEq::UlpFail(actual_ulp)) => + panic!("{a:?} is not approximately equal to {b:?}\nulp diff: {actual_ulp} > {allowed_ulp}"), + Ok(_) => {} + }; }}; + + ($a:expr, $b: expr) => { + // accept up to 64ULP (16ULP for host floats and 16ULP for miri artificial error and 32 for any rounding errors) + assert_approx_eq!($a, $b, 64); + }; } fn main() { @@ -27,15 +101,23 @@ fn main() { ops(); nan_casts(); rounding(); - mul_add(); libm(); + mul_add(); test_fast(); test_algebraic(); test_fmuladd(); test_min_max_nondet(); + test_non_determinism(); } -trait Float: Copy + PartialEq + Debug { +trait Float: + Copy + + PartialEq + + Debug + + std::ops::Sub + + std::cmp::PartialOrd + + std::ops::Div +{ /// The unsigned integer with the same bit width as this float type Int: Copy + PartialEq + LowerHex + Debug; const BITS: u32 = size_of::() as u32 * 8; @@ -49,6 +131,15 @@ trait Float: Copy + PartialEq + Debug { const EXPONENT_BIAS: u32 = Self::EXPONENT_SAT >> 1; fn to_bits(self) -> Self::Int; + + // to make "approx_eq_check" generic + fn signum(self) -> Self; + fn next_up(self) -> Self; + fn next_down(self) -> Self; + fn round(self) -> Self; + // self / 2 + fn halve(self) -> Self; + fn as_int(self) -> Self::Int; } macro_rules! impl_float { @@ -61,6 +152,27 @@ macro_rules! impl_float { fn to_bits(self) -> Self::Int { self.to_bits() } + + fn signum(self) -> Self { + self.signum() + } + fn next_up(self) -> Self { + self.next_up() + } + fn next_down(self) -> Self { + self.next_down() + } + fn round(self) -> Self { + self.round() + } + + fn halve(self) -> Self { + self / 2.0 + } + + fn as_int(self) -> Self::Int { + self as Self::Int + } } }; } @@ -1005,8 +1117,8 @@ pub fn libm() { #[allow(deprecated)] { - assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0); - assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0); + assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0f32); + assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0f64); } assert_approx_eq!(27.0f32.cbrt(), 3.0f32); @@ -1023,30 +1135,30 @@ pub fn libm() { assert_approx_eq!(0f32.sin(), 0f32); assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64); - assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5); - assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5f32); + assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5f64); assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4); assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4); assert_approx_eq!(1.0f32.sinh(), 1.1752012f32); - assert_approx_eq!(1.0f64.sinh(), 1.1752012f64); + assert_approx_eq!(1.0f64.sinh(), 1.1752011936438014f64); assert_approx_eq!(2.0f32.asinh(), 1.443635475178810342493276740273105f32); assert_approx_eq!((-2.0f64).asinh(), -1.443635475178810342493276740273105f64); assert_approx_eq!(0f32.cos(), 1f32); assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64); - assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5); - assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5f32); + assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5f64); assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4); assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4); assert_approx_eq!(1.0f32.cosh(), 1.54308f32); - assert_approx_eq!(1.0f64.cosh(), 1.54308f64); + assert_approx_eq!(1.0f64.cosh(), 1.5430806348152437f64); assert_approx_eq!(2.0f32.acosh(), 1.31695789692481670862504634730796844f32); assert_approx_eq!(3.0f64.acosh(), 1.76274717403908605046521864995958461f64); assert_approx_eq!(1.0f32.tan(), 1.557408f32); - assert_approx_eq!(1.0f64.tan(), 1.557408f64); + assert_approx_eq!(1.0f64.tan(), 1.5574077246549023f64); assert_approx_eq!(1.0_f32, 1.0_f32.tan().atan()); assert_approx_eq!(1.0_f64, 1.0_f64.tan().atan()); assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32); @@ -1063,8 +1175,8 @@ pub fn libm() { assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32); assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64); - assert_approx_eq!(5.0f32.gamma(), 24.0); - assert_approx_eq!(5.0f64.gamma(), 24.0); + assert_approx_eq!(5.0f32.gamma(), 24.0f32); + assert_approx_eq!(5.0f64.gamma(), 24.0f64); assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32::consts::PI.sqrt()); assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64::consts::PI.sqrt()); @@ -1091,11 +1203,11 @@ fn test_fast() { pub fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1103,11 +1215,11 @@ fn test_fast() { pub fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1115,11 +1227,11 @@ fn test_fast() { pub fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1127,11 +1239,11 @@ fn test_fast() { pub fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation unsafe { - assert_eq!(fadd_fast(a, b), a + b); - assert_eq!(fsub_fast(a, b), a - b); - assert_eq!(fmul_fast(a, b), a * b); - assert_eq!(fdiv_fast(a, b), a / b); - assert_eq!(frem_fast(a, b), a % b); + assert_approx_eq!(fadd_fast(a, b), a + b); + assert_approx_eq!(fsub_fast(a, b), a - b); + assert_approx_eq!(fmul_fast(a, b), a * b); + assert_approx_eq!(fdiv_fast(a, b), a / b); + assert_approx_eq!(frem_fast(a, b), a % b); } } @@ -1153,41 +1265,41 @@ fn test_algebraic() { #[inline(never)] pub fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } #[inline(never)] pub fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } #[inline(never)] pub fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } #[inline(never)] pub fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation - assert_eq!(fadd_algebraic(a, b), a + b); - assert_eq!(fsub_algebraic(a, b), a - b); - assert_eq!(fmul_algebraic(a, b), a * b); - assert_eq!(fdiv_algebraic(a, b), a / b); - assert_eq!(frem_algebraic(a, b), a % b); + assert_approx_eq!(fadd_algebraic(a, b), a + b); + assert_approx_eq!(fsub_algebraic(a, b), a - b); + assert_approx_eq!(fmul_algebraic(a, b), a * b); + assert_approx_eq!(fdiv_algebraic(a, b), a / b); + assert_approx_eq!(frem_algebraic(a, b), a % b); } test_operations_f16(11., 2.); @@ -1245,3 +1357,102 @@ fn test_min_max_nondet() { ensure_both(|| f128::min(0.0, -0.0).is_sign_positive()); ensure_both(|| f128::max(0.0, -0.0).is_sign_positive()); } + +fn test_non_determinism() { + use std::intrinsics::{ + fadd_algebraic, fadd_fast, fdiv_algebraic, fdiv_fast, fmul_algebraic, fmul_fast, + frem_algebraic, frem_fast, fsub_algebraic, fsub_fast, + }; + use std::{f32, f64}; + // TODO: Also test powi and powf when the non-determinism is implemented for them + + /// Ensure that the operation is non-deterministic + #[track_caller] + fn ensure_nondet(f: impl Fn() -> T) { + let rounds = 16; + let first = f(); + for _ in 1..rounds { + if f() != first { + // We saw two different values! + return; + } + } + // We saw the same thing N times. + panic!("expected non-determinism, got {rounds} times the same result: {first:?}"); + } + + macro_rules! test_operations_f { + ($a:expr, $b:expr) => { + ensure_nondet(|| fadd_algebraic($a, $b)); + ensure_nondet(|| fsub_algebraic($a, $b)); + ensure_nondet(|| fmul_algebraic($a, $b)); + ensure_nondet(|| fdiv_algebraic($a, $b)); + ensure_nondet(|| frem_algebraic($a, $b)); + + unsafe { + ensure_nondet(|| fadd_fast($a, $b)); + ensure_nondet(|| fsub_fast($a, $b)); + ensure_nondet(|| fmul_fast($a, $b)); + ensure_nondet(|| fdiv_fast($a, $b)); + ensure_nondet(|| frem_fast($a, $b)); + } + }; + } + + pub fn test_operations_f16(a: f16, b: f16) { + test_operations_f!(a, b); + } + pub fn test_operations_f32(a: f32, b: f32) { + test_operations_f!(a, b); + ensure_nondet(|| a.log(b)); + ensure_nondet(|| a.exp()); + ensure_nondet(|| 10f32.exp2()); + ensure_nondet(|| f32::consts::E.ln()); + ensure_nondet(|| 1f32.ln_1p()); + ensure_nondet(|| 10f32.log10()); + ensure_nondet(|| 8f32.log2()); + ensure_nondet(|| 27.0f32.cbrt()); + ensure_nondet(|| 3.0f32.hypot(4.0f32)); + ensure_nondet(|| 1f32.sin()); + ensure_nondet(|| 0f32.cos()); + ensure_nondet(|| 1.0f32.sinh()); + ensure_nondet(|| 1.0f32.asinh()); + ensure_nondet(|| 1.0f32.cosh()); + ensure_nondet(|| 2.0f32.acosh()); + ensure_nondet(|| 1.0f32.tan()); + ensure_nondet(|| 1.0f32.tanh()); + ensure_nondet(|| 1.0f32.atan2(2.0f32)); + ensure_nondet(|| 0.5f32.atanh()); + ensure_nondet(|| 5.0f32.gamma()); + } + pub fn test_operations_f64(a: f64, b: f64) { + test_operations_f!(a, b); + ensure_nondet(|| a.log(b)); + ensure_nondet(|| a.exp()); + ensure_nondet(|| 50f64.exp2()); + ensure_nondet(|| 3f64.ln()); + ensure_nondet(|| 1f64.ln_1p()); + ensure_nondet(|| f64::consts::E.log10()); + ensure_nondet(|| f64::consts::E.log2()); + ensure_nondet(|| 1f64.sin()); + ensure_nondet(|| 0f64.cos()); + ensure_nondet(|| 27.0f64.cbrt()); + ensure_nondet(|| 3.0f64.hypot(4.0f64)); + ensure_nondet(|| 1.0f64.sinh()); + ensure_nondet(|| 1.0f64.asinh()); + ensure_nondet(|| 1.0f64.cosh()); + ensure_nondet(|| 3.0f64.acosh()); + ensure_nondet(|| 1.0f64.tan()); + ensure_nondet(|| 1.0f64.tanh()); + ensure_nondet(|| 0.5f64.atanh()); + ensure_nondet(|| 5.0f64.gamma()); + } + pub fn test_operations_f128(a: f128, b: f128) { + test_operations_f!(a, b); + } + + test_operations_f16(5., 7.); + test_operations_f32(12., 5.); + test_operations_f64(19., 11.); + test_operations_f128(25., 18.); +} From 1849256a267186949e922c657b15bb9f2e893498 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 16 Feb 2025 19:07:21 +0100 Subject: [PATCH 22/37] add erf and erfc to nondet tests, and reduce how much we're changing the float test --- src/tools/miri/src/intrinsics/mod.rs | 21 ++-- src/tools/miri/src/shims/foreign_items.rs | 14 ++- src/tools/miri/tests/pass/float.rs | 138 +++++----------------- 3 files changed, 48 insertions(+), 125 deletions(-) diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 377f3a902ed76..a8655aee0ca21 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -254,7 +254,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = apply_random_float_error_ulp( this, res, - 4 + 4, // log2(16) ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; @@ -289,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = apply_random_float_error_ulp( this, res, - 4 + 4, // log2(16) ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; @@ -411,7 +411,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `binary_op` already called `generate_nan` if needed. // Apply a relative error of 16ULP to simulate non-deterministic precision loss // due to optimizations. - let res = apply_random_float_error_to_imm(this, res)?; + let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?; this.write_immediate(*res, dest)?; } @@ -464,7 +464,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Apply a relative error of 16ULP to simulate non-deterministic precision loss // due to optimizations. - let res = apply_random_float_error_to_imm(this, res)?; + let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?; this.write_immediate(*res, dest)?; } @@ -503,13 +503,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn apply_random_float_error_to_imm<'tcx>( ecx: &mut MiriInterpCx<'tcx>, val: ImmTy<'tcx>, + ulp_exponent: u32, ) -> InterpResult<'tcx, ImmTy<'tcx>> { let scalar = val.to_scalar_int()?; let res: ScalarInt = match val.layout.ty.kind() { - ty::Float(FloatTy::F16) => apply_random_float_error_ulp(ecx, scalar.to_f16(), 4).into(), - ty::Float(FloatTy::F32) => apply_random_float_error_ulp(ecx, scalar.to_f32(), 4).into(), - ty::Float(FloatTy::F64) => apply_random_float_error_ulp(ecx, scalar.to_f64(), 4).into(), - ty::Float(FloatTy::F128) => apply_random_float_error_ulp(ecx, scalar.to_f128(), 4).into(), + ty::Float(FloatTy::F16) => + apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(), + ty::Float(FloatTy::F32) => + apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(), + ty::Float(FloatTy::F64) => + apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(), + ty::Float(FloatTy::F128) => + apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(), _ => bug!("intrinsic called with non-float input type"), }; diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index f7746ca81f290..ec8f6663822d0 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -770,7 +770,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = math::apply_random_float_error_ulp( this, res.to_soft(), - 4 + 4, // log2(16) ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; @@ -799,7 +799,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = math::apply_random_float_error_ulp( this, res, - 4 + 4, // log2(16) ); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; @@ -844,7 +844,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = math::apply_random_float_error_ulp( this, res.to_soft(), - 4 + 4, // log2(16) ); let res = this.adjust_nan(res, &[f]); this.write_scalar(res, dest)?; @@ -873,7 +873,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = math::apply_random_float_error_ulp( this, res, - 4 + 4, // log2(16) ); let res = this.adjust_nan(res, &[f1, f2]); this.write_scalar(res, dest)?; @@ -902,7 +902,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(sign, &signp)?; // Apply a relative error of 16ULP to introduce some non-determinism // simulating imprecise implementations and optimizations. - let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4); + let res = + math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */); let res = this.adjust_nan(res, &[x]); this.write_scalar(res, dest)?; } @@ -916,7 +917,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_int(sign, &signp)?; // Apply a relative error of 16ULP to introduce some non-determinism // simulating imprecise implementations and optimizations. - let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4); + let res = + math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */); let res = this.adjust_nan(res, &[x]); this.write_scalar(res, dest)?; } diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 808495c73d379..0eb7d6e830957 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -13,78 +13,27 @@ use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; use std::{f32, f64}; -/// Another way of checking if 2 floating-point numbers are almost equal to eachother. -/// Using `a` and `b` as floating-point numbers: +/// Compare the two floats, allowing for $ulp many ULPs of error. /// -/// Instead of performing a simple EPSILON check (which we used at first): -/// The absolute difference between 'a' and 'b' must not be greater than some E (10^-6, ...) -/// -/// We will now use ULP: `Units in the Last Place` or `Units of Least Precision`, -/// more specific, the difference in ULP of `a` and `b`. -/// First: The ULP of a float 'a' is the smallest possible change at 'a', so the ULP difference represents how -/// many discrete floating-point steps are needed to reach 'b' from 'a'. -/// -/// ULP(a) is the distance between the 2 closest floating-point numbers `x` and `y` around `a`, satisfying x < a < y, x != y. -/// To use this to calculate the ULP difference we have to halve it (we need it at `a`, but we just went "up" and "down", halving it gives us this ULP). -/// Then take the difference of `b` and `a` and divide it by that ULP and finally round it. -/// We know now how many floating-point changes we have to apply to `a` to get to `b`. -/// -/// So if this ULP difference is less than or equal to our chosen upper bound -/// we can say that `a` and `b` are approximately equal, because they lie "close" enough to each other to be considered equal. -/// -/// Note: We can see that checking `a` and `b` with different signs has no meaning, but we should not forget -/// -0.0 and +0.0. +/// ULP means "Units in the Last Place" or "Units of Least Precision". +/// The ULP of a float `a`` is the smallest possible change at `a`, so the ULP difference represents how +/// many discrete floating-point steps are needed to reach the actual value from the expected value. /// /// Essentially ULP can be seen as a distance metric of floating-point numbers, but with /// the same amount of "spacing" between all consecutive representable values. So even though 2 very large floating point numbers /// have a large value difference, their ULP can still be 1, so they are still "approximatly equal", /// but the EPSILON check would have failed. -/// -fn approx_eq_check( - actual: F, - expected: F, - allowed_ulp: F::Int, -) -> Result<(), NotApproxEq> -where - F::Int: PartialOrd, -{ - let actual_signum = actual.signum(); - let expected_signum = expected.signum(); - - if actual_signum != expected_signum { - // Floats with different signs must both be 0. - if actual != expected { - return Err(NotApproxEq::SignsDiffer); - } - } else { - let ulp = (expected.next_up() - expected.next_down()).halve(); - let ulp_diff = ((actual - expected) / ulp).round().as_int(); - - if ulp_diff > allowed_ulp { - return Err(NotApproxEq::UlpFail(ulp_diff)); - } - } - Ok(()) -} - -/// Give more context to execution and result of [`approx_eq_check`]. -enum NotApproxEq { - SignsDiffer, - - /// Contains the actual ulp value calculated. - UlpFail(F::Int), -} - macro_rules! assert_approx_eq { ($a:expr, $b:expr, $ulp:expr) => {{ - let (a, b) = ($a, $b); - let allowed_ulp = $ulp; - match approx_eq_check(a, b, allowed_ulp) { - Err(NotApproxEq::SignsDiffer) => - panic!("{a:?} is not approximately equal to {b:?}: signs differ"), - Err(NotApproxEq::UlpFail(actual_ulp)) => - panic!("{a:?} is not approximately equal to {b:?}\nulp diff: {actual_ulp} > {allowed_ulp}"), - Ok(_) => {} + let (actual, expected) = ($a, $b); + let allowed_ulp_diff = $ulp; + let _force_same_type = actual == expected; + // Approximate the ULP by taking half the distance between the number one place "up" + // and the number one place "down". + let ulp = (expected.next_up() - expected.next_down()) / 2.0; + let ulp_diff = ((actual - expected) / ulp).abs().round() as i32; + if ulp_diff > allowed_ulp_diff { + panic!("{actual:?} is not approximately equal to {expected:?}\ndifference in ULP: {ulp_diff} > {allowed_ulp_diff}"); }; }}; @@ -101,8 +50,8 @@ fn main() { ops(); nan_casts(); rounding(); - libm(); mul_add(); + libm(); test_fast(); test_algebraic(); test_fmuladd(); @@ -110,14 +59,7 @@ fn main() { test_non_determinism(); } -trait Float: - Copy - + PartialEq - + Debug - + std::ops::Sub - + std::cmp::PartialOrd - + std::ops::Div -{ +trait Float: Copy + PartialEq + Debug { /// The unsigned integer with the same bit width as this float type Int: Copy + PartialEq + LowerHex + Debug; const BITS: u32 = size_of::() as u32 * 8; @@ -131,15 +73,6 @@ trait Float: const EXPONENT_BIAS: u32 = Self::EXPONENT_SAT >> 1; fn to_bits(self) -> Self::Int; - - // to make "approx_eq_check" generic - fn signum(self) -> Self; - fn next_up(self) -> Self; - fn next_down(self) -> Self; - fn round(self) -> Self; - // self / 2 - fn halve(self) -> Self; - fn as_int(self) -> Self::Int; } macro_rules! impl_float { @@ -152,27 +85,6 @@ macro_rules! impl_float { fn to_bits(self) -> Self::Int { self.to_bits() } - - fn signum(self) -> Self { - self.signum() - } - fn next_up(self) -> Self { - self.next_up() - } - fn next_down(self) -> Self { - self.next_down() - } - fn round(self) -> Self { - self.round() - } - - fn halve(self) -> Self { - self / 2.0 - } - - fn as_int(self) -> Self::Int { - self as Self::Int - } } }; } @@ -1117,8 +1029,8 @@ pub fn libm() { #[allow(deprecated)] { - assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0f32); - assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0f64); + assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0); + assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0); } assert_approx_eq!(27.0f32.cbrt(), 3.0f32); @@ -1135,8 +1047,8 @@ pub fn libm() { assert_approx_eq!(0f32.sin(), 0f32); assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64); - assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5f32); - assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5f64); + assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5); + assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5); assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4); assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4); @@ -1147,8 +1059,8 @@ pub fn libm() { assert_approx_eq!(0f32.cos(), 1f32); assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64); - assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5f32); - assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5f64); + assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5); + assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5); assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4); assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4); @@ -1175,8 +1087,8 @@ pub fn libm() { assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32); assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64); - assert_approx_eq!(5.0f32.gamma(), 24.0f32); - assert_approx_eq!(5.0f64.gamma(), 24.0f64); + assert_approx_eq!(5.0f32.gamma(), 24.0); + assert_approx_eq!(5.0f64.gamma(), 24.0); assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32::consts::PI.sqrt()); assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64::consts::PI.sqrt()); @@ -1424,6 +1336,8 @@ fn test_non_determinism() { ensure_nondet(|| 1.0f32.atan2(2.0f32)); ensure_nondet(|| 0.5f32.atanh()); ensure_nondet(|| 5.0f32.gamma()); + ensure_nondet(|| 5.0f32.erf()); + ensure_nondet(|| 5.0f32.erfc()); } pub fn test_operations_f64(a: f64, b: f64) { test_operations_f!(a, b); @@ -1446,6 +1360,8 @@ fn test_non_determinism() { ensure_nondet(|| 1.0f64.tanh()); ensure_nondet(|| 0.5f64.atanh()); ensure_nondet(|| 5.0f64.gamma()); + ensure_nondet(|| 5.0f64.erf()); + ensure_nondet(|| 5.0f64.erfc()); } pub fn test_operations_f128(a: f128, b: f128) { test_operations_f!(a, b); From 639b7134df7a43d01d2c9964eab4c9d8881d3372 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Mon, 17 Feb 2025 05:08:57 +0000 Subject: [PATCH 23/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index dbf61312b5d33..0ea62d4d85026 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -500a686ba8bb1b51df7e7f8f81d286b2e20209ff +d1fb81e8dd5354ddf7cb334d5a234cab7f64b3bb From 20eb224a559c3bd74ed97efc7c98c0a25f5b5225 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 19 Feb 2025 05:06:01 +0000 Subject: [PATCH 24/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 0ea62d4d85026..a2a2f28ea71c9 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -d1fb81e8dd5354ddf7cb334d5a234cab7f64b3bb +17c1c329a5512d718b67ef6797538b154016cd34 From 72538180a665485ef0f14fbdba7b0d9bf09e0044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Feb 2025 08:55:15 +0100 Subject: [PATCH 25/37] Remove GitHub job summaries They don't seem to be used by miri contributors, and they pollute job summaries in rust-lang/rust. --- src/tools/miri/tests/ui.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 3bc953c3a5fbf..0e8db0f3f600a 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -217,15 +217,10 @@ fn run_tests( ui_test::default_file_filter, // This could be used to overwrite the `Config` on a per-test basis. |_, _| {}, - ( - match args.format { - Format::Terse => status_emitter::Text::quiet(), - Format::Pretty => status_emitter::Text::verbose(), - }, - status_emitter::Gha:: { - name: format!("{mode:?} {path} ({target})"), - }, - ), + match args.format { + Format::Terse => status_emitter::Text::quiet(), + Format::Pretty => status_emitter::Text::verbose(), + }, ) } From 30fc90976a1683df4416d30a37f3286d6cafb8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Feb 2025 12:56:35 +0100 Subject: [PATCH 26/37] Add explanation comment Co-authored-by: Ralf Jung --- src/tools/miri/tests/ui.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index 0e8db0f3f600a..85ce38f57d637 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -217,6 +217,7 @@ fn run_tests( ui_test::default_file_filter, // This could be used to overwrite the `Config` on a per-test basis. |_, _| {}, + // No GHA output as that would also show in the main rustc repo. match args.format { Format::Terse => status_emitter::Text::quiet(), Format::Pretty => status_emitter::Text::verbose(), From 2335fd69bc7a124eb37e366c3e4dc78a4a6774f6 Mon Sep 17 00:00:00 2001 From: tiif Date: Fri, 21 Feb 2025 00:06:50 +0800 Subject: [PATCH 27/37] Resolve some FIXME from socketpair test --- src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs index bbf0e2159530e..9163fd3d06fa6 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs @@ -88,8 +88,7 @@ fn test_socketpair_threaded() { assert_eq!(res, 5); assert_eq!(buf, "abcde".as_bytes()); }); - // FIXME: we should yield here once blocking is implemented. - //thread::yield_now(); + thread::yield_now(); let data = "abcde".as_bytes().as_ptr(); let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) }; assert_eq!(res, 5); @@ -97,14 +96,11 @@ fn test_socketpair_threaded() { // Read and write from different direction let thread2 = thread::spawn(move || { - // FIXME: we should yield here once blocking is implemented. - //thread::yield_now(); + thread::yield_now(); let data = "12345".as_bytes().as_ptr(); let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) }; assert_eq!(res, 5); }); - // FIXME: we should not yield here once blocking is implemented. - thread::yield_now(); let mut buf: [u8; 5] = [0; 5]; let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; assert_eq!(res, 5); From ad0810b73b0df9f727f2693391bdcb327dccf649 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:09:10 +0000 Subject: [PATCH 28/37] Implement vpmaxq_u8 on aarch64 --- src/tools/miri/src/shims/aarch64.rs | 77 +++++++++++++++++++++++ src/tools/miri/src/shims/foreign_items.rs | 18 ++---- src/tools/miri/src/shims/mod.rs | 1 + 3 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/tools/miri/src/shims/aarch64.rs diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs new file mode 100644 index 0000000000000..4751064a1b928 --- /dev/null +++ b/src/tools/miri/src/shims/aarch64.rs @@ -0,0 +1,77 @@ +use rustc_middle::mir::BinOp; +use rustc_middle::ty::Ty; +use rustc_span::Symbol; +use rustc_target::callconv::{Conv, FnAbi}; + +use crate::*; + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn emulate_aarch64_intrinsic( + &mut self, + link_name: Symbol, + abi: &FnAbi<'tcx, Ty<'tcx>>, + args: &[OpTy<'tcx>], + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + // Prefix should have already been checked. + let unprefixed_name = link_name.as_str().strip_prefix("llvm.aarch64.").unwrap(); + match unprefixed_name { + "isb" => { + let [arg] = this.check_shim(abi, Conv::C, link_name, args)?; + let arg = this.read_scalar(arg)?.to_i32()?; + match arg { + // SY ("full system scope") + 15 => { + this.yield_active_thread(); + } + _ => { + throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg); + } + } + } + + // Used to implement the vpmaxq_u8 function. + // Folding maximum of adjacent pairs. + // https://developer.arm.com/architectures/instruction-sets/intrinsics/vpmaxq_u8 + "neon.umaxp.v16i8" => { + let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + + let (left, left_len) = this.project_to_simd(left)?; + let (right, right_len) = this.project_to_simd(right)?; + let (dest, lane_count) = this.project_to_simd(dest)?; + assert_eq!(left_len, right_len); + assert_eq!(lane_count, left_len); + + for lane_idx in 0..lane_count { + let src = if lane_idx < (lane_count / 2) { &left } else { &right }; + #[allow(clippy::arithmetic_side_effects)] + let src_idx = lane_idx % (lane_count / 2); + + #[allow(clippy::arithmetic_side_effects)] + let lhs_lane = this.read_immediate(&this.project_index(src, src_idx * 2)?)?; + #[allow(clippy::arithmetic_side_effects)] + let rhs_lane = + this.read_immediate(&this.project_index(src, src_idx * 2 + 1)?)?; + + let res_lane = if this + .binary_op(BinOp::Gt, &lhs_lane, &rhs_lane)? + .to_scalar() + .to_bool()? + { + lhs_lane + } else { + rhs_lane + }; + + let dest = this.project_index(&dest, lane_idx)?; + this.write_immediate(*res_lane, &dest)?; + } + } + + _ => return interp_ok(EmulateItemResult::NotSupported), + } + interp_ok(EmulateItemResult::NeedsReturn) + } +} diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 97bfb04f1f471..011eeac87bc79 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -939,20 +939,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this, link_name, abi, args, dest, ); } - // FIXME: Move these to an `arm` submodule. - "llvm.aarch64.isb" if this.tcx.sess.target.arch == "aarch64" => { - let [arg] = this.check_shim(abi, Conv::C, link_name, args)?; - let arg = this.read_scalar(arg)?.to_i32()?; - match arg { - // SY ("full system scope") - 15 => { - this.yield_active_thread(); - } - _ => { - throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg); - } - } + name if name.starts_with("llvm.aarch64.") && this.tcx.sess.target.arch == "aarch64" => { + return shims::aarch64::EvalContextExt::emulate_aarch64_intrinsic( + this, link_name, abi, args, dest, + ); } + // FIXME: Move this to an `arm` submodule. "llvm.arm.hint" if this.tcx.sess.target.arch == "arm" => { let [arg] = this.check_shim(abi, Conv::C, link_name, args)?; let arg = this.read_scalar(arg)?.to_i32()?; diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index 61681edcf762c..b498551ace34c 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -1,5 +1,6 @@ #![warn(clippy::arithmetic_side_effects)] +mod aarch64; mod alloc; mod backtrace; mod files; From eb4720dc8e09476d99a846b5e9f78137038e62aa Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:46:19 +0000 Subject: [PATCH 29/37] Fix review comments --- src/tools/miri/src/shims/aarch64.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs index 4751064a1b928..fc6e3b303d1c9 100644 --- a/src/tools/miri/src/shims/aarch64.rs +++ b/src/tools/miri/src/shims/aarch64.rs @@ -46,15 +46,15 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { for lane_idx in 0..lane_count { let src = if lane_idx < (lane_count / 2) { &left } else { &right }; - #[allow(clippy::arithmetic_side_effects)] - let src_idx = lane_idx % (lane_count / 2); + let src_idx = lane_idx.strict_rem(lane_count / 2); - #[allow(clippy::arithmetic_side_effects)] - let lhs_lane = this.read_immediate(&this.project_index(src, src_idx * 2)?)?; - #[allow(clippy::arithmetic_side_effects)] - let rhs_lane = - this.read_immediate(&this.project_index(src, src_idx * 2 + 1)?)?; + let lhs_lane = + this.read_immediate(&this.project_index(src, src_idx.strict_mul(2))?)?; + let rhs_lane = this.read_immediate( + &this.project_index(src, src_idx.strict_mul(2).strict_add(1))?, + )?; + // Compute `if lhs > rhs { lhs } else { rhs }`, i.e., `max`. let res_lane = if this .binary_op(BinOp::Gt, &lhs_lane, &rhs_lane)? .to_scalar() From d0dc36eccca3f058956702f2281110119bbbfd5c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:07:24 +0000 Subject: [PATCH 30/37] Add tests --- .../shims/aarch64/intrinsics-aarch64-neon.rs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs diff --git a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs new file mode 100644 index 0000000000000..84485dbad8c9e --- /dev/null +++ b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs @@ -0,0 +1,40 @@ +// We're testing aarch64 target specific features +//@only-target: aarch64 +//@compile-flags: -C target-feature=+neon + +use std::arch::aarch64::*; +use std::arch::is_aarch64_feature_detected; + +fn main() { + assert!(is_aarch64_feature_detected!("neon")); + + unsafe { + test_neon(); + } +} + +#[target_feature(enable = "neon")] +unsafe fn test_neon() { + // Adapted from library/stdarch/crates/core_arch/src/aarch64/neon/mod.rs + unsafe fn test_vpmaxq_u8() { + let a = vld1q_u8([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8].as_ptr()); + let b = vld1q_u8([0, 3, 2, 5, 4, 7, 6, 9, 0, 3, 2, 5, 4, 7, 6, 9].as_ptr()); + let e = [2, 4, 6, 8, 2, 4, 6, 8, 3, 5, 7, 9, 3, 5, 7, 9]; + let mut r = [0; 16]; + vst1q_u8(r.as_mut_ptr(), vpmaxq_u8(a, b)); + assert_eq!(r, e); + } + test_vpmaxq_u8(); + + unsafe fn test_vpmaxq_u8_is_unsigned() { + let a = vld1q_u8( + [255, 0, 253, 252, 251, 250, 249, 248, 255, 254, 253, 252, 251, 250, 249, 248].as_ptr(), + ); + let b = vld1q_u8([254, 3, 2, 5, 4, 7, 6, 9, 0, 3, 2, 5, 4, 7, 6, 9].as_ptr()); + let e = [255, 253, 251, 249, 255, 253, 251, 249, 254, 5, 7, 9, 3, 5, 7, 9]; + let mut r = [0; 16]; + vst1q_u8(r.as_mut_ptr(), vpmaxq_u8(a, b)); + assert_eq!(r, e); + } + test_vpmaxq_u8_is_unsigned(); +} From e4de3acb971ce3c22d2cea43a8664bc570a9b3a8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Feb 2025 07:50:28 +0100 Subject: [PATCH 31/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a2a2f28ea71c9..6cd39fabeeeef 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -17c1c329a5512d718b67ef6797538b154016cd34 +e0be1a02626abef2878cb7f4aaef7ae409477112 From 561dce772fed55a73f474b2e3f79c43981ea084f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Feb 2025 10:57:57 +0100 Subject: [PATCH 32/37] sanity-check for HOST_TARGET --- src/tools/miri/ci/ci.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index 5583030b490ae..3327ad17c44ef 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -euo pipefail +set -eu function begingroup { echo "::group::$@" @@ -11,6 +11,17 @@ function endgroup { echo "::endgroup" } +begingroup "Sanity-check environment" + +# Ensure the HOST_TARGET is what it should be. +if ! rustc -vV | grep -q "^host: $HOST_TARGET\$"; then + echo "This runner should be using host target $HOST_TARGET but rustc disagrees:" + rustc -vV + exit 1 +fi + +endgroup + begingroup "Building Miri" # Global configuration From 61a25fd270b9a0e172fdc3e390c0cdad1e5c2dbb Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Feb 2025 13:27:18 +0100 Subject: [PATCH 33/37] make sure we install the toolchain for the intended host target --- src/tools/miri/.github/workflows/ci.yml | 2 ++ src/tools/miri/.github/workflows/setup/action.yml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 81df0964d5912..59bae513a58f4 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/workflows/setup + with: + toolchain_flags: "--host ${{ matrix.host_target }}" # The `style` job only runs on Linux; this makes sure the Windows-host-specific # code is also covered by clippy. diff --git a/src/tools/miri/.github/workflows/setup/action.yml b/src/tools/miri/.github/workflows/setup/action.yml index bf5749a7b17eb..146b432171e1d 100644 --- a/src/tools/miri/.github/workflows/setup/action.yml +++ b/src/tools/miri/.github/workflows/setup/action.yml @@ -1,5 +1,9 @@ name: "Miri CI setup" description: "Sets up Miri CI" +inputs: + toolchain_flags: + required: false + default: '' runs: using: "composite" steps: @@ -45,7 +49,7 @@ runs: echo "Building against latest rustc git version" git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1 > rust-version fi - ./miri toolchain + ./miri toolchain ${{ inputs.toolchain_flags }} shell: bash - name: Show Rust version (miri toolchain) From b4bb011cbcf73bdecc499562346cb8a0f33b44bb Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Feb 2025 13:43:52 +0100 Subject: [PATCH 34/37] add missing float non-determinism tests and skip some on i686-pc-windows-msvc that are internally implemented via f64 --- src/tools/miri/tests/pass/float.rs | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 0eb7d6e830957..d8792a6c30268 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -1327,15 +1327,24 @@ fn test_non_determinism() { ensure_nondet(|| 3.0f32.hypot(4.0f32)); ensure_nondet(|| 1f32.sin()); ensure_nondet(|| 0f32.cos()); - ensure_nondet(|| 1.0f32.sinh()); + // On i686-pc-windows-msvc , these functions are implemented by calling the `f64` version, + // which means the little rounding errors Miri introduces are discard by the cast down to `f32`. + // Just skip the test for them. + if !cfg!(all(target_os = "windows", target_env = "msvc", target_arch = "x86")) { + ensure_nondet(|| 1.0f32.tan()); + ensure_nondet(|| 1.0f32.asin()); + ensure_nondet(|| 5.0f32.acos()); + ensure_nondet(|| 1.0f32.atan()); + ensure_nondet(|| 1.0f32.atan2(2.0f32)); + ensure_nondet(|| 1.0f32.sinh()); + ensure_nondet(|| 1.0f32.cosh()); + ensure_nondet(|| 1.0f32.tanh()); + } ensure_nondet(|| 1.0f32.asinh()); - ensure_nondet(|| 1.0f32.cosh()); ensure_nondet(|| 2.0f32.acosh()); - ensure_nondet(|| 1.0f32.tan()); - ensure_nondet(|| 1.0f32.tanh()); - ensure_nondet(|| 1.0f32.atan2(2.0f32)); ensure_nondet(|| 0.5f32.atanh()); ensure_nondet(|| 5.0f32.gamma()); + ensure_nondet(|| 5.0f32.ln_gamma()); ensure_nondet(|| 5.0f32.erf()); ensure_nondet(|| 5.0f32.erfc()); } @@ -1348,18 +1357,23 @@ fn test_non_determinism() { ensure_nondet(|| 1f64.ln_1p()); ensure_nondet(|| f64::consts::E.log10()); ensure_nondet(|| f64::consts::E.log2()); - ensure_nondet(|| 1f64.sin()); - ensure_nondet(|| 0f64.cos()); ensure_nondet(|| 27.0f64.cbrt()); ensure_nondet(|| 3.0f64.hypot(4.0f64)); + ensure_nondet(|| 1f64.sin()); + ensure_nondet(|| 0f64.cos()); + ensure_nondet(|| 1.0f64.tan()); + ensure_nondet(|| 1.0f64.asin()); + ensure_nondet(|| 5.0f64.acos()); + ensure_nondet(|| 1.0f64.atan()); + ensure_nondet(|| 1.0f64.atan2(2.0f64)); ensure_nondet(|| 1.0f64.sinh()); - ensure_nondet(|| 1.0f64.asinh()); ensure_nondet(|| 1.0f64.cosh()); - ensure_nondet(|| 3.0f64.acosh()); - ensure_nondet(|| 1.0f64.tan()); ensure_nondet(|| 1.0f64.tanh()); + ensure_nondet(|| 1.0f64.asinh()); + ensure_nondet(|| 3.0f64.acosh()); ensure_nondet(|| 0.5f64.atanh()); ensure_nondet(|| 5.0f64.gamma()); + ensure_nondet(|| 5.0f64.ln_gamma()); ensure_nondet(|| 5.0f64.erf()); ensure_nondet(|| 5.0f64.erfc()); } From 4303a14bdd8388e5934c0d823c54b8f935b2e1b3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 24 Feb 2025 13:50:42 +0100 Subject: [PATCH 35/37] slightly extend comment --- src/tools/miri/src/shims/aarch64.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs index fc6e3b303d1c9..7cccc9e51d8ee 100644 --- a/src/tools/miri/src/shims/aarch64.rs +++ b/src/tools/miri/src/shims/aarch64.rs @@ -33,7 +33,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Used to implement the vpmaxq_u8 function. - // Folding maximum of adjacent pairs. + // Computes the maximum of adjacent pairs; the first half of the output is produced from the + // `left` input, the second half of the output from the `right` input. // https://developer.arm.com/architectures/instruction-sets/intrinsics/vpmaxq_u8 "neon.umaxp.v16i8" => { let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; From 88f5f1ef2cfc4c2ce7cd3b159e990871cb6f78de Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 25 Feb 2025 08:14:07 +0100 Subject: [PATCH 36/37] Preparing for merge from rustc --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 6cd39fabeeeef..b3e207f53b8b7 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -e0be1a02626abef2878cb7f4aaef7ae409477112 +f5729cfed3c45e061e8a443677fc1d5ef9277df7 From 5e4c582b3e125c1260d05609aee276155b0e9b72 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 25 Feb 2025 09:35:15 +0100 Subject: [PATCH 37/37] disable a potentially bogus test on Miri --- library/coretests/tests/num/int_log.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/coretests/tests/num/int_log.rs b/library/coretests/tests/num/int_log.rs index 60902752dab64..9c630a61dd5b3 100644 --- a/library/coretests/tests/num/int_log.rs +++ b/library/coretests/tests/num/int_log.rs @@ -34,6 +34,7 @@ fn checked_ilog() { } #[test] +#[cfg_attr(miri, ignore)] // FIXME test is broken on Miri: https://github.com/rust-lang/rust/issues/137591 fn checked_ilog2() { assert_eq!(5u32.checked_ilog2(), Some(2)); assert_eq!(0u64.checked_ilog2(), None);