From 9d4130b1bb81fd53a74cc8c83f5f74682e828129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 23 Sep 2025 14:12:43 +0200 Subject: [PATCH 01/35] Use `rustc-josh-sync` merge commit message for pull PR description --- src/tools/miri/.github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 740118bb4a02..3078cd35b108 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -231,6 +231,9 @@ jobs: exit ${exitcode} fi + # Store merge commit message + git log -1 --pretty=%B > message.txt + # Format changes ./miri toolchain ./miri fmt --check || (./miri fmt && git commit -am "fmt") @@ -239,7 +242,7 @@ jobs: BRANCH="rustup-$(date -u +%Y-%m-%d)" git switch -c $BRANCH git push -u origin $BRANCH - gh pr create -B master --title 'Automatic Rustup' --body "Update \`rustc\` to https://github.com/rust-lang/rust/commit/$(cat rust-version)." + gh pr create -B master --title 'Automatic Rustup' --body-file message.txt env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} From 1ec098878c5f7a6f5cda2a81093fd34b76f75e9e Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Mon, 8 Sep 2025 00:04:33 +0200 Subject: [PATCH 02/35] Expand GenMC mode: - Implement support for 'assume' statements. - Improve scheduling and next instruction type determination. - Improve GenMC mode documentation. Co-authored-by: Ralf Jung --- src/tools/miri/doc/genmc.md | 77 ++++++++++- .../genmc-sys/cpp/include/MiriInterface.hpp | 7 + .../cpp/src/MiriInterface/EventHandling.cpp | 6 + src/tools/miri/genmc-sys/src/lib.rs | 12 +- src/tools/miri/src/concurrency/genmc/dummy.rs | 43 +++--- .../miri/src/concurrency/genmc/intercept.rs | 38 ++++++ src/tools/miri/src/concurrency/genmc/mod.rs | 22 ++- .../miri/src/concurrency/genmc/scheduling.rs | 128 +++++++++++++----- src/tools/miri/src/concurrency/mod.rs | 2 +- src/tools/miri/src/concurrency/thread.rs | 17 ++- src/tools/miri/src/shims/foreign_items.rs | 12 ++ .../spinloop_assume.bounded123.stderr | 5 + .../spinloop_assume.bounded321.stderr | 5 + .../spinloop_assume.replaced123.stderr | 5 + .../spinloop_assume.replaced321.stderr | 5 + .../genmc/pass/intercept/spinloop_assume.rs | 98 ++++++++++++++ src/tools/miri/tests/utils/miri_extern.rs | 3 + 17 files changed, 405 insertions(+), 80 deletions(-) create mode 100644 src/tools/miri/src/concurrency/genmc/intercept.rs create mode 100644 src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded123.stderr create mode 100644 src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded321.stderr create mode 100644 src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced123.stderr create mode 100644 src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced321.stderr create mode 100644 src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md index 44e11dcbec44..3e5859c6f5b2 100644 --- a/src/tools/miri/doc/genmc.md +++ b/src/tools/miri/doc/genmc.md @@ -1,15 +1,26 @@ # **(WIP)** Documentation for Miri-GenMC +**NOTE: GenMC mode is not yet fully implemented, and has [several correctness issues](https://github.com/rust-lang/miri/issues/4572). Using GenMC mode currently requires manually compiling Miri, see [Usage](#usage).** + + [GenMC](https://github.com/MPI-SWS/genmc) is a stateless model checker for exploring concurrent executions of a program. Miri-GenMC integrates that model checker into Miri. -**NOTE: Currently, no actual GenMC functionality is part of Miri, this is still WIP.** +Miri in GenMC mode takes a program as input like regular Miri, but instead of running it once, the program is executed repeatedly, until all possible executions allowed by the Rust memory model are explored. +This includes all possible thread interleavings and all allowed return values for atomic operations, including cases that are very rare to encounter on actual hardware. +(However, this does not include other sources of non-determinism, such as the absolute addresses of allocations. +It is hence still possible to have latent bugs in a test case even if they passed GenMC.) - +GenMC requires the input program to be bounded, i.e., have finitely many possible executions, otherwise it will not terminate. +Any loops that may run infinitely must be replaced or bounded (see below). + +GenMC makes use of Dynamic Partial Order Reduction (DPOR) to reduce the number of executions that must be explored, but the runtime can still be super-exponential in the size of the input program (number of threads and amount of interaction between threads). +Large programs may not be verifiable in a reasonable amount of time. ## Usage For testing/developing Miri-GenMC: +- install all [dependencies required by GenMC](https://github.com/MPI-SWS/genmc?tab=readme-ov-file#dependencies) - clone the Miri repo. - build Miri-GenMC with `./miri build --features=genmc`. - OR: install Miri-GenMC in the current system with `./miri install --features=genmc` @@ -50,6 +61,68 @@ Note that `cargo miri test` in GenMC mode is currently not supported. +### Eliminating unbounded loops + +#### Limiting the number of explored executions + +The number of explored executions in a concurrent program can increase super-exponentially in the size of the program. +Reducing the number of explored executions is the most impactful way to improve verification times. +Some programs also contain possibly infinite loops, which are not supported by GenMC. + +One way to drastically improve verification performance is by bounding spinloops. +A spinloop may loop a many times before it can finally make progress. +If such a loop doesn't have any visible side effects, meaning it does not matter to the outcome of the program whether the loop ran once or a million time, then the loop can be limited to one iteration. + +The following code gives an example for how to replace a loop that waits for a boolean to be true. +Since there are no side effects, replacing the loop with one iteration is safe. + +```rust +#[cfg(miri)] +unsafe extern "Rust" { + // This is a special function that Miri provides. + // It blocks the thread calling this function if the condition is false. + pub unsafe fn miri_genmc_assume(condition: bool); +} + +/// This functions loads an atomic boolean in a loop until it is true. +/// GenMC will explore all executions where this does 1, 2, ..., ∞ loads, which means the verification will never terminate. +fn spin_until_true(flag: &AtomicBool) { + while (!flag.load(Relaxed)) { + std::hint::spin_loop(); + } +} + +/// By replacing this loop with an assume statement, the only executions that will be explored are those with exactly 1 load. +/// Incorrect use of assume statements can lead GenMC to miss important executions, so it is marked `unsafe`. +fn spin_until_true_genmc(flag: &AtomicBool) { + unsafe { miri_genmc_assume(flag.load(Relaxed)); } +} +``` + +Some loops do contain side effects, meaning the number of explored iterations affects the rest of the program. +Replacing the loop with one iteration like we did above would mean we miss all those possible executions. + +In such a case, the loop can be limited to a fixed number of iterations instead. +The choice of iteration limit trades off verification time for possibly missing bugs requiring more iterations. + +```rust +/// The loop in this function has a side effect, which is to increment the counter for the number of iterations. +/// Instead of replacing the loop entirely (which would miss all executions with `count > 0`), we limit the loop to at most 3 iterations. +fn count_until_true_genmc(flag: &AtomicBool) -> u64 { + let mut count = 0; + while (!flag.load(Relaxed)) { + // Any execution that takes more than 3 iterations will not be explored. + unsafe { miri_genmc_assume(count < 3); } + count += 1; + std::hint::spin_loop(); + } + count +} +``` + + + + ## Limitations Some or all of these limitations might get removed in the future: diff --git a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp index 444c9375319a..8972341d491d 100644 --- a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp +++ b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp @@ -134,6 +134,13 @@ struct MiriGenmcShim : private GenMCDriver { void handle_thread_finish(ThreadId thread_id, uint64_t ret_val); void handle_thread_kill(ThreadId thread_id); + /**** Blocking instructions ****/ + /// Inform GenMC that the thread should be blocked. + /// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user + /// supplied assume statements. This can become a parameter once more types of assumes are + /// added. + void handle_assume_block(ThreadId thread_id); + /***** Exploration related functionality *****/ /** Ask the GenMC scheduler for a new thread to schedule and return whether the execution is diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp index 05c82641df94..fac7217ea5c8 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp @@ -30,6 +30,12 @@ #include #include +/**** Blocking instructions ****/ + +void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { + GenMCDriver::handleAssume(inc_pos(thread_id), AssumeType::User); +} + /**** Memory access handling ****/ [[nodiscard]] auto MiriGenmcShim::handle_load( diff --git a/src/tools/miri/genmc-sys/src/lib.rs b/src/tools/miri/genmc-sys/src/lib.rs index 733b3d780b18..385afb9eaba0 100644 --- a/src/tools/miri/genmc-sys/src/lib.rs +++ b/src/tools/miri/genmc-sys/src/lib.rs @@ -258,9 +258,11 @@ mod ffi { /// Corresponds to GenMC's type with the same name. /// Should only be modified if changed by GenMC. enum ActionKind { - /// Any Mir terminator that's atomic and has load semantics. + /// Any MIR terminator that's atomic and that may have load semantics. + /// This includes functions with atomic properties, such as `pthread_create`. + /// If the exact type of the terminator cannot be determined, load is a safe default `Load`. Load, - /// Anything that's not a `Load`. + /// Anything that's definitely not a `Load`. NonLoad, } @@ -412,6 +414,12 @@ mod ffi { fn handle_thread_finish(self: Pin<&mut MiriGenmcShim>, thread_id: i32, ret_val: u64); fn handle_thread_kill(self: Pin<&mut MiriGenmcShim>, thread_id: i32); + /**** Blocking instructions ****/ + /// Inform GenMC that the thread should be blocked. + /// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user supplied assume statements. + /// This can become a parameter once more types of assumes are added. + fn handle_assume_block(self: Pin<&mut MiriGenmcShim>, thread_id: i32); + /***** Exploration related functionality *****/ /// Ask the GenMC scheduler for a new thread to schedule and diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index c28984cef35a..e260af7c4a16 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -1,11 +1,12 @@ use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult}; +pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; use crate::intrinsics::AtomicRmwOp; use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, Scalar, - ThreadId, ThreadManager, VisitProvenance, VisitWith, + AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriMachine, OpTy, + Scalar, ThreadId, ThreadManager, VisitProvenance, VisitWith, }; #[derive(Clone, Copy, Debug)] @@ -36,9 +37,27 @@ mod run { } } +mod intercept { + use super::*; + + impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} + pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn handle_genmc_verifier_assume(&mut self, _condition: &OpTy<'tcx>) -> InterpResult<'tcx> { + unreachable!(); + } + } +} + impl GenmcCtx { // We don't provide the `new` function in the dummy module. + pub(crate) fn schedule_thread<'tcx>( + &self, + _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, + ) -> InterpResult<'tcx, Option> { + unreachable!() + } + /**** Memory access handling ****/ pub(super) fn set_ongoing_action_data_race_free(&self, _enable: bool) { @@ -191,26 +210,6 @@ impl GenmcCtx { ) -> InterpResult<'tcx> { unreachable!() } - - /**** Scheduling functionality ****/ - - pub fn schedule_thread<'tcx>( - &self, - _ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - ) -> InterpResult<'tcx, ThreadId> { - unreachable!() - } - - /**** Blocking instructions ****/ - - #[allow(unused)] - pub(crate) fn handle_verifier_assume<'tcx>( - &self, - _machine: &MiriMachine<'tcx>, - _condition: bool, - ) -> InterpResult<'tcx, ()> { - unreachable!() - } } impl VisitProvenance for GenmcCtx { diff --git a/src/tools/miri/src/concurrency/genmc/intercept.rs b/src/tools/miri/src/concurrency/genmc/intercept.rs new file mode 100644 index 000000000000..4867b1dc21af --- /dev/null +++ b/src/tools/miri/src/concurrency/genmc/intercept.rs @@ -0,0 +1,38 @@ +use tracing::debug; + +use crate::concurrency::thread::EvalContextExt as _; +use crate::{ + BlockReason, InterpResult, MachineCallback, MiriInterpCx, OpTy, UnblockKind, VisitProvenance, + VisitWith, callback, interp_ok, +}; + +// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`. + +/// Other functionality not directly related to event handling +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Handle an `assume` statement. This will tell GenMC to block the current thread if the `condition` is false. + /// Returns `true` if the current thread should be blocked in Miri too. + fn handle_genmc_verifier_assume(&mut self, condition: &OpTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let condition_bool = this.read_scalar(condition)?.to_bool()?; + debug!("GenMC: handle_genmc_verifier_assume, condition: {condition:?} = {condition_bool}"); + if condition_bool { + return interp_ok(()); + } + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + genmc_ctx.handle_assume_block(&this.machine)?; + this.block_thread( + BlockReason::Genmc, + None, + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + unreachable!("GenMC should never unblock a thread blocked by an `assume`."); + } + ), + ); + interp_ok(()) + } +} diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index 0086d3f2bf0b..3e8e8fbfd491 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -29,6 +29,7 @@ use crate::{ mod config; mod global_allocations; mod helper; +mod intercept; mod run; pub(crate) mod scheduling; mod thread_id_map; @@ -36,6 +37,7 @@ mod thread_id_map; pub use genmc_sys::GenmcParams; pub use self::config::GenmcConfig; +pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; #[derive(Debug)] @@ -732,17 +734,6 @@ impl GenmcCtx { self.exec_state.exit_status.set(Some(exit_status)); interp_ok(()) } - - /**** Blocking instructions ****/ - - #[allow(unused)] - pub(crate) fn handle_verifier_assume<'tcx>( - &self, - machine: &MiriMachine<'tcx>, - condition: bool, - ) -> InterpResult<'tcx, ()> { - if condition { interp_ok(()) } else { self.handle_user_block(machine) } - } } impl GenmcCtx { @@ -903,8 +894,13 @@ impl GenmcCtx { /// Handle a user thread getting blocked. /// This may happen due to an manual `assume` statement added by a user /// or added by some automated program transformation, e.g., for spinloops. - fn handle_user_block<'tcx>(&self, _machine: &MiriMachine<'tcx>) -> InterpResult<'tcx> { - todo!() + fn handle_assume_block<'tcx>(&self, machine: &MiriMachine<'tcx>) -> InterpResult<'tcx> { + let curr_thread = machine.threads.active_thread(); + let genmc_curr_thread = + self.exec_state.thread_id_manager.borrow().get_genmc_tid(curr_thread); + debug!("GenMC: assume statement, blocking thread {curr_thread:?} ({genmc_curr_thread:?})"); + self.handle.borrow_mut().pin_mut().handle_assume_block(genmc_curr_thread); + interp_ok(()) } } diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index b5c23f2d0845..7fb3d8fa3ef3 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -1,58 +1,112 @@ use genmc_sys::{ActionKind, ExecutionState}; +use rustc_middle::mir::TerminatorKind; +use rustc_middle::ty::{self, Ty}; use super::GenmcCtx; use crate::{ InterpCx, InterpResult, MiriMachine, TerminationInfo, ThreadId, interp_ok, throw_machine_stop, }; +enum NextInstrKind { + MaybeAtomic(ActionKind), + NonAtomic, +} + +/// Check if a call or tail-call could have atomic load semantics. +fn get_next_instruction_kind<'tcx>( + ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, +) -> InterpResult<'tcx, NextInstrKind> { + use NextInstrKind::*; + + let thread_manager = &ecx.machine.threads; + + // Determine whether the next instruction in the current thread might be a load. + // This is used for the "writes-first" scheduling in GenMC. + // Scheduling writes before reads can be beneficial for verification performance. + // `Load` is a safe default for the next instruction type if we cannot guarantee that it isn't a load. + if !thread_manager.active_thread_ref().is_enabled() { + // The current thread can get blocked (e.g., due to a thread join, `Mutex::lock`, assume statement, ...), then we need to ask GenMC for another thread to schedule. + // Most to all blocking operations have load semantics, since they wait on something to change in another thread, + // e.g., a thread join waiting on another thread to finish (join loads the return value(s) of the other thread), + // or a thread waiting for another thread to unlock a `Mutex`, which loads the mutex state (Locked, Unlocked). + // `Load` is a safe default for the next instruction type, since we may not know what the next instruction is. + return interp_ok(MaybeAtomic(ActionKind::Load)); + } + // This thread is still enabled. If it executes a terminator next, we consider yielding, + // but in all other cases we just keep running this thread since it never makes sense + // to yield before a non-atomic operation. + let Some(frame) = thread_manager.active_thread_stack().last() else { + return interp_ok(NonAtomic); + }; + let either::Either::Left(loc) = frame.current_loc() else { + // We are unwinding, so the next step is definitely not atomic. + return interp_ok(NonAtomic); + }; + let basic_block = &frame.body().basic_blocks[loc.block]; + if let Some(_statement) = basic_block.statements.get(loc.statement_index) { + // Statements can't be atomic. + return interp_ok(NonAtomic); + } + match &basic_block.terminator().kind { + // All atomics are modeled as function calls to intrinsic functions. + // The one exception is thread joining, but those are also calls. + TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => + get_function_kind(ecx, func.ty(&frame.body().local_decls, *ecx.tcx)), + // Non-call terminators are not atomic. + _ => interp_ok(NonAtomic), + } +} + +fn get_function_kind<'tcx>( + ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, + func_ty: Ty<'tcx>, +) -> InterpResult<'tcx, NextInstrKind> { + use NextInstrKind::*; + let callee_def_id = match func_ty.kind() { + ty::FnDef(def_id, _args) => def_id, + _ => return interp_ok(MaybeAtomic(ActionKind::Load)), // we don't know the callee, might be pthread_join + }; + let Some(intrinsic_def) = ecx.tcx.intrinsic(callee_def_id) else { + if ecx.tcx.is_foreign_item(*callee_def_id) { + // Some shims, like pthread_join, must be considered loads. So just consider them all loads, + // these calls are not *that* common. + return interp_ok(MaybeAtomic(ActionKind::Load)); + } + // The next step is a call to a regular Rust function. + return interp_ok(NonAtomic); + }; + let intrinsic_name = intrinsic_def.name.as_str(); + let Some(suffix) = intrinsic_name.strip_prefix("atomic_") else { + return interp_ok(NonAtomic); // Non-atomic intrinsic, so guaranteed not an atomic load + }; + // `atomic_store`, `atomic_fence` and `atomic_singlethreadfence` are not considered loads. + // Any future `atomic_*` intrinsics may have load semantics, so we err on the side of caution and classify them as "maybe loads". + interp_ok(MaybeAtomic(if matches!(suffix, "store" | "fence" | "singlethreadfence") { + ActionKind::NonLoad + } else { + ActionKind::Load + })) +} + impl GenmcCtx { + /// Returns the thread ID of the next thread to schedule, or `None` to continue with the current thread. pub(crate) fn schedule_thread<'tcx>( &self, ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - ) -> InterpResult<'tcx, ThreadId> { - let thread_manager = &ecx.machine.threads; - let active_thread_id = thread_manager.active_thread(); - - // Determine whether the next instruction in the current thread might be a load. - // This is used for the "writes-first" scheduling in GenMC. - // Scheduling writes before reads can be beneficial for verification performance. - // `Load` is a safe default for the next instruction type if we cannot guarantee that it isn't a load. - let curr_thread_next_instr_kind = if !thread_manager.active_thread_ref().is_enabled() { - // The current thread can get blocked (e.g., due to a thread join, `Mutex::lock`, assume statement, ...), then we need to ask GenMC for another thread to schedule. - // Most to all blocking operations have load semantics, since they wait on something to change in another thread, - // e.g., a thread join waiting on another thread to finish (join loads the return value(s) of the other thread), - // or a thread waiting for another thread to unlock a `Mutex`, which loads the mutex state (Locked, Unlocked). - ActionKind::Load - } else { - // This thread is still enabled. If it executes a terminator next, we consider yielding, - // but in all other cases we just keep running this thread since it never makes sense - // to yield before a non-atomic operation. - let Some(frame) = thread_manager.active_thread_stack().last() else { - return interp_ok(active_thread_id); - }; - let either::Either::Left(loc) = frame.current_loc() else { - // We are unwinding, so the next step is definitely not atomic. - return interp_ok(active_thread_id); - }; - let basic_block = &frame.body().basic_blocks[loc.block]; - if let Some(_statement) = basic_block.statements.get(loc.statement_index) { - // Statements can't be atomic. - return interp_ok(active_thread_id); - } - - // FIXME(genmc): determine terminator kind. - ActionKind::Load + ) -> InterpResult<'tcx, Option> { + let atomic_kind = match get_next_instruction_kind(ecx)? { + NextInstrKind::MaybeAtomic(atomic_kind) => atomic_kind, + NextInstrKind::NonAtomic => return interp_ok(None), // No need to reschedule on a non-atomic. }; + let active_thread_id = ecx.machine.threads.active_thread(); let thread_infos = self.exec_state.thread_id_manager.borrow(); let genmc_tid = thread_infos.get_genmc_tid(active_thread_id); - let mut mc = self.handle.borrow_mut(); - let pinned_mc = mc.as_mut().unwrap(); - let result = pinned_mc.schedule_next(genmc_tid, curr_thread_next_instr_kind); + let result = self.handle.borrow_mut().pin_mut().schedule_next(genmc_tid, atomic_kind); // Depending on the exec_state, we either schedule the given thread, or we are finished with this execution. match result.exec_state { - ExecutionState::Ok => interp_ok(thread_infos.get_miri_tid(result.next_thread)), + ExecutionState::Ok => interp_ok(Some(thread_infos.get_miri_tid(result.next_thread))), ExecutionState::Blocked => throw_machine_stop!(TerminationInfo::GenmcBlockedExecution), ExecutionState::Finished => { let exit_status = self.exec_state.exit_status.get().expect( diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs index 9dae858592f1..b20a17dd6989 100644 --- a/src/tools/miri/src/concurrency/mod.rs +++ b/src/tools/miri/src/concurrency/mod.rs @@ -22,5 +22,5 @@ pub mod weak_memory; mod genmc; pub use self::data_race_handler::{AllocDataRaceHandler, GlobalDataRaceHandler}; -pub use self::genmc::{ExitType, GenmcConfig, GenmcCtx, run_genmc_mode}; +pub use self::genmc::{ExitType, GenmcConfig, GenmcCtx, GenmcEvalContextExt, run_genmc_mode}; pub use self::vector_clock::VClock; diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 00c5e337b1e9..246300f8dda1 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -110,6 +110,9 @@ pub enum BlockReason { Eventfd, /// Blocked on unnamed_socket. UnnamedSocket, + /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only). + /// Will be implicitly unblocked when GenMC schedules this thread again. + Genmc, } /// The state of a thread. @@ -572,6 +575,7 @@ impl<'tcx> ThreadManager<'tcx> { /// See : /// > The handle is valid until closed, even after the thread it represents has been terminated. fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> { + // NOTE: In GenMC mode, we treat detached threads like regular threads that are never joined, so there is no special handling required here. trace!("detaching {:?}", id); let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() { @@ -705,11 +709,18 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { // In GenMC mode, we let GenMC do the scheduling. if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - let next_thread_id = genmc_ctx.schedule_thread(this)?; - + let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else { + return interp_ok(SchedulingAction::ExecuteStep); + }; + // If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again. + if this.machine.threads.threads[next_thread_id].state.is_blocked_on(BlockReason::Genmc) + { + info!("GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now."); + this.unblock_thread(next_thread_id, BlockReason::Genmc)?; + } + // Set the new active thread. let thread_manager = &mut this.machine.threads; thread_manager.active_thread = next_thread_id; - assert!(thread_manager.threads[thread_manager.active_thread].state.is_enabled()); return interp_ok(SchedulingAction::ExecuteStep); } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index eca8cccf5efc..1d086906e7a5 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -16,6 +16,7 @@ use rustc_target::callconv::FnAbi; use super::alloc::EvalContextExt as _; use super::backtrace::EvalContextExt as _; +use crate::concurrency::GenmcEvalContextExt as _; use crate::helpers::EvalContextExt as _; use crate::*; @@ -485,6 +486,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } + // GenMC mode: Assume statements block the current thread when their condition is false. + "miri_genmc_assume" => { + let [condition] = + this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?; + if this.machine.data_race.as_genmc_ref().is_some() { + this.handle_genmc_verifier_assume(condition)?; + } else { + throw_unsup_format!("miri_genmc_assume is only supported in GenMC mode") + } + } + // Aborting the process. "exit" => { let [code] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded123.stderr b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded123.stderr new file mode 100644 index 000000000000..1458300b1110 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded123.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 232 executions. No errors found. +Number of complete executions explored: 108 +Number of blocked executions seen: 124 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded321.stderr b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded321.stderr new file mode 100644 index 000000000000..1458300b1110 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded321.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 232 executions. No errors found. +Number of complete executions explored: 108 +Number of blocked executions seen: 124 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced123.stderr b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced123.stderr new file mode 100644 index 000000000000..c79a87b33a5f --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced123.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 9 executions. No errors found. +Number of complete executions explored: 1 +Number of blocked executions seen: 8 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced321.stderr b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced321.stderr new file mode 100644 index 000000000000..c79a87b33a5f --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced321.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 9 executions. No errors found. +Number of complete executions explored: 1 +Number of blocked executions seen: 8 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs new file mode 100644 index 000000000000..9d7c823b1e48 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs @@ -0,0 +1,98 @@ +//@ revisions: bounded123 bounded321 replaced123 replaced321 +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// This test uses GenMC assume statements to bound or replace spinloops. +// Three threads pass a value to each other, spinning on an atomic FLAG to wait for the previous thread. +// +// There are two variants, one limits the spinloop to three iterations, and one that completely replaces the spin loop. +// Without this loop bounding, this test *cannot* be verified, since GenMC will have to explore infinitely many executions (one per possible number of loop iterations). + +// FIXME(genmc): GenMC provides the `--unroll=N` option, which limits all loops to at most N iterations (at the LLVM IR level). +// Such an option for Miri would allow a variant of this test without manual bounding, using this automatic loop bounding instead. + +// We use different thread orders to ensure it doesn't just pass by chance (each thread order should give the same result). +// We use verbose output to see the number of explored vs blocked executions. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; +use crate::utils::*; + +static mut X: u64 = 0; +static FLAG: AtomicU64 = AtomicU64::new(0); + +/// Unbounded variant of the spinloop. +/// This function causes GenMC to explore infinite executions. +#[allow(unused)] +fn spin_until_unbounded(value: u64) { + while FLAG.load(Acquire) != value { + std::hint::spin_loop(); + } +} + +#[cfg(any(bounded123, bounded321))] +/// We bound the loop to at most 3 iterations. +fn spin_until(value: u64) { + for _ in 0..3 { + if FLAG.load(Acquire) == value { + return; + } + } + unsafe { miri_genmc_assume(false) }; +} + +#[cfg(not(any(bounded123, bounded321)))] +/// For full replacement, we limit it to only 1 load. +fn spin_until(value: u64) { + unsafe { miri_genmc_assume(FLAG.load(Acquire) == value) }; +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. + unsafe { X = 0 }; + FLAG.store(0, Relaxed); + + unsafe { + let t0 = || { + X = 42; + FLAG.store(1, Release); + + spin_until(3); + let c = X; + if c != 44 { + std::process::abort(); + } + }; + let t1 = || { + spin_until(1); + let a = X; + X = a + 1; + FLAG.store(2, Release); + }; + let t2 = || { + spin_until(2); + let b = X; + X = b + 1; + FLAG.store(3, Release); + }; + // Reverse the order for the second test variant. + #[cfg(any(bounded321, replaced321))] + let (t0, t1, t2) = (t2, t1, t0); + + spawn_pthread_closure(t0); + spawn_pthread_closure(t1); + spawn_pthread_closure(t2); + + 0 + } +} diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index d6c43b188219..633f337f7e7c 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -147,4 +147,7 @@ extern "Rust" { /// "symbolic" alignment checks. Will fail if the pointer is not actually aligned or `align` is /// not a power of two. Has no effect when alignment checks are concrete (which is the default). pub fn miri_promise_symbolic_alignment(ptr: *const (), align: usize); + + /// Blocks the current execution if the argument is false + pub fn miri_genmc_assume(condition: bool); } From 64bfe7c98f126a189218f0615a377fa0d7412c77 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 24 Sep 2025 10:56:32 +0200 Subject: [PATCH 03/35] genmc doc edits --- src/tools/miri/doc/genmc.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md index 3e5859c6f5b2..7da7a3d18948 100644 --- a/src/tools/miri/doc/genmc.md +++ b/src/tools/miri/doc/genmc.md @@ -63,18 +63,14 @@ Note that `cargo miri test` in GenMC mode is currently not supported. ### Eliminating unbounded loops -#### Limiting the number of explored executions +As mentioned above, GenMC requires all loops to be bounded. +Otherwise, it is not possible to exhaustively explore all executions. +Currently, Miri-GenMC has no support for automatically bounding loops, so this needs to be done manually. -The number of explored executions in a concurrent program can increase super-exponentially in the size of the program. -Reducing the number of explored executions is the most impactful way to improve verification times. -Some programs also contain possibly infinite loops, which are not supported by GenMC. +#### Bounding loops without side effects -One way to drastically improve verification performance is by bounding spinloops. -A spinloop may loop a many times before it can finally make progress. -If such a loop doesn't have any visible side effects, meaning it does not matter to the outcome of the program whether the loop ran once or a million time, then the loop can be limited to one iteration. - -The following code gives an example for how to replace a loop that waits for a boolean to be true. -Since there are no side effects, replacing the loop with one iteration is safe. +The easiest case is that of a loop that simply spins until it observes a certain condition, without any side effects. +Such loops can be limited to one iteration, as demonstrated by the following example: ```rust #[cfg(miri)] @@ -84,21 +80,23 @@ unsafe extern "Rust" { pub unsafe fn miri_genmc_assume(condition: bool); } -/// This functions loads an atomic boolean in a loop until it is true. -/// GenMC will explore all executions where this does 1, 2, ..., ∞ loads, which means the verification will never terminate. +// This functions loads an atomic boolean in a loop until it is true. +// GenMC will explore all executions where this does 1, 2, ..., ∞ loads, which means the verification will never terminate. fn spin_until_true(flag: &AtomicBool) { - while (!flag.load(Relaxed)) { + while !flag.load(Relaxed) { std::hint::spin_loop(); } } -/// By replacing this loop with an assume statement, the only executions that will be explored are those with exactly 1 load. -/// Incorrect use of assume statements can lead GenMC to miss important executions, so it is marked `unsafe`. +// By replacing this loop with an assume statement, the only executions that will be explored are those with exactly 1 load that observes the expected value. +// Incorrect use of assume statements can lead GenMC to miss important executions, so it is marked `unsafe`. fn spin_until_true_genmc(flag: &AtomicBool) { - unsafe { miri_genmc_assume(flag.load(Relaxed)); } + unsafe { miri_genmc_assume(flag.load(Relaxed)) }; } ``` +#### Bounding loops with side effects + Some loops do contain side effects, meaning the number of explored iterations affects the rest of the program. Replacing the loop with one iteration like we did above would mean we miss all those possible executions. @@ -110,11 +108,11 @@ The choice of iteration limit trades off verification time for possibly missing /// Instead of replacing the loop entirely (which would miss all executions with `count > 0`), we limit the loop to at most 3 iterations. fn count_until_true_genmc(flag: &AtomicBool) -> u64 { let mut count = 0; - while (!flag.load(Relaxed)) { - // Any execution that takes more than 3 iterations will not be explored. - unsafe { miri_genmc_assume(count < 3); } + while !flag.load(Relaxed) { count += 1; std::hint::spin_loop(); + // Any execution that takes more than 3 iterations will not be explored. + unsafe { miri_genmc_assume(count <= 3) }; } count } From 7dfc6b2e27825ca5e1928233971f3e7996bbe44b Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Fri, 26 Sep 2025 04:53:29 +0000 Subject: [PATCH 04/35] Prepare for merging from rust-lang/rust This updates the rust-version file to b733736ea2feb7798c99cbb9a769bce74be108df. --- 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 388e88fe43eb..c06de8db0a51 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -f6092f224d2b1774b31033f12d0bee626943b02f +b733736ea2feb7798c99cbb9a769bce74be108df From aa6332efc2c6f30360419dc40351dc2cab245729 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 27 Sep 2025 10:58:19 +0200 Subject: [PATCH 05/35] silence unused import error --- src/tools/miri/src/bin/miri.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 8b15a7863476..40ac5c3ff385 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -352,6 +352,7 @@ fn fatal_error_(msg: &impl std::fmt::Display) -> ! { macro_rules! fatal_error { ($($tt:tt)*) => { $crate::fatal_error_(&format_args!($($tt)*)) }; } +#[allow(unused)] // use depends on cfg use fatal_error; /// Execute a compiler with the given CLI arguments and callbacks. From 9e984cdfde19d4235bab070c28870d907af3a4c2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 27 Sep 2025 12:31:42 +0200 Subject: [PATCH 06/35] alloc_addresses: track more explicitly whether we are in charge of generating addresses --- src/tools/miri/src/alloc_addresses/mod.rs | 79 ++++++++++++----------- src/tools/miri/src/machine.rs | 5 +- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index f011ee717850..b3ea51bec6fc 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -42,20 +42,19 @@ pub struct GlobalStateInner { /// they do not have an `AllocExtra`. /// This is the inverse of `int_to_ptr_map`. base_addr: FxHashMap, - /// Temporarily store prepared memory space for global allocations the first time their memory - /// address is required. This is used to ensure that the memory is allocated before Miri assigns - /// it an internal address, which is important for matching the internal address to the machine - /// address so FFI can read from pointers. - prepared_alloc_bytes: FxHashMap, - /// A pool of addresses we can reuse for future allocations. - reuse: ReusePool, - /// Whether an allocation has been exposed or not. This cannot be put + /// The set of exposed allocations. This cannot be put /// into `AllocExtra` for the same reason as `base_addr`. exposed: FxHashSet, - /// The generator for new addresses in a given range. - address_generator: AddressGenerator, /// The provenance to use for int2ptr casts provenance_mode: ProvenanceMode, + /// The generator for new addresses in a given range, and a pool for address reuse. This is + /// `None` if addresses are generated elsewhere (in native-lib mode or with GenMC). + address_generation: Option<(AddressGenerator, ReusePool)>, + /// Native-lib mode only: Temporarily store prepared memory space for global allocations the + /// first time their memory address is required. This is used to ensure that the memory is + /// allocated before Miri assigns it an internal address, which is important for matching the + /// internal address to the machine address so FFI can read from pointers. + prepared_alloc_bytes: Option>, } impl VisitProvenance for GlobalStateInner { @@ -64,9 +63,8 @@ impl VisitProvenance for GlobalStateInner { int_to_ptr_map: _, base_addr: _, prepared_alloc_bytes: _, - reuse: _, exposed: _, - address_generator: _, + address_generation: _, provenance_mode: _, } = self; // Though base_addr, int_to_ptr_map, and exposed contain AllocIds, we do not want to visit them. @@ -83,11 +81,16 @@ impl GlobalStateInner { GlobalStateInner { int_to_ptr_map: Vec::default(), base_addr: FxHashMap::default(), - prepared_alloc_bytes: FxHashMap::default(), - reuse: ReusePool::new(config), exposed: FxHashSet::default(), - address_generator: AddressGenerator::new(stack_addr..tcx.target_usize_max()), provenance_mode: config.provenance_mode, + address_generation: (config.native_lib.is_empty() && config.genmc_config.is_none()) + .then(|| { + ( + AddressGenerator::new(stack_addr..tcx.target_usize_max()), + ReusePool::new(config), + ) + }), + prepared_alloc_bytes: (!config.native_lib.is_empty()).then(FxHashMap::default), } } @@ -147,6 +150,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Store prepared allocation to be picked up for use later. global_state .prepared_alloc_bytes + .as_mut() + .unwrap() .try_insert(alloc_id, prepared_bytes) .unwrap(); ptr @@ -173,29 +178,25 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // We don't have to expose this pointer yet, we do that in `prepare_for_native_call`. return interp_ok(base_ptr.addr().to_u64()); } - // We are not in native lib mode, so we control the addresses ourselves. + // We are not in native lib or genmc mode, so we control the addresses ourselves. + let (addr_gen, reuse) = global_state.address_generation.as_mut().unwrap(); let mut rng = this.machine.rng.borrow_mut(); - if let Some((reuse_addr, clock)) = global_state.reuse.take_addr( - &mut *rng, - info.size, - info.align, - memory_kind, - this.active_thread(), - ) { + if let Some((reuse_addr, clock)) = + reuse.take_addr(&mut *rng, info.size, info.align, memory_kind, this.active_thread()) + { if let Some(clock) = clock { this.acquire_clock(&clock)?; } interp_ok(reuse_addr) } else { // We have to pick a fresh address. - let new_addr = - global_state.address_generator.generate(info.size, info.align, &mut rng)?; + let new_addr = addr_gen.generate(info.size, info.align, &mut rng)?; // If we filled up more than half the address space, start aggressively reusing // addresses to avoid running out. - let remaining_range = global_state.address_generator.get_remaining(); + let remaining_range = addr_gen.get_remaining(); if remaining_range.start > remaining_range.end / 2 { - global_state.reuse.address_space_shortage(); + reuse.address_space_shortage(); } interp_ok(new_addr) @@ -414,6 +415,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut global_state = this.machine.alloc_addresses.borrow_mut(); let mut prepared_alloc_bytes = global_state .prepared_alloc_bytes + .as_mut() + .unwrap() .remove(&id) .unwrap_or_else(|| panic!("alloc bytes for {id:?} have not been prepared")); // Sanity-check that the prepared allocation has the right size and alignment. @@ -496,15 +499,17 @@ impl<'tcx> MiriMachine<'tcx> { // `alloc_id_from_addr` any more. global_state.exposed.remove(&dead_id); // Also remember this address for future reuse. - let thread = self.threads.active_thread(); - global_state.reuse.add_addr(rng, addr, size, align, kind, thread, || { - // We already excluded GenMC above. We cannot use `self.release_clock` as - // `self.alloc_addresses` is borrowed. - if let Some(data_race) = self.data_race.as_vclocks_ref() { - data_race.release_clock(&self.threads, |clock| clock.clone()) - } else { - VClock::default() - } - }) + if let Some((_addr_gen, reuse)) = global_state.address_generation.as_mut() { + let thread = self.threads.active_thread(); + reuse.add_addr(rng, addr, size, align, kind, thread, || { + // We cannot be in GenMC mode as then `address_generation` is `None`. We cannot use + // `self.release_clock` as `self.alloc_addresses` is borrowed. + if let Some(data_race) = self.data_race.as_vclocks_ref() { + data_race.release_clock(&self.threads, |clock| clock.clone()) + } else { + VClock::default() + } + }) + } } } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 04c8bee72c03..af8b40da14ad 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -777,9 +777,8 @@ impl<'tcx> MiriMachine<'tcx> { local_crates, extern_statics: FxHashMap::default(), rng: RefCell::new(rng), - allocator: if !config.native_lib.is_empty() { - Some(Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new()))) - } else { None }, + allocator: (!config.native_lib.is_empty()) + .then(|| Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new()))), tracked_alloc_ids: config.tracked_alloc_ids.clone(), track_alloc_accesses: config.track_alloc_accesses, check_alignment: config.check_alignment, From 5debc4368cde86b1c794725030f65de215d3ece4 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sun, 28 Sep 2025 04:52:54 +0000 Subject: [PATCH 07/35] Prepare for merging from rust-lang/rust This updates the rust-version file to 848e6746fe03dfd703075c5077312b63877d51d6. --- 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 c06de8db0a51..2bf64d62060d 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -b733736ea2feb7798c99cbb9a769bce74be108df +848e6746fe03dfd703075c5077312b63877d51d6 From 2d038a0ed3ab4995ee863f7a54673cc7ef68f816 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Mon, 29 Sep 2025 04:53:44 +0000 Subject: [PATCH 08/35] Prepare for merging from rust-lang/rust This updates the rust-version file to f957826bff7a68b267ce75b1ea56352aed0cca0a. --- 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 2bf64d62060d..1f90d4e5e498 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -848e6746fe03dfd703075c5077312b63877d51d6 +f957826bff7a68b267ce75b1ea56352aed0cca0a From 8b38760176abe1f3f6db5dd10e469158f82830f0 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Fri, 14 Mar 2025 09:58:46 +0100 Subject: [PATCH 09/35] Implement Pointer conversions to and from GenMC. Limitation: Borrow tracking must be disabled. Remove hacks for keeping all memory allocations in GenMC mode. --- src/tools/miri/genmc-sys/build.rs | 16 +- .../genmc-sys/cpp/include/MiriInterface.hpp | 6 +- .../cpp/src/MiriInterface/EventHandling.cpp | 5 +- .../cpp/src/MiriInterface/Exploration.cpp | 4 +- .../genmc-sys/cpp/src/MiriInterface/Setup.cpp | 18 +- src/tools/miri/genmc-sys/src/lib.rs | 20 +- src/tools/miri/src/alloc_addresses/mod.rs | 27 +-- .../miri/src/concurrency/genmc/helper.rs | 54 +++-- src/tools/miri/src/concurrency/genmc/mod.rs | 46 ++-- .../miri/src/concurrency/genmc/scheduling.rs | 8 + src/tools/miri/src/shims/native_lib/mod.rs | 8 +- .../fail/atomics/atomic_ptr_double_free.rs | 47 ++++ .../atomics/atomic_ptr_double_free.stderr | 39 ++++ .../atomic_ptr_invalid_provenance.make.stderr | 21 ++ .../atomics/atomic_ptr_invalid_provenance.rs | 61 ++++++ .../atomic_ptr_invalid_provenance.send.stderr | 21 ++ .../atomic_ptr_alloc_race.dealloc.stderr | 24 +++ .../fail/data_race/atomic_ptr_alloc_race.rs | 51 +++++ .../atomic_ptr_alloc_race.write.stderr | 22 ++ .../atomic_ptr_dealloc_write_race.rs | 42 ++++ .../atomic_ptr_dealloc_write_race.stderr | 32 +++ .../atomic_ptr_write_dealloc_race.rs | 50 +++++ .../atomic_ptr_write_dealloc_race.stderr | 22 ++ .../genmc/pass/atomics/atomic_ptr_ops.rs | 173 +++++++++++++++ .../genmc/pass/atomics/atomic_ptr_ops.stderr | 2 + .../pass/atomics/atomic_ptr_roundtrip.rs | 60 ++++++ .../pass/atomics/atomic_ptr_roundtrip.stderr | 2 + .../ms_queue_dynamic.default_R1W1.stderr | 3 + .../ms_queue_dynamic.default_R1W2.stderr | 3 + .../pass/data-structures/ms_queue_dynamic.rs | 200 ++++++++++++++++++ ..._queue_dynamic.spinloop_assume_R1W1.stderr | 3 + ..._queue_dynamic.spinloop_assume_R1W2.stderr | 5 + .../treiber_stack_dynamic.default_R1W1.stderr | 3 + .../treiber_stack_dynamic.default_R1W2.stderr | 3 + .../treiber_stack_dynamic.default_R1W3.stderr | 3 + .../data-structures/treiber_stack_dynamic.rs | 150 +++++++++++++ ..._stack_dynamic.spinloop_assume_R1W1.stderr | 3 + ..._stack_dynamic.spinloop_assume_R1W2.stderr | 5 + ..._stack_dynamic.spinloop_assume_R1W3.stderr | 5 + .../miri/tests/genmc/pass/litmus/IRIWish.rs | 2 +- .../tests/genmc/pass/litmus/MPU2_rels_acqf.rs | 2 +- .../miri/tests/genmc/pass/litmus/Z6_U.rs | 3 +- .../tests/genmc/pass/thread/thread_locals.rs | 47 ++++ .../genmc/pass/thread/thread_locals.stderr | 2 + 44 files changed, 1235 insertions(+), 88 deletions(-) create mode 100644 src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs create mode 100644 src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr create mode 100644 src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr create mode 100644 src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs create mode 100644 src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs create mode 100644 src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr create mode 100644 src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs create mode 100644 src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr create mode 100644 src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs create mode 100644 src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr create mode 100644 src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr create mode 100644 src/tools/miri/tests/genmc/pass/thread/thread_locals.rs create mode 100644 src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index 8d437c20a092..ef49be151bf0 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -28,7 +28,7 @@ mod downloading { /// The GenMC repository the we get our commit from. pub(crate) const GENMC_GITHUB_URL: &str = "https://gitlab.inf.ethz.ch/public-plf/genmc.git"; /// The GenMC commit we depend on. It must be available on the specified GenMC repository. - pub(crate) const GENMC_COMMIT: &str = "af9cc9ccd5d412b16defc35dbf36571c63a19c76"; + pub(crate) const GENMC_COMMIT: &str = "cd01c12032bdd71df742b41c7817f99acc72e7ab"; /// Ensure that a local GenMC repo is present and set to the correct commit. /// Return the path of the GenMC repo and whether the checked out commit was changed. @@ -227,12 +227,16 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) { // These definitions are parsed into a cmake list and then printed to the config.h file, so they are ';' separated. let definitions = llvm_definitions.split(";"); + // These are all the C++ files we need to compile, which needs to be updated if more C++ files are added to Miri. + // We use absolute paths since relative paths can confuse IDEs when attempting to go-to-source on a path in a compiler error. + let cpp_files_base_path = Path::new("cpp/src/"); let cpp_files = [ - "./cpp/src/MiriInterface/EventHandling.cpp", - "./cpp/src/MiriInterface/Exploration.cpp", - "./cpp/src/MiriInterface/Setup.cpp", - "./cpp/src/MiriInterface/ThreadManagement.cpp", - ]; + "MiriInterface/EventHandling.cpp", + "MiriInterface/Exploration.cpp", + "MiriInterface/Setup.cpp", + "MiriInterface/ThreadManagement.cpp", + ] + .map(|file| std::path::absolute(cpp_files_base_path.join(file)).unwrap()); let mut bridge = cxx_build::bridge("src/lib.rs"); // FIXME(genmc,cmake): Remove once the GenMC debug setting is available in the config.h file. diff --git a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp index 8972341d491d..5f73e5d3d3d0 100644 --- a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp +++ b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp @@ -126,7 +126,7 @@ struct MiriGenmcShim : private GenMCDriver { /**** Memory (de)allocation ****/ auto handle_malloc(ThreadId thread_id, uint64_t size, uint64_t alignment) -> uint64_t; - void handle_free(ThreadId thread_id, uint64_t address); + auto handle_free(ThreadId thread_id, uint64_t address) -> bool; /**** Thread management ****/ void handle_thread_create(ThreadId thread_id, ThreadId parent_id); @@ -257,6 +257,7 @@ namespace GenmcScalarExt { inline GenmcScalar uninit() { return GenmcScalar { .value = 0, + .extra = 0, .is_init = false, }; } @@ -264,13 +265,14 @@ inline GenmcScalar uninit() { inline GenmcScalar from_sval(SVal sval) { return GenmcScalar { .value = sval.get(), + .extra = sval.getExtra(), .is_init = true, }; } inline SVal to_sval(GenmcScalar scalar) { ERROR_ON(!scalar.is_init, "Cannot convert an uninitialized `GenmcScalar` into an `SVal`\n"); - return SVal(scalar.value); + return SVal(scalar.value, scalar.extra); } } // namespace GenmcScalarExt diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp index fac7217ea5c8..c9e7f7a1a8a1 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp @@ -250,8 +250,9 @@ auto MiriGenmcShim::handle_malloc(ThreadId thread_id, uint64_t size, uint64_t al return ret_val.get(); } -void MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) { +auto MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) -> bool { const auto pos = inc_pos(thread_id); GenMCDriver::handleFree(pos, SAddr(address), EventDeps()); - // FIXME(genmc): add error handling once GenMC returns errors from `handleFree` + // FIXME(genmc): use returned error from `handleFree` once implemented in GenMC. + return getResult().status.has_value(); } diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp index 5e7188f17e0d..0f64083ddda6 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Exploration.cpp @@ -24,8 +24,10 @@ auto MiriGenmcShim::schedule_next( if (const auto result = GenMCDriver::scheduleNext(threads_action_)) return SchedulingResult { ExecutionState::Ok, static_cast(result.value()) }; - if (GenMCDriver::isExecutionBlocked()) + if (getExec().getGraph().isBlocked()) return SchedulingResult { ExecutionState::Blocked, 0 }; + if (getResult().status.has_value()) // the "value" here is a `VerificationError` + return SchedulingResult { ExecutionState::Error, 0 }; return SchedulingResult { ExecutionState::Finished, 0 }; } diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp index af13f0d07746..5455b1a8de7f 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Setup.cpp @@ -170,9 +170,8 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel // From a Miri perspective, this API doesn't work very well: most memory starts out // "uninitialized"; // only statics have an initial value. And their initial value is just a sequence of bytes, - // but GenMC - // expect this to be already split into separate atomic variables. So we return a dummy - // value. + // but GenMC expect this to be already split into separate atomic variables. So we return a + // dummy value. // This value should never be visible to the interpreted program. // GenMC does not understand uninitialized memory the same way Miri does, which may cause // this function to be called. The returned value can be visible to Miri or the user: @@ -183,13 +182,14 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel // Currently, atomic loads can see this value, unless initialized by an *atomic* store. // FIXME(genmc): update this comment once mixed atomic-non-atomic support is added. // - // FIXME(genmc): implement proper support for uninitialized memory in GenMC. Ideally, the - // initial value getter would return an `optional`, since the memory location may be - // uninitialized. + // FIXME(genmc): implement proper support for uninitialized memory in GenMC. + // Ideally, the initial value getter would return an `optional`, since the memory + // location may be uninitialized. .initValGetter = [](const AAccess& a) { return SVal(0xDEAD); }, - // Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in - // that case. This should no longer be required with proper mixed-size access support. - .skipUninitLoadChecks = [](MemOrdering ord) { return ord == MemOrdering::NotAtomic; }, + // Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in that + // case. This should no longer be required with proper mixed-size access support. + .skipUninitLoadChecks = [](const MemAccessLabel* access_label + ) { return access_label->getOrdering() == MemOrdering::NotAtomic; }, }; driver->setInterpCallbacks(std::move(interpreter_callbacks)); diff --git a/src/tools/miri/genmc-sys/src/lib.rs b/src/tools/miri/genmc-sys/src/lib.rs index 385afb9eaba0..619d4ab67b2f 100644 --- a/src/tools/miri/genmc-sys/src/lib.rs +++ b/src/tools/miri/genmc-sys/src/lib.rs @@ -45,10 +45,14 @@ pub fn create_genmc_driver_handle( } impl GenmcScalar { - pub const UNINIT: Self = Self { value: 0, is_init: false }; + pub const UNINIT: Self = Self { value: 0, extra: 0, is_init: false }; pub const fn from_u64(value: u64) -> Self { - Self { value, is_init: true } + Self { value, extra: 0, is_init: true } + } + + pub const fn has_provenance(&self) -> bool { + self.extra != 0 } } @@ -162,10 +166,16 @@ mod ffi { } /// This type corresponds to `Option` (or `std::optional`), where `SVal` is the type that GenMC uses for storing values. - /// CXX doesn't support `std::optional` currently, so we need to use an extra `bool` to define whether this value is initialized or not. #[derive(Debug, Clone, Copy)] struct GenmcScalar { + /// The raw byte-level value (discarding provenance, if any) of this scalar. value: u64, + /// This is zero for integer values. For pointers, this encodes the provenance by + /// storing the base address of the allocation that this pointer belongs to. + /// Operations on `SVal` in GenMC (e.g., `fetch_add`) preserve the `extra` of the left argument (`left.fetch_add(right, ...)`). + extra: u64, + /// Indicates whether this value is initialized. If this is `false`, the other fields do not matter. + /// (Ideally we'd use `std::optional` but CXX does not support that.) is_init: bool, } @@ -173,6 +183,7 @@ mod ffi { #[derive(Debug, Clone, Copy)] enum ExecutionState { Ok, + Error, Blocked, Finished, } @@ -406,7 +417,8 @@ mod ffi { size: u64, alignment: u64, ) -> u64; - fn handle_free(self: Pin<&mut MiriGenmcShim>, thread_id: i32, address: u64); + /// Returns true if an error was found. + fn handle_free(self: Pin<&mut MiriGenmcShim>, thread_id: i32, address: u64) -> bool; /**** Thread management ****/ fn handle_thread_create(self: Pin<&mut MiriGenmcShim>, thread_id: i32, parent_id: i32); diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index f011ee717850..7ad4782cd2c6 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -207,13 +207,7 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Returns the `AllocId` that corresponds to the specified addr, // or `None` if the addr is out of bounds. - // Setting `only_exposed_allocations` selects whether only exposed allocations are considered. - fn alloc_id_from_addr( - &self, - addr: u64, - size: i64, - only_exposed_allocations: bool, - ) -> Option { + fn alloc_id_from_addr(&self, addr: u64, size: i64) -> Option { let this = self.eval_context_ref(); let global_state = this.machine.alloc_addresses.borrow(); assert!(global_state.provenance_mode != ProvenanceMode::Strict); @@ -242,13 +236,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } }?; - // We only use this provenance if it has been exposed, or if the caller requested also non-exposed allocations - if !only_exposed_allocations || global_state.exposed.contains(&alloc_id) { + // We only use this provenance if it has been exposed. + if global_state.exposed.contains(&alloc_id) { // This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed. - // In GenMC mode, we keep all allocations, so this check doesn't apply there. - if this.machine.data_race.as_genmc_ref().is_none() { - debug_assert!(this.is_alloc_live(alloc_id)); - } + debug_assert!(this.is_alloc_live(alloc_id)); Some(alloc_id) } else { None @@ -443,8 +434,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { alloc_id } else { // A wildcard pointer. - let only_exposed_allocations = true; - this.alloc_id_from_addr(addr.bytes(), size, only_exposed_allocations)? + this.alloc_id_from_addr(addr.bytes(), size)? }; // This cannot fail: since we already have a pointer with that provenance, adjust_alloc_root_pointer @@ -465,13 +455,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { impl<'tcx> MiriMachine<'tcx> { pub fn free_alloc_id(&mut self, dead_id: AllocId, size: Size, align: Align, kind: MemoryKind) { - // In GenMC mode, we can't remove dead allocation info since such pointers can - // still be stored in atomics and we need this info to convert GenMC pointers to Miri pointers. - // `global_state.reuse` is also unused so we can just skip this entire function. - if self.data_race.as_genmc_ref().is_some() { - return; - } - let global_state = self.alloc_addresses.get_mut(); let rng = self.rng.get_mut(); diff --git a/src/tools/miri/src/concurrency/genmc/helper.rs b/src/tools/miri/src/concurrency/genmc/helper.rs index 48a5ec8bb260..ebbef23a2a54 100644 --- a/src/tools/miri/src/concurrency/genmc/helper.rs +++ b/src/tools/miri/src/concurrency/genmc/helper.rs @@ -5,16 +5,18 @@ use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir; +use rustc_middle::mir::interpret; use rustc_middle::ty::ScalarInt; use rustc_span::Span; use tracing::debug; use super::GenmcScalar; -use crate::diagnostics::EvalContextExt; +use crate::alloc_addresses::EvalContextExt as _; +use crate::diagnostics::EvalContextExt as _; use crate::intrinsics::AtomicRmwOp; use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, InterpCx, MiriInterpCx, - MiriMachine, NonHaltingDiagnostic, Scalar, throw_unsup_format, + AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, BorTag, GenmcCtx, InterpCx, + MiriInterpCx, MiriMachine, NonHaltingDiagnostic, Scalar, machine, throw_unsup_format, }; /// Maximum size memory access in bytes that GenMC supports. @@ -80,19 +82,30 @@ pub fn split_access(address: Size, size: Size) -> impl Iterator( - _ecx: &MiriInterpCx<'tcx>, + ecx: &MiriInterpCx<'tcx>, + genmc_ctx: &GenmcCtx, scalar: Scalar, ) -> InterpResult<'tcx, GenmcScalar> { interp_ok(match scalar { rustc_const_eval::interpret::Scalar::Int(scalar_int) => { // FIXME(genmc): Add u128 support once GenMC supports it. let value: u64 = scalar_int.to_uint(scalar_int.size()).try_into().unwrap(); - GenmcScalar { value, is_init: true } + GenmcScalar { value, extra: 0, is_init: true } + } + rustc_const_eval::interpret::Scalar::Ptr(pointer, size) => { + // FIXME(genmc,borrow tracking): Borrow tracking information is lost. + let addr = crate::Pointer::from(pointer).addr(); + if let crate::Provenance::Wildcard = pointer.provenance { + throw_unsup_format!("Pointers with wildcard provenance not allowed in GenMC mode"); + } + let (alloc_id, _size, _prov_extra) = + rustc_const_eval::interpret::Machine::ptr_get_alloc(ecx, pointer, size.into()) + .unwrap(); + let base_addr = ecx.addr_from_alloc_id(alloc_id, None)?; + // Add the base_addr alloc_id pair to the map. + genmc_ctx.exec_state.genmc_shared_allocs_map.borrow_mut().insert(base_addr, alloc_id); + GenmcScalar { value: addr.bytes(), extra: base_addr, is_init: true } } - rustc_const_eval::interpret::Scalar::Ptr(_pointer, _size) => - throw_unsup_format!( - "FIXME(genmc): Implement sending pointers (with provenance) to GenMC." - ), }) } @@ -101,16 +114,25 @@ pub fn scalar_to_genmc_scalar<'tcx>( /// Convert a `GenmcScalar` back into a Miri `Scalar`. /// For pointers, attempt to convert the stored base address of their allocation back into an `AllocId`. pub fn genmc_scalar_to_scalar<'tcx>( - _ecx: &MiriInterpCx<'tcx>, + ecx: &MiriInterpCx<'tcx>, + genmc_ctx: &GenmcCtx, scalar: GenmcScalar, size: Size, ) -> InterpResult<'tcx, Scalar> { - // FIXME(genmc): Add GenmcScalar to Miri Pointer conversion. - - // NOTE: GenMC always returns 64 bit values, and the upper bits are not yet truncated. - // FIXME(genmc): GenMC should be doing the truncation, not Miri. - let (value_scalar_int, _got_truncated) = ScalarInt::truncate_from_uint(scalar.value, size); - interp_ok(Scalar::Int(value_scalar_int)) + // If `extra` is zero, we have a regular integer. + if scalar.extra == 0 { + // NOTE: GenMC always returns 64 bit values, and the upper bits are not yet truncated. + // FIXME(genmc): GenMC should be doing the truncation, not Miri. + let (value_scalar_int, _got_truncated) = ScalarInt::truncate_from_uint(scalar.value, size); + return interp_ok(Scalar::from(value_scalar_int)); + } + // `extra` is non-zero, we have a pointer. + // When we get a pointer from GenMC, then we must have sent it to GenMC before in the same execution (since the reads-from relation is always respected). + let alloc_id = genmc_ctx.exec_state.genmc_shared_allocs_map.borrow()[&scalar.extra]; + // FIXME(genmc,borrow tracking): Borrow tracking not yet supported. + let provenance = machine::Provenance::Concrete { alloc_id, tag: BorTag::default() }; + let ptr = interpret::Pointer::new(provenance, Size::from_bytes(scalar.value)); + interp_ok(Scalar::from_pointer(ptr, &ecx.tcx)) } impl AtomicReadOrd { diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index 3e8e8fbfd491..b2f8ab6f972e 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -7,6 +7,7 @@ use genmc_sys::{ }; use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult, interp_ok}; +use rustc_data_structures::fx::FxHashMap; use rustc_middle::{throw_machine_stop, throw_ub_format, throw_unsup_format}; // FIXME(genmc,tracing): Implement some work-around for enabling debug/trace level logging (currently disabled statically in rustc). use tracing::{debug, info}; @@ -85,6 +86,9 @@ struct PerExecutionState { /// we cover all possible executions. /// `None` if no thread has called `exit` and the main thread isn't finished yet. exit_status: Cell>, + + /// Allocations in this map have been sent to GenMC, and should thus be kept around, since future loads from GenMC may return this allocation again. + genmc_shared_allocs_map: RefCell>, } impl PerExecutionState { @@ -92,6 +96,7 @@ impl PerExecutionState { self.allow_data_races.replace(false); self.thread_id_manager.borrow_mut().reset(); self.exit_status.set(None); + self.genmc_shared_allocs_map.borrow_mut().clear(); } } @@ -268,13 +273,13 @@ impl GenmcCtx { ) -> InterpResult<'tcx, Scalar> { assert!(!self.get_alloc_data_races(), "atomic load with data race checking disabled."); let genmc_old_value = if let Some(scalar) = old_val { - scalar_to_genmc_scalar(ecx, scalar)? + scalar_to_genmc_scalar(ecx, self, scalar)? } else { GenmcScalar::UNINIT }; let read_value = self.handle_load(&ecx.machine, address, size, ordering.to_genmc(), genmc_old_value)?; - genmc_scalar_to_scalar(ecx, read_value, size) + genmc_scalar_to_scalar(ecx, self, read_value, size) } /// Inform GenMC about an atomic store. @@ -291,9 +296,9 @@ impl GenmcCtx { ordering: AtomicWriteOrd, ) -> InterpResult<'tcx, bool> { assert!(!self.get_alloc_data_races(), "atomic store with data race checking disabled."); - let genmc_value = scalar_to_genmc_scalar(ecx, value)?; + let genmc_value = scalar_to_genmc_scalar(ecx, self, value)?; let genmc_old_value = if let Some(scalar) = old_value { - scalar_to_genmc_scalar(ecx, scalar)? + scalar_to_genmc_scalar(ecx, self, scalar)? } else { GenmcScalar::UNINIT }; @@ -345,8 +350,8 @@ impl GenmcCtx { size, ordering, to_genmc_rmw_op(atomic_op, is_signed), - scalar_to_genmc_scalar(ecx, rhs_scalar)?, - scalar_to_genmc_scalar(ecx, old_value)?, + scalar_to_genmc_scalar(ecx, self, rhs_scalar)?, + scalar_to_genmc_scalar(ecx, self, old_value)?, ) } @@ -368,8 +373,8 @@ impl GenmcCtx { size, ordering, /* genmc_rmw_op */ RMWBinOp::Xchg, - scalar_to_genmc_scalar(ecx, rhs_scalar)?, - scalar_to_genmc_scalar(ecx, old_value)?, + scalar_to_genmc_scalar(ecx, self, rhs_scalar)?, + scalar_to_genmc_scalar(ecx, self, old_value)?, ) } @@ -441,9 +446,9 @@ impl GenmcCtx { genmc_tid, address.bytes(), size.bytes(), - scalar_to_genmc_scalar(ecx, expected_old_value)?, - scalar_to_genmc_scalar(ecx, new_value)?, - scalar_to_genmc_scalar(ecx, old_value)?, + scalar_to_genmc_scalar(ecx, self, expected_old_value)?, + scalar_to_genmc_scalar(ecx, self, new_value)?, + scalar_to_genmc_scalar(ecx, self, old_value)?, upgraded_success_ordering.to_genmc(), fail.to_genmc(), can_fail_spuriously, @@ -454,7 +459,7 @@ impl GenmcCtx { throw_ub_format!("{}", error.to_string_lossy()); } - let return_scalar = genmc_scalar_to_scalar(ecx, cas_result.old_value, size)?; + let return_scalar = genmc_scalar_to_scalar(ecx, self, cas_result.old_value, size)?; debug!( "GenMC: atomic_compare_exchange: result: {cas_result:?}, returning scalar: {return_scalar:?}" ); @@ -644,7 +649,11 @@ impl GenmcCtx { let curr_thread = machine.threads.active_thread(); let genmc_tid = thread_infos.get_genmc_tid(curr_thread); - self.handle.borrow_mut().pin_mut().handle_free(genmc_tid, address.bytes()); + if self.handle.borrow_mut().pin_mut().handle_free(genmc_tid, address.bytes()) { + // FIXME(genmc): improve error handling. + // An error was detected, so we get the error string from GenMC. + throw_ub_format!("{}", self.try_get_error().unwrap()); + } interp_ok(()) } @@ -879,10 +888,10 @@ impl GenmcCtx { throw_ub_format!("{}", error.to_string_lossy()); } - let old_value_scalar = genmc_scalar_to_scalar(ecx, rmw_result.old_value, size)?; + let old_value_scalar = genmc_scalar_to_scalar(ecx, self, rmw_result.old_value, size)?; let new_value_scalar = if rmw_result.is_coherence_order_maximal_write { - Some(genmc_scalar_to_scalar(ecx, rmw_result.new_value, size)?) + Some(genmc_scalar_to_scalar(ecx, self, rmw_result.new_value, size)?) } else { None }; @@ -905,7 +914,10 @@ impl GenmcCtx { } impl VisitProvenance for GenmcCtx { - fn visit_provenance(&self, _visit: &mut VisitWith<'_>) { - // We don't have any tags. + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let genmc_shared_allocs_map = self.exec_state.genmc_shared_allocs_map.borrow(); + for alloc_id in genmc_shared_allocs_map.values().copied() { + visit(Some(alloc_id), None); + } } } diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index 7fb3d8fa3ef3..be7df8682f07 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -117,6 +117,14 @@ impl GenmcCtx { leak_check: exit_status.do_leak_check() }); } + ExecutionState::Error => { + // GenMC found an error in one of the `handle_*` functions, but didn't return the detected error from the function immediately. + // This is still an bug in the user program, so we print the error string. + panic!( + "GenMC found an error ({:?}), but didn't report it immediately, so we cannot provide an appropriate source code location for where it happened.", + self.try_get_error().unwrap() + ); + } _ => unreachable!(), } } diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index da8f785e3734..a0e871d87d3a 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -219,11 +219,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // so we cannot assume 1 access = 1 allocation. :( let mut rg = evt_rg.addr..evt_rg.end(); while let Some(curr) = rg.next() { - let Some(alloc_id) = this.alloc_id_from_addr( - curr.to_u64(), - rg.len().try_into().unwrap(), - /* only_exposed_allocations */ true, - ) else { + let Some(alloc_id) = + this.alloc_id_from_addr(curr.to_u64(), rg.len().try_into().unwrap()) + else { throw_ub_format!("Foreign code did an out-of-bounds access!") }; let alloc = this.get_alloc_raw(alloc_id)?; diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs new file mode 100644 index 000000000000..d712822a6d00 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can detect a double-free bug across two threads, which only shows up if the second thread reads an atomic pointer at a very specific moment. +// GenMC can detect this error consistently, without having to run the buggy code with multiple RNG seeds or in a loop. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +unsafe fn free(ptr: *mut u64) { + dealloc(ptr as *mut u8, Layout::new::()) //~ ERROR: Undefined Behavior +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + X.store(std::ptr::null_mut(), SeqCst); + + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let a: *mut u64 = alloc(Layout::new::()) as *mut u64; + X.store(a, SeqCst); + // We have to yield to the other thread exactly here to reproduce the double-free. + let b = X.swap(std::ptr::null_mut(), SeqCst); + free(b); + }), + spawn_pthread_closure(|| { + let b = X.load(SeqCst); + if !b.is_null() { + free(b); + } + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr new file mode 100644 index 000000000000..7d03bd9a8eb8 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.stderr @@ -0,0 +1,39 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | dealloc(ptr as *mut u8, Layout::new::()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | let a: *mut u64 = alloc(Layout::new::()) as *mut u64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | dealloc(ptr as *mut u8, Layout::new::()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span) on thread `unnamed-ID`: + = note: inside `free` at tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC +note: inside closure + --> tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC + | +LL | free(b); + | ^^^^^^^ + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/atomics/atomic_ptr_double_free.rs:LL:CC}>` + --> tests/genmc/fail/atomics/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr new file mode 100644 index 000000000000..d74fa12256bb --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.make.stderr @@ -0,0 +1,21 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: attempting to access 8 bytes, but got ALLOC-$HEX which points to before the beginning of the allocation + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | *ptr = 44; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | let mut b = Box::new(0u64); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `miri_start` at tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs new file mode 100644 index 000000000000..87223e990bde --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs @@ -0,0 +1,61 @@ +//@revisions: send make +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can distinguish two pointers with the same address, but different provenance, after they are sent to GenMC and back. +// We have two variants, one where we send such a pointer to GenMC, and one where we make it on the GenMC side. + +#![no_main] +#![feature(box_as_ptr)] + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let atomic_ptr = AtomicPtr::new(std::ptr::null_mut()); + let mut a = Box::new(0u64); + let mut b = Box::new(0u64); + let a_ptr: *mut u64 = Box::as_mut_ptr(&mut a); + let b_ptr: *mut u64 = Box::as_mut_ptr(&mut b); + + // Store valid pointer to `a`: + atomic_ptr.store(a_ptr, Relaxed); + let ptr = atomic_ptr.load(Relaxed); + *ptr = 42; + if *a != 42 { + std::process::abort(); + } + // Store valid pointer to `b`: + atomic_ptr.store(b_ptr, Relaxed); + let ptr = atomic_ptr.load(Relaxed); + *ptr = 43; + if *b != 43 { + std::process::abort(); + } + + // Make `atomic_ptr` contain a pointer with the provenance of `b`, but the address of `a`. + if cfg!(send) { + // Variant 1: create the invalid pointer non-atomically, then send it to GenMC. + let fake_a_ptr = b_ptr.with_addr(a_ptr.addr()); + if a_ptr.addr() != fake_a_ptr.addr() { + std::process::abort(); + } + atomic_ptr.store(fake_a_ptr, Relaxed); + } else { + // Variant 2: send `b_ptr` to GenMC, then create the invalid pointer to `a` using atomic operations. + atomic_ptr.store(b_ptr, Relaxed); + atomic_ptr.fetch_byte_add(a_ptr.addr(), Relaxed); + atomic_ptr.fetch_byte_sub(b_ptr.addr(), Relaxed); + } + let ptr = atomic_ptr.load(Relaxed); + if a_ptr.addr() != ptr.addr() { + std::process::abort(); + } + // This pointer has the same address as `a_ptr`, but not the same + // provenance, so writing to it fails. + *ptr = 44; //~ ERROR: points to before the beginning of the allocation + + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr new file mode 100644 index 000000000000..d74fa12256bb --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.send.stderr @@ -0,0 +1,21 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: attempting to access 8 bytes, but got ALLOC-$HEX which points to before the beginning of the allocation + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | *ptr = 44; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + | +LL | let mut b = Box::new(0u64); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `miri_start` at tests/genmc/fail/atomics/atomic_ptr_invalid_provenance.rs:LL:CC + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr new file mode 100644 index 000000000000..bde793014bbf --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.dealloc.stderr @@ -0,0 +1,24 @@ +Running GenMC Verification... +error: Undefined Behavior: Attempt to access freed memory + --> tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + | +LL | dealloc(b as *mut u8, Layout::new::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs new file mode 100644 index 000000000000..d82fbf53dac7 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs @@ -0,0 +1,51 @@ +//@revisions: write dealloc +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-ignore-leaks + +// Test that we can detect data races between an allocation and an unsynchronized action in another thread. +// We have two variants, an alloc-dealloc race and an alloc-write race. +// +// We never deallocate the memory, so leak-checks must be disabled. +// +// FIXME(genmc): The error message is currently suboptimal, since it mentions non-allocated/freed memory, instead of pointing towards the missing synchronization with the allocation. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + X.store(std::ptr::null_mut(), SeqCst); + + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let a: *mut u64 = alloc(Layout::new::()) as *mut u64; + X.store(a, Relaxed); // Relaxed ordering does not synchronize the alloc with the other thread. + }), + spawn_pthread_closure(|| { + let b = X.load(Relaxed); + if !b.is_null() { + if cfg!(dealloc) { + // Variant: alloc-dealloc race + dealloc(b as *mut u8, Layout::new::()); //~[dealloc] ERROR: Undefined Behavior + } else { + // Variant: alloc-write race + *b = 42; //~[write] ERROR: Undefined Behavior + } + } + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr new file mode 100644 index 000000000000..7bfafe0ca086 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.write.stderr @@ -0,0 +1,22 @@ +Running GenMC Verification... +error: Undefined Behavior: Attempt to access non-allocated memory + --> tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + | +LL | *b = 42; + | ^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs new file mode 100644 index 000000000000..c9bf1f755925 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs @@ -0,0 +1,42 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that use-after-free bugs involving atomic pointers are detected in GenMC mode. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; +use crate::utils::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); +static mut Y: u64 = 0; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + X.store(std::ptr::null_mut(), SeqCst); + + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let mut z: u64 = 1234; + X.store(&raw mut z, SeqCst); // The other thread can read this value and then access `z` after it is deallocated. + X.store(&raw mut Y, SeqCst); + }), + spawn_pthread_closure(|| { + let ptr = X.load(SeqCst); + miri_genmc_assume(!ptr.is_null()); + *ptr = 42; //~ ERROR: Undefined Behavior + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr new file mode 100644 index 000000000000..0facf6a5d177 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.stderr @@ -0,0 +1,32 @@ +Running GenMC Verification... +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling + --> tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + | +LL | *ptr = 42; + | ^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + | +LL | let mut z: u64 = 1234; + | ^^^^^ +help: ALLOC was deallocated here: + --> tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + | +LL | }), + | ^ + = note: BACKTRACE (of the first span) on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs new file mode 100644 index 000000000000..2253bac95de8 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs @@ -0,0 +1,50 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that use-after-free bugs involving atomic pointers are detected in GenMC mode. +// Compared to `atomic_ptr_dealloc_write_race.rs`, this variant checks that the data race is still detected, even if the write happens before the free. +// +// FIXME(genmc): We currently cannot show the two spans related to this error (only the second one), +// since there is no mapping between GenMC events (shown with `-Zmiri-genmc-print-genmc-output`) and Miri spans. + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; +use crate::utils::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); +static Y: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + X.store(std::ptr::null_mut(), SeqCst); + + unsafe { + let ids = [ + spawn_pthread_closure(|| { + let ptr = X.load(SeqCst); + miri_genmc_assume(!ptr.is_null()); + *ptr = 42; + }), + spawn_pthread_closure(|| { + let mut z: u64 = 1234; + X.store(&raw mut z, SeqCst); // The other thread can read this value and then access `z` after it is deallocated. + for _ in 0..10 { + // We do some extra loads here so GenMC schedules the pointer write on the other thread first. + Y.load(Relaxed); + } + // `z` gets dropped on the next line, at which point GenMC will detect the data race with the write in the other thread. + }), //~ ERROR: Undefined Behavior + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr new file mode 100644 index 000000000000..8e4ed1aba043 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.stderr @@ -0,0 +1,22 @@ +Running GenMC Verification... +error: Undefined Behavior: Attempt to access freed memory + --> tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC + | +LL | }), + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside closure at tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC + = note: inside ` as std::ops::FnOnce<()>>::call_once` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `genmc::spawn_pthread_closure::thread_func::<{closure@tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs:LL:CC}>` + --> tests/genmc/fail/data_race/../../../utils/genmc.rs:LL:CC + | +LL | f(); + | ^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs new file mode 100644 index 000000000000..9db58b1f9501 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs @@ -0,0 +1,173 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test several operations on atomic pointers. + +#![no_main] + +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::fmt::{Debug, Write}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::utils::*; + +static mut X: u64 = 0; +static mut Y: u64 = 0; + +fn assert_equals(a: T, b: T) { + if a != b { + writeln!(MiriStderr, "{:?}, {:?}", a, b).ok(); + std::process::abort(); + } +} + +/// Check that two pointers are equal and stores to one update the value read from the other. +unsafe fn pointers_equal(a: *mut u64, b: *mut u64) { + assert_equals(a, b); + assert_equals(*a, *b); + *a = 42; + assert_equals(*a, 42); + assert_equals(*b, 42); + *b = 0xAA; + assert_equals(*a, 0xAA); + assert_equals(*b, 0xAA); +} + +unsafe fn test_load_store_exchange() { + let atomic_ptr: AtomicPtr = AtomicPtr::new(&raw mut X); + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + // FIXME(genmc): Add test cases with temporal mixing of atomics/non-atomics. + atomic_ptr.store(&raw mut X, SeqCst); + + // Load can read the initial value. + pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); + // Store works as expected. + atomic_ptr.store(&raw mut Y, SeqCst); + pointers_equal(atomic_ptr.load(SeqCst), &raw mut Y); + + // Atomic swap must return the old value and store the new one. + pointers_equal(atomic_ptr.swap(&raw mut X, SeqCst), &raw mut Y); + pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); + + // Failing compare_exchange (wrong expected pointer). + match atomic_ptr.compare_exchange(&raw mut Y, std::ptr::null_mut(), SeqCst, SeqCst) { + Ok(_ptr) => std::process::abort(), + Err(ptr) => pointers_equal(ptr, &raw mut X), + } + // Failing compare_exchange (null). + match atomic_ptr.compare_exchange(std::ptr::null_mut(), std::ptr::null_mut(), SeqCst, SeqCst) { + Ok(_ptr) => std::process::abort(), + Err(ptr) => pointers_equal(ptr, &raw mut X), + } + // Successful compare_exchange. + match atomic_ptr.compare_exchange(&raw mut X, &raw mut Y, SeqCst, SeqCst) { + Ok(ptr) => pointers_equal(ptr, &raw mut X), + Err(_ptr) => std::process::abort(), + } + // compare_exchange should update the pointer. + pointers_equal(atomic_ptr.load(SeqCst), &raw mut Y); +} + +unsafe fn test_add_sub() { + const LEN: usize = 16; + let mut array: [u64; LEN] = std::array::from_fn(|i| i as u64); + + let atomic_ptr: AtomicPtr = AtomicPtr::new(&raw mut array[0]); + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + atomic_ptr.store(&raw mut array[0], SeqCst); + + // Each element of the array should be reachable using `fetch_ptr_add`. + // All pointers must stay valid. + for i in 0..LEN { + let ptr = atomic_ptr.fetch_ptr_add(1, SeqCst); + pointers_equal(ptr, &raw mut array[i]); + } + // This should return the pointer back to the start of the array. + let ptr = atomic_ptr.fetch_ptr_sub(LEN, SeqCst); + pointers_equal(ptr.offset(-(LEN as isize)), &raw mut array[0]); + pointers_equal(atomic_ptr.load(SeqCst), &raw mut array[0]); + + let array_mid_ptr = &raw mut array[LEN / 2]; + for i in 0..size_of::() { + atomic_ptr.store(array_mid_ptr, SeqCst); // Reset to test `byte_add` and `byte_sub`. + pointers_equal(array_mid_ptr, atomic_ptr.fetch_byte_add(i, SeqCst)); + if array_mid_ptr.byte_add(i) != atomic_ptr.load(SeqCst) { + std::process::abort(); + } + if array_mid_ptr.byte_add(i) != atomic_ptr.fetch_byte_sub(i, SeqCst) { + std::process::abort(); + } + pointers_equal(array_mid_ptr, atomic_ptr.load(SeqCst)); + } +} + +unsafe fn test_and_or_xor() { + const LEN: usize = 16; + #[repr(align(1024))] // Aligned to size 16 * 8 bytes. + struct AlignedArray([u64; LEN]); + + let mut array = AlignedArray(std::array::from_fn(|i| i as u64 * 10)); + let array_ptr = &raw mut array.0[0]; + + let atomic_ptr: AtomicPtr = AtomicPtr::new(array_ptr); + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + atomic_ptr.store(array_ptr, SeqCst); + + // Test no-op arguments. + assert_equals(array_ptr, atomic_ptr.fetch_or(0, SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_xor(0, SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_and(!0, SeqCst)); + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + + // Test identity arguments. + let array_addr = array_ptr as usize; + assert_equals(array_ptr, atomic_ptr.fetch_or(array_addr, SeqCst)); + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_and(array_addr, SeqCst)); + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + assert_equals(array_ptr, atomic_ptr.fetch_xor(array_addr, SeqCst)); + assert_equals(std::ptr::null_mut(), atomic_ptr.load(SeqCst)); // `null_mut` is guaranteed to have address 0. + + // Test moving within an allocation. + // The array is aligned to 64 bytes, so we can change which element we point by or/and/xor-ing the address. + let index = LEN / 2; // Choose an index in the middle of the array. + let offset = index * size_of::(); + let array_mid_ptr = &raw mut array.0[index]; + + atomic_ptr.store(array_ptr, SeqCst); // Reset to test `or`. + assert_equals(array_ptr, atomic_ptr.fetch_or(offset, SeqCst)); + assert_equals(array_mid_ptr, atomic_ptr.load(SeqCst)); + + atomic_ptr.store(array_ptr, SeqCst); // Reset to test `xor`. + assert_equals(array_ptr, atomic_ptr.fetch_xor(offset, SeqCst)); + assert_equals(array_mid_ptr, atomic_ptr.load(SeqCst)); + assert_equals(array_mid_ptr, atomic_ptr.fetch_xor(offset, SeqCst)); // two xor should yield original value. + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + + let mask = !(u64::BITS as usize - 1); + for i in 0..size_of::() { + // We offset the pointer by `i` bytes, making it unaligned. + let offset_ptr = array_ptr.byte_add(i); + atomic_ptr.store(array_ptr, SeqCst); + // `fetch_byte_add` should return the old value. + assert_equals(array_ptr, atomic_ptr.fetch_byte_add(i, SeqCst)); + // `ptr::byte_add` and `AtomicPtr::fetch_byte_add` should give the same result. + if offset_ptr != atomic_ptr.fetch_and(mask, SeqCst) { + std::process::abort(); + } + // Masking off the last bits should restore the pointer. + assert_equals(array_ptr, atomic_ptr.load(SeqCst)); + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + test_load_store_exchange(); + test_add_sub(); + test_and_or_xor(); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr new file mode 100644 index 000000000000..7867be2dbe8e --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs new file mode 100644 index 000000000000..75ee5ede9c96 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs @@ -0,0 +1,60 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can send pointers with any alignment to GenMC and back, even across threads. +// After a round-trip, the pointers should still work properly (no missing provenance). + +#![no_main] +#![allow(static_mut_refs)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use genmc::*; +use utils::*; + +static PTR: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +static mut X: [u8; 16] = [0; 16]; + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + PTR.store(std::ptr::null_mut(), SeqCst); + + unsafe { + let ids = [ + spawn_pthread_closure(|| { + for i in 0..X.len() { + X[i] = i.try_into().unwrap(); + PTR.store(&raw mut X[i], SeqCst); + // Wait for the other thread to reset the AtomicPtr. + miri_genmc_assume(PTR.load(SeqCst).is_null()); + // Check that we see the update the other thread did through the pointer. + if X[i] != (i + 1) as u8 { + std::process::abort(); + } + } + }), + spawn_pthread_closure(|| { + for i in 0..X.len() { + let x = PTR.load(SeqCst); + // Wait for the other thread to store the next pointer. + miri_genmc_assume(!x.is_null()); + // Check that we see the update when reading from the pointer. + if usize::from(*x) != i { + std::process::abort(); + } + *x = (i + 1) as u8; + PTR.store(std::ptr::null_mut(), SeqCst); + } + }), + ]; + join_pthreads(ids); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr new file mode 100644 index 000000000000..67d88dfaed71 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 33 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr new file mode 100644 index 000000000000..5b60672a09d7 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 4 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr new file mode 100644 index 000000000000..1a025216a22d --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.default_R1W2.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 188 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs new file mode 100644 index 000000000000..fda517ae7d23 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs @@ -0,0 +1,200 @@ +//@ revisions: default_R1W1 default_R1W2 spinloop_assume_R1W1 spinloop_assume_R1W2 +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// This test is a translations of the GenMC test `ms-queue-dynamic`, but with all code related to GenMC's hazard pointer API removed. +// The test leaks memory, so leak checks are disabled. +// +// Test variant naming convention: "[VARIANT_NAME]_R[#reader_threads]_W[#writer_threads]". +// We test different numbers of writer threads to see the scaling. +// Implementing optimizations such as automatic spinloop-assume transformation or symmetry reduction should reduce the number of explored executions. +// We also test variants using manual spinloop replacement, which should yield fewer executions in total compared to the unmodified code. +// +// FIXME(genmc): Add revisions `default_R1W3` and `spinloop_assume_R1W3` once Miri-GenMC performance is improved. These currently slow down the test suite too much. +// +// The test uses verbose output to see the difference between blocked and explored executions. + +#![no_main] +#![allow(static_mut_refs)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[allow(unused)] +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use genmc::*; +use libc::pthread_t; + +const MAX_THREADS: usize = 32; + +static mut QUEUE: MyStack = MyStack::new(); +static mut INPUT: [u64; MAX_THREADS] = [0; MAX_THREADS]; +static mut OUTPUT: [Option; MAX_THREADS] = [None; MAX_THREADS]; + +#[repr(C)] +struct Node { + value: u64, + next: AtomicPtr, +} + +struct MyStack { + head: AtomicPtr, + tail: AtomicPtr, +} + +impl Node { + pub unsafe fn alloc() -> *mut Self { + alloc(Layout::new::()) as *mut Self + } + + pub unsafe fn free(node: *mut Self) { + dealloc(node as *mut u8, Layout::new::()) + } +} + +impl MyStack { + pub const fn new() -> Self { + let head = AtomicPtr::new(std::ptr::null_mut()); + let tail = AtomicPtr::new(std::ptr::null_mut()); + Self { head, tail } + } + + pub unsafe fn init_queue(&mut self, _num_threads: usize) { + let dummy = Node::alloc(); + + (*dummy).next = AtomicPtr::new(std::ptr::null_mut()); + self.head = AtomicPtr::new(dummy); + self.tail = AtomicPtr::new(dummy); + + // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. + (*dummy).next.store(std::ptr::null_mut(), Relaxed); + self.head.store(dummy, Relaxed); + self.tail.store(dummy, Relaxed); + } + + pub unsafe fn clear_queue(&mut self, _num_threads: usize) { + let mut next; + let mut head = *self.head.get_mut(); + while !head.is_null() { + next = *(*head).next.get_mut(); + Node::free(head); + head = next; + } + } + + pub unsafe fn enqueue(&self, value: u64) { + let mut tail; + let node = Node::alloc(); + (*node).value = value; + (*node).next = AtomicPtr::new(std::ptr::null_mut()); + + // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. + (*node).next.store(std::ptr::null_mut(), Relaxed); + + loop { + tail = self.tail.load(Acquire); + let next = (*tail).next.load(Acquire); + if tail != self.tail.load(Acquire) { + // Looping here has no side effects, so we prevent exploring any executions where this branch happens. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + continue; + } + + if next.is_null() { + if (*tail).next.compare_exchange(next, node, Release, Relaxed).is_ok() { + break; + } + } else { + let _ = self.tail.compare_exchange(tail, next, Release, Relaxed); + } + } + + let _ = self.tail.compare_exchange(tail, node, Release, Relaxed); + } + + pub unsafe fn dequeue(&self) -> Option { + loop { + let head = self.head.load(Acquire); + let tail = self.tail.load(Acquire); + + let next = (*head).next.load(Acquire); + if self.head.load(Acquire) != head { + // Looping here has no side effects, so we prevent exploring any executions where this branch happens. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + continue; + } + if head == tail { + if next.is_null() { + return None; + } + let _ = self.tail.compare_exchange(tail, next, Release, Relaxed); + } else { + let ret_val = (*next).value; + if self.head.compare_exchange(head, next, Release, Relaxed).is_ok() { + // NOTE: The popped `Node` is leaked. + return Some(ret_val); + } + // Looping here has no side effects, so we prevent exploring any executions where this branch happens. + // All operations in the loop leading to here are either loads, or failed compare-exchange operations. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + } + } + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // We try multiple different parameters for the number and types of threads: + let (readers, writers) = if cfg!(any(default_R1W3, spinloop_assume_R1W3)) { + (1, 3) + } else if cfg!(any(default_R1W2, spinloop_assume_R1W2)) { + (1, 2) + } else { + // default_R1W1, spinloop_assume_R1W1 + (1, 1) + }; + + let num_threads = readers + writers; + if num_threads > MAX_THREADS { + std::process::abort(); + } + + let mut i = 0; + unsafe { + MyStack::init_queue(&mut QUEUE, num_threads); + + /* Spawn threads */ + let mut thread_ids: [pthread_t; MAX_THREADS] = [0; MAX_THREADS]; + for _ in 0..readers { + let pid = i as u64; + thread_ids[i] = spawn_pthread_closure(move || { + OUTPUT[pid as usize] = QUEUE.dequeue(); + }); + i += 1; + } + for _ in 0..writers { + let pid = i as u64; + thread_ids[i] = spawn_pthread_closure(move || { + INPUT[pid as usize] = pid * 10; + QUEUE.enqueue(INPUT[pid as usize]); + }); + i += 1; + } + + for i in 0..num_threads { + join_pthread(thread_ids[i]); + } + + MyStack::clear_queue(&mut QUEUE, num_threads); + } + + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr new file mode 100644 index 000000000000..5b60672a09d7 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 4 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr new file mode 100644 index 000000000000..44332a2f027e --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.spinloop_assume_R1W2.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 152 executions. No errors found. +Number of complete executions explored: 128 +Number of blocked executions seen: 24 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr new file mode 100644 index 000000000000..5452d10bb260 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr new file mode 100644 index 000000000000..98b0ced1c89a --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W2.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 22 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr new file mode 100644 index 000000000000..ba085e45e57f --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.default_R1W3.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 1002 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs new file mode 100644 index 000000000000..2101f9ae66bb --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs @@ -0,0 +1,150 @@ +//@ revisions: default_R1W1 default_R1W2 default_R1W3 spinloop_assume_R1W1 spinloop_assume_R1W2 spinloop_assume_R1W3 +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// This test is a translations of the GenMC test `treiber-stack-dynamic`, but with all code related to GenMC's hazard pointer API removed. +// The test leaks memory, so leak checks are disabled. +// +// Test variant naming convention: "[VARIANT_NAME]_R[#reader_threads]_W[#writer_threads]". +// We test different numbers of writer threads to see the scaling. +// Implementing optimizations such as automatic spinloop-assume transformation or symmetry reduction should reduce the number of explored executions. +// We also test variants using manual spinloop replacement, which should yield fewer executions in total compared to the unmodified code. +// +// The test uses verbose output to see the difference between blocked and explored executions. + +#![no_main] +#![allow(static_mut_refs)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; +#[allow(unused)] +#[path = "../../../utils/mod.rs"] +mod utils; + +use std::alloc::{Layout, alloc, dealloc}; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use genmc::*; +use libc::pthread_t; + +const MAX_THREADS: usize = 32; + +static mut STACK: MyStack = MyStack::new(); + +#[repr(C)] +struct Node { + value: u64, + next: AtomicPtr, +} + +struct MyStack { + top: AtomicPtr, +} + +impl Node { + pub unsafe fn alloc() -> *mut Self { + alloc(Layout::new::()) as *mut Self + } + + pub unsafe fn free(node: *mut Self) { + dealloc(node as *mut u8, Layout::new::()) + } +} + +impl MyStack { + pub const fn new() -> Self { + Self { top: AtomicPtr::new(std::ptr::null_mut()) } + } + + pub unsafe fn clear_stack(&mut self, _num_threads: usize) { + let mut next; + let mut top = *self.top.get_mut(); + while !top.is_null() { + next = *(*top).next.get_mut(); + Node::free(top); + top = next; + } + } + + pub unsafe fn push(&self, value: u64) { + let node = Node::alloc(); + (*node).value = value; + + loop { + let top = self.top.load(Acquire); + (*node).next.store(top, Relaxed); + if self.top.compare_exchange(top, node, Release, Relaxed).is_ok() { + break; + } + // We manually limit the number of iterations of this spinloop to 1. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + } + } + + pub unsafe fn pop(&self) -> u64 { + loop { + let top = self.top.load(Acquire); + if top.is_null() { + return 0; + } + + let next = (*top).next.load(Relaxed); + if self.top.compare_exchange(top, next, Release, Relaxed).is_ok() { + // NOTE: The popped `Node` is leaked. + return (*top).value; + } + // We manually limit the number of iterations of this spinloop to 1. + #[cfg(any(spinloop_assume_R1W1, spinloop_assume_R1W2, spinloop_assume_R1W3))] + utils::miri_genmc_assume(false); // GenMC will stop any execution that reaches this. + } + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + unsafe { STACK.top.store(std::ptr::null_mut(), Relaxed) }; + + // We try multiple different parameters for the number and types of threads: + let (readers, writers) = if cfg!(any(default_R1W3, spinloop_assume_R1W3)) { + (1, 3) + } else if cfg!(any(default_R1W2, spinloop_assume_R1W2)) { + (1, 2) + } else { + // default_R1W1, spinloop_assume_R1W1 + (1, 1) + }; + + let num_threads = readers + writers; + if num_threads > MAX_THREADS { + std::process::abort(); + } + + let mut i = 0; + unsafe { + let mut thread_ids: [pthread_t; MAX_THREADS] = [0; MAX_THREADS]; + for _ in 0..readers { + thread_ids[i] = spawn_pthread_closure(move || { + let _idx = STACK.pop(); + }); + i += 1; + } + for _ in 0..writers { + let pid = i as u64; + thread_ids[i] = spawn_pthread_closure(move || { + STACK.push(pid); + }); + i += 1; + } + + for i in 0..num_threads { + join_pthread(thread_ids[i]); + } + + MyStack::clear_stack(&mut STACK, num_threads); + } + + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr new file mode 100644 index 000000000000..5452d10bb260 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W1.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr new file mode 100644 index 000000000000..9082acd027d4 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W2.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 16 executions. No errors found. +Number of complete executions explored: 8 +Number of blocked executions seen: 8 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr new file mode 100644 index 000000000000..02ac5a982f3f --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.spinloop_assume_R1W3.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 195 executions. No errors found. +Number of complete executions explored: 36 +Number of blocked executions seen: 159 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs b/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs index c81573d59d1d..fa717b4cf18a 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs @@ -57,7 +57,7 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { join_pthreads(ids); // Print the values to check that we get all of them: - writeln!(MiriStderr, "{results:?}").unwrap_or_else(|_| std::process::abort()); + writeln!(MiriStderr, "{results:?}").ok(); 0 } diff --git a/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs b/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs index 9bb156a99970..a1b13e3f3c56 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs @@ -60,7 +60,7 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { X.load(Relaxed), Y.load(Relaxed) ) - .unwrap_or_else(|_| std::process::abort()); + .ok(); 0 } diff --git a/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs b/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs index f96679b23a5c..a991ccb0d4e0 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs @@ -55,8 +55,7 @@ fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { join_pthreads(ids); // Print the values to check that we get all of them: - writeln!(MiriStderr, "a={a}, b={b}, X={}, Y={}", X.load(Relaxed), Y.load(Relaxed)) - .unwrap_or_else(|_| std::process::abort()); + writeln!(MiriStderr, "a={a}, b={b}, X={}, Y={}", X.load(Relaxed), Y.load(Relaxed)).ok(); 0 } } diff --git a/src/tools/miri/tests/genmc/pass/thread/thread_locals.rs b/src/tools/miri/tests/genmc/pass/thread/thread_locals.rs new file mode 100644 index 000000000000..cabc8ec92da1 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/thread/thread_locals.rs @@ -0,0 +1,47 @@ +//@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows + +#![no_main] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::alloc::{Layout, alloc}; +use std::cell::Cell; +use std::sync::atomic::AtomicPtr; +use std::sync::atomic::Ordering::*; + +use crate::genmc::*; + +static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); + +thread_local! { + static R: Cell<*mut u64> = Cell::new(std::ptr::null_mut()); +} + +pub unsafe fn malloc() -> *mut u64 { + alloc(Layout::new::()) as *mut u64 +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. + X.store(std::ptr::null_mut(), SeqCst); + + unsafe { + spawn_pthread_closure(|| { + R.set(malloc()); + let r_ptr = R.get(); + let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); + }); + spawn_pthread_closure(|| { + R.set(malloc()); + }); + spawn_pthread_closure(|| { + R.set(malloc()); + let r_ptr = R.get(); + let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); + }); + + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr new file mode 100644 index 000000000000..bde951866d01 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 2 executions. No errors found. From cfefa9bc883dda6fe09a5041e35b33d17aa0969e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 29 Sep 2025 19:07:01 +0200 Subject: [PATCH 10/35] genmc: bail out for non-64bit-little-endian targets --- src/tools/miri/src/bin/miri.rs | 10 +++++----- src/tools/miri/src/concurrency/genmc/config.rs | 11 ++++++++--- src/tools/miri/src/concurrency/genmc/dummy.rs | 4 +++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 40ac5c3ff385..3c759521c6ca 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -188,6 +188,11 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { // Run in GenMC mode if enabled. if config.genmc_config.is_some() { + // Validate GenMC settings. + if let Err(err) = GenmcConfig::validate(&mut config, tcx) { + fatal_error!("Invalid settings: {err}"); + } + // This is the entry point used in GenMC mode. // This closure will be called multiple times to explore the concurrent execution space of the program. let eval_entry_once = |genmc_ctx: Rc| { @@ -745,11 +750,6 @@ fn main() { let many_seeds = many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going }); - // Validate settings for data race detection and GenMC mode. - if let Err(err) = GenmcConfig::validate_genmc_mode_settings(&mut miri_config) { - fatal_error!("Invalid settings: {err}"); - } - if miri_config.weak_memory_emulation && !miri_config.data_race_detector { fatal_error!( "Weak memory emulation cannot be enabled when the data race detector is disabled" diff --git a/src/tools/miri/src/concurrency/genmc/config.rs b/src/tools/miri/src/concurrency/genmc/config.rs index c7cfa6012b8d..a05cda46f3e7 100644 --- a/src/tools/miri/src/concurrency/genmc/config.rs +++ b/src/tools/miri/src/concurrency/genmc/config.rs @@ -1,4 +1,6 @@ use genmc_sys::LogLevel; +use rustc_abi::Endian; +use rustc_middle::ty::TyCtxt; use super::GenmcParams; use crate::{IsolatedOp, MiriConfig, RejectOpWith}; @@ -32,8 +34,6 @@ impl GenmcConfig { genmc_config: &mut Option, trimmed_arg: &str, ) -> Result<(), String> { - // FIXME(genmc): Ensure host == target somewhere. - if genmc_config.is_none() { *genmc_config = Some(Default::default()); } @@ -86,11 +86,16 @@ impl GenmcConfig { /// /// Unsupported configurations return an error. /// Adjusts Miri settings where required, printing a warnings if the change might be unexpected for the user. - pub fn validate_genmc_mode_settings(miri_config: &mut MiriConfig) -> Result<(), &'static str> { + pub fn validate(miri_config: &mut MiriConfig, tcx: TyCtxt<'_>) -> Result<(), &'static str> { let Some(genmc_config) = miri_config.genmc_config.as_mut() else { return Ok(()); }; + // Check for supported target. + if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 { + return Err("GenMC only supports 64bit little-endian targets"); + } + // Check for disallowed configurations. if !miri_config.data_race_detector { return Err("Cannot disable data race detection in GenMC mode"); diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index e260af7c4a16..ef5b24d5abad 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -1,5 +1,6 @@ use rustc_abi::{Align, Size}; use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult}; +use rustc_middle::ty::TyCtxt; pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; @@ -230,8 +231,9 @@ impl GenmcConfig { } } - pub fn validate_genmc_mode_settings( + pub fn validate( _miri_config: &mut crate::MiriConfig, + _tcx: TyCtxt<'_>, ) -> Result<(), &'static str> { Ok(()) } From 8b51a63dbbe2663403e644009dee9e35f611ae32 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 30 Sep 2025 04:53:35 +0000 Subject: [PATCH 11/35] Prepare for merging from rust-lang/rust This updates the rust-version file to 29b7717de23f3969ceeb5bef5b01d9223f807655. --- 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 1f90d4e5e498..70374af27e9c 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -f957826bff7a68b267ce75b1ea56352aed0cca0a +29b7717de23f3969ceeb5bef5b01d9223f807655 From 574ff896d63a02d29e2bb5f8348978692a9a81e2 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Fri, 14 Mar 2025 09:58:46 +0100 Subject: [PATCH 12/35] Implement support for temporal mixing of atomic/non-atomic accesses in GenMC mode. Remove atomic initialization dummy writes from tests. --- src/tools/miri/genmc-sys/build.rs | 2 +- .../genmc-sys/cpp/include/MiriInterface.hpp | 11 +- .../cpp/src/MiriInterface/EventHandling.cpp | 44 ++-- .../fail/atomics/atomic_ptr_double_free.rs | 3 - .../fail/data_race/atomic_ptr_alloc_race.rs | 3 - .../atomic_ptr_dealloc_write_race.rs | 3 - .../atomic_ptr_write_dealloc_race.rs | 3 - .../miri/tests/genmc/fail/loom/buggy_inc.rs | 3 - .../genmc/pass/atomics/atomic_ptr_ops.rs | 27 ++- .../pass/atomics/atomic_ptr_roundtrip.rs | 3 - .../atomics/cas_failure_ord_racy_key_init.rs | 3 - .../pass/atomics/mixed_atomic_non_atomic.rs | 41 ++++ .../atomics/mixed_atomic_non_atomic.stderr | 2 + .../genmc/pass/atomics/read_initial_value.rs | 41 ++++ .../pass/atomics/read_initial_value.stderr | 2 + .../miri/tests/genmc/pass/atomics/rmw_ops.rs | 5 +- .../pass/data-structures/ms_queue_dynamic.rs | 8 - .../data-structures/treiber_stack_dynamic.rs | 3 - .../genmc/pass/intercept/spinloop_assume.rs | 4 - .../miri/tests/genmc/pass/litmus/2cowr.rs | 4 - .../tests/genmc/pass/litmus/IRIW-acq-sc.rs | 4 - .../miri/tests/genmc/pass/litmus/IRIWish.rs | 4 - src/tools/miri/tests/genmc/pass/litmus/LB.rs | 4 - src/tools/miri/tests/genmc/pass/litmus/MP.rs | 4 - .../tests/genmc/pass/litmus/MPU2_rels_acqf.rs | 4 - .../tests/genmc/pass/litmus/MPU_rels_acq.rs | 4 - .../tests/genmc/pass/litmus/MP_rels_acqf.rs | 4 - src/tools/miri/tests/genmc/pass/litmus/SB.rs | 4 - .../miri/tests/genmc/pass/litmus/Z6_U.rs | 4 - .../miri/tests/genmc/pass/litmus/casdep.rs | 5 - src/tools/miri/tests/genmc/pass/litmus/ccr.rs | 3 - src/tools/miri/tests/genmc/pass/litmus/cii.rs | 3 - .../miri/tests/genmc/pass/litmus/corr.rs | 4 - .../miri/tests/genmc/pass/litmus/corr0.rs | 3 - .../miri/tests/genmc/pass/litmus/corr1.rs | 3 - .../miri/tests/genmc/pass/litmus/corr2.rs | 3 - .../miri/tests/genmc/pass/litmus/corw.rs | 3 - .../tests/genmc/pass/litmus/cumul-release.rs | 5 - .../miri/tests/genmc/pass/litmus/default.rs | 3 - .../miri/tests/genmc/pass/litmus/detour.rs | 5 - .../tests/genmc/pass/litmus/fr_w_w_w_reads.rs | 3 - .../miri/tests/genmc/pass/litmus/inc2w.rs | 3 - .../genmc/pass/std/arc.check_count.stderr | 163 +++++++++++++++ src/tools/miri/tests/genmc/pass/std/arc.rs | 49 +++++ .../genmc/pass/std/arc.try_upgrade.stderr | 191 ++++++++++++++++++ .../miri/tests/genmc/pass/std/empty_main.rs | 5 + .../tests/genmc/pass/std/empty_main.stderr | 60 ++++++ .../tests/genmc/pass/std/spawn_std_threads.rs | 13 ++ .../genmc/pass/std/spawn_std_threads.stderr | 167 +++++++++++++++ .../pass/{thread => std}/thread_locals.rs | 38 ++-- .../tests/genmc/pass/std/thread_locals.stderr | 184 +++++++++++++++++ .../genmc/pass/thread/thread_locals.stderr | 2 - 52 files changed, 978 insertions(+), 191 deletions(-) create mode 100644 src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs create mode 100644 src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr create mode 100644 src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs create mode 100644 src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr create mode 100644 src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr create mode 100644 src/tools/miri/tests/genmc/pass/std/arc.rs create mode 100644 src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr create mode 100644 src/tools/miri/tests/genmc/pass/std/empty_main.rs create mode 100644 src/tools/miri/tests/genmc/pass/std/empty_main.stderr create mode 100644 src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs create mode 100644 src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr rename src/tools/miri/tests/genmc/pass/{thread => std}/thread_locals.rs (53%) create mode 100644 src/tools/miri/tests/genmc/pass/std/thread_locals.stderr delete mode 100644 src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index ef49be151bf0..964382d82408 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -28,7 +28,7 @@ mod downloading { /// The GenMC repository the we get our commit from. pub(crate) const GENMC_GITHUB_URL: &str = "https://gitlab.inf.ethz.ch/public-plf/genmc.git"; /// The GenMC commit we depend on. It must be available on the specified GenMC repository. - pub(crate) const GENMC_COMMIT: &str = "cd01c12032bdd71df742b41c7817f99acc72e7ab"; + pub(crate) const GENMC_COMMIT: &str = "ce775ccd7866db820fa12ffca66463087a11dd96"; /// Ensure that a local GenMC repo is present and set to the correct commit. /// Return the path of the GenMC repo and whether the checked out commit was changed. diff --git a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp index 5f73e5d3d3d0..c9da718aabd6 100644 --- a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp +++ b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp @@ -214,9 +214,10 @@ struct MiriGenmcShim : private GenMCDriver { * Automatically calls `inc_pos` and `dec_pos` where needed for the given thread. */ template - auto handle_load_reset_if_none(ThreadId tid, Ts&&... params) -> HandleResult { + auto handle_load_reset_if_none(ThreadId tid, std::optional old_val, Ts&&... params) + -> HandleResult { const auto pos = inc_pos(tid); - const auto ret = GenMCDriver::handleLoad(pos, std::forward(params)...); + const auto ret = GenMCDriver::handleLoad(pos, old_val, std::forward(params)...); // If we didn't get a value, we have to reset the index of the current thread. if (!std::holds_alternative(ret)) { dec_pos(tid); @@ -274,6 +275,12 @@ inline SVal to_sval(GenmcScalar scalar) { ERROR_ON(!scalar.is_init, "Cannot convert an uninitialized `GenmcScalar` into an `SVal`\n"); return SVal(scalar.value, scalar.extra); } + +inline std::optional try_to_sval(GenmcScalar scalar) { + if (scalar.is_init) + return { SVal(scalar.value, scalar.extra) }; + return std::nullopt; +} } // namespace GenmcScalarExt namespace LoadResultExt { diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp index c9e7f7a1a8a1..a8f8b9cc24f9 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp @@ -49,6 +49,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { const auto type = AType::Unsigned; const auto ret = handle_load_reset_if_none( thread_id, + GenmcScalarExt::try_to_sval(old_val), ord, SAddr(address), ASize(size), @@ -74,6 +75,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { const auto pos = inc_pos(thread_id); const auto ret = GenMCDriver::handleStore( pos, + GenmcScalarExt::try_to_sval(old_val), ord, SAddr(address), ASize(size), @@ -84,15 +86,13 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { if (const auto* err = std::get_if(&ret)) return StoreResultExt::from_error(format_error(*err)); - if (!std::holds_alternative(ret)) - ERROR("store returned unexpected result"); - // FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once - // available). - const auto& g = getExec().getGraph(); - return StoreResultExt::ok( - /* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == pos + const bool* is_coherence_order_maximal_write = std::get_if(&ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: Store returned unexpected result." ); + return StoreResultExt::ok(*is_coherence_order_maximal_write); } void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { @@ -117,6 +117,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { // `FaiRead` and `FaiWrite`. const auto load_ret = handle_load_reset_if_none( thread_id, + GenmcScalarExt::try_to_sval(old_val), ordering, SAddr(address), ASize(size), @@ -139,6 +140,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { const auto storePos = inc_pos(thread_id); const auto store_ret = GenMCDriver::handleStore( storePos, + GenmcScalarExt::try_to_sval(old_val), ordering, SAddr(address), ASize(size), @@ -148,16 +150,15 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { if (const auto* err = std::get_if(&store_ret)) return ReadModifyWriteResultExt::from_error(format_error(*err)); - const auto* store_ret_val = std::get_if(&store_ret); - ERROR_ON(nullptr == store_ret_val, "Unimplemented: RMW store returned unexpected result."); - - // FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once - // available). - const auto& g = getExec().getGraph(); + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: RMW store returned unexpected result." + ); return ReadModifyWriteResultExt::ok( /* old_value: */ read_old_val, new_value, - /* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == storePos + *is_coherence_order_maximal_write ); } @@ -183,6 +184,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { const auto load_ret = handle_load_reset_if_none( thread_id, + GenmcScalarExt::try_to_sval(old_val), success_ordering, SAddr(address), ASize(size), @@ -203,6 +205,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { const auto storePos = inc_pos(thread_id); const auto store_ret = GenMCDriver::handleStore( storePos, + GenmcScalarExt::try_to_sval(old_val), success_ordering, SAddr(address), ASize(size), @@ -211,19 +214,12 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { ); if (const auto* err = std::get_if(&store_ret)) return CompareExchangeResultExt::from_error(format_error(*err)); - const auto* store_ret_val = std::get_if(&store_ret); + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); ERROR_ON( - nullptr == store_ret_val, + nullptr == is_coherence_order_maximal_write, "Unimplemented: compare-exchange store returned unexpected result." ); - - // FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once - // available). - const auto& g = getExec().getGraph(); - return CompareExchangeResultExt::success( - read_old_val, - /* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == storePos - ); + return CompareExchangeResultExt::success(read_old_val, *is_coherence_order_maximal_write); } /**** Memory (de)allocation ****/ diff --git a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs index d712822a6d00..c18675931719 100644 --- a/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs +++ b/src/tools/miri/tests/genmc/fail/atomics/atomic_ptr_double_free.rs @@ -22,9 +22,6 @@ unsafe fn free(ptr: *mut u64) { #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(std::ptr::null_mut(), SeqCst); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs index d82fbf53dac7..e453c16b157d 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_alloc_race.rs @@ -23,9 +23,6 @@ static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(std::ptr::null_mut(), SeqCst); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs index c9bf1f755925..10e0d8d854c2 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_dealloc_write_race.rs @@ -20,9 +20,6 @@ static mut Y: u64 = 0; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(std::ptr::null_mut(), SeqCst); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs index 2253bac95de8..e2d3057a5b0d 100644 --- a/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs +++ b/src/tools/miri/tests/genmc/fail/data_race/atomic_ptr_write_dealloc_race.rs @@ -24,9 +24,6 @@ static Y: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(std::ptr::null_mut(), SeqCst); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs b/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs index 508eae756f32..2e614e6a360b 100644 --- a/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs +++ b/src/tools/miri/tests/genmc/fail/loom/buggy_inc.rs @@ -42,9 +42,6 @@ impl BuggyInc { fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { unsafe { static BUGGY_INC: BuggyInc = BuggyInc::new(); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - BUGGY_INC.num.store(0, Relaxed); - let ids = [ spawn_pthread_closure(|| { BUGGY_INC.inc(); diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs index 9db58b1f9501..aa40e193dbfd 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_ops.rs @@ -37,18 +37,22 @@ unsafe fn pointers_equal(a: *mut u64, b: *mut u64) { unsafe fn test_load_store_exchange() { let atomic_ptr: AtomicPtr = AtomicPtr::new(&raw mut X); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - // FIXME(genmc): Add test cases with temporal mixing of atomics/non-atomics. - atomic_ptr.store(&raw mut X, SeqCst); - // Load can read the initial value. + // Atomic load can read the initial value. pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); - // Store works as expected. + // Atomic store works as expected. atomic_ptr.store(&raw mut Y, SeqCst); pointers_equal(atomic_ptr.load(SeqCst), &raw mut Y); + // We can read the value of the atomic store non-atomically. + pointers_equal(*atomic_ptr.as_ptr(), &raw mut Y); + // We can read the value of a non-atomic store atomically. + *atomic_ptr.as_ptr() = &raw mut X; + pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); // Atomic swap must return the old value and store the new one. + *atomic_ptr.as_ptr() = &raw mut Y; // Test that we can read this non-atomic store using `swap`. pointers_equal(atomic_ptr.swap(&raw mut X, SeqCst), &raw mut Y); + pointers_equal(*atomic_ptr.as_ptr(), &raw mut X); pointers_equal(atomic_ptr.load(SeqCst), &raw mut X); // Failing compare_exchange (wrong expected pointer). @@ -56,11 +60,17 @@ unsafe fn test_load_store_exchange() { Ok(_ptr) => std::process::abort(), Err(ptr) => pointers_equal(ptr, &raw mut X), } + // Non-atomic read value should also be unchanged by a failing compare_exchange. + pointers_equal(*atomic_ptr.as_ptr(), &raw mut X); + // Failing compare_exchange (null). match atomic_ptr.compare_exchange(std::ptr::null_mut(), std::ptr::null_mut(), SeqCst, SeqCst) { Ok(_ptr) => std::process::abort(), Err(ptr) => pointers_equal(ptr, &raw mut X), } + // Non-atomic read value should also be unchanged by a failing compare_exchange. + pointers_equal(*atomic_ptr.as_ptr(), &raw mut X); + // Successful compare_exchange. match atomic_ptr.compare_exchange(&raw mut X, &raw mut Y, SeqCst, SeqCst) { Ok(ptr) => pointers_equal(ptr, &raw mut X), @@ -68,15 +78,13 @@ unsafe fn test_load_store_exchange() { } // compare_exchange should update the pointer. pointers_equal(atomic_ptr.load(SeqCst), &raw mut Y); + pointers_equal(*atomic_ptr.as_ptr(), &raw mut Y); } unsafe fn test_add_sub() { const LEN: usize = 16; let mut array: [u64; LEN] = std::array::from_fn(|i| i as u64); - let atomic_ptr: AtomicPtr = AtomicPtr::new(&raw mut array[0]); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - atomic_ptr.store(&raw mut array[0], SeqCst); // Each element of the array should be reachable using `fetch_ptr_add`. // All pointers must stay valid. @@ -110,10 +118,7 @@ unsafe fn test_and_or_xor() { let mut array = AlignedArray(std::array::from_fn(|i| i as u64 * 10)); let array_ptr = &raw mut array.0[0]; - let atomic_ptr: AtomicPtr = AtomicPtr::new(array_ptr); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - atomic_ptr.store(array_ptr, SeqCst); // Test no-op arguments. assert_equals(array_ptr, atomic_ptr.fetch_or(0, SeqCst)); diff --git a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs index 75ee5ede9c96..d846a55cbc31 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/atomic_ptr_roundtrip.rs @@ -23,9 +23,6 @@ static mut X: [u8; 16] = [0; 16]; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - PTR.store(std::ptr::null_mut(), SeqCst); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs index 8a77d54a64f8..e17a988cb373 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/cas_failure_ord_racy_key_init.rs @@ -26,9 +26,6 @@ static mut VALUES: [usize; 2] = [0, 0]; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - KEY.store(KEY_SENTVAL, Relaxed); - unsafe { let mut a = 0; let mut b = 0; diff --git a/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs new file mode 100644 index 000000000000..7601b354b1c0 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.rs @@ -0,0 +1,41 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can read the value of a non-atomic store atomically and an of an atomic value non-atomically. + +#![no_main] + +use std::sync::atomic::Ordering::*; +use std::sync::atomic::{AtomicI8, AtomicU64}; + +static X: AtomicU64 = AtomicU64::new(1234); +static Y: AtomicI8 = AtomicI8::new(0xB); + +fn assert_equals(a: T, b: T) { + if a != b { + std::process::abort(); + } +} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // 8 byte unsigned integer: + // Read initial value. + assert_equals(1234, X.load(Relaxed)); + // Atomic store, non-atomic load. + X.store(0xFFFF, Relaxed); + assert_equals(0xFFFF, unsafe { *X.as_ptr() }); + // Non-atomic store, atomic load. + unsafe { *X.as_ptr() = 0xAAAA }; + assert_equals(0xAAAA, X.load(Relaxed)); + + // 1 byte signed integer: + // Read initial value. + assert_equals(0xB, Y.load(Relaxed)); + // Atomic store, non-atomic load. + Y.store(42, Relaxed); + assert_equals(42, unsafe { *Y.as_ptr() }); + // Non-atomic store, atomic load. + unsafe { *Y.as_ptr() = -42 }; + assert_equals(-42, Y.load(Relaxed)); + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr new file mode 100644 index 000000000000..7867be2dbe8e --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/mixed_atomic_non_atomic.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs new file mode 100644 index 000000000000..18e039fdd0df --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.rs @@ -0,0 +1,41 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// Test that we can read the initial value of global, heap and stack allocations in GenMC mode. + +#![no_main] + +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering::*; + +static X: AtomicU64 = AtomicU64::new(1234); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + // Read initial value of global allocation. + if 1234 != unsafe { *X.as_ptr() } { + std::process::abort(); + } + if 1234 != X.load(SeqCst) { + std::process::abort(); + } + + // Read initial value of stack allocation. + let a = AtomicU64::new(0xBB); + if 0xBB != unsafe { *a.as_ptr() } { + std::process::abort(); + } + if 0xBB != a.load(SeqCst) { + std::process::abort(); + } + + // Read initial value of heap allocation. + let b = Box::new(AtomicU64::new(0xCCC)); + if 0xCCC != unsafe { *b.as_ptr() } { + std::process::abort(); + } + if 0xCCC != b.load(SeqCst) { + std::process::abort(); + } + + 0 +} diff --git a/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr new file mode 100644 index 000000000000..7867be2dbe8e --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/atomics/read_initial_value.stderr @@ -0,0 +1,2 @@ +Running GenMC Verification... +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs b/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs index f48466e8ce10..7e6e33c8a7b1 100644 --- a/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs +++ b/src/tools/miri/tests/genmc/pass/atomics/rmw_ops.rs @@ -21,11 +21,8 @@ fn assert_eq(x: T, y: T) { macro_rules! test_rmw_edge_cases { ($int:ty, $atomic:ty) => {{ - let x = <$atomic>::new(123); - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - x.store(123, ORD); - // MAX, ADD + let x = <$atomic>::new(123); assert_eq(123, x.fetch_max(0, ORD)); // `max` keeps existing value assert_eq(123, x.fetch_max(<$int>::MAX, ORD)); // `max` stores the new value assert_eq(<$int>::MAX, x.fetch_add(10, ORD)); // `fetch_add` should be wrapping diff --git a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs index fda517ae7d23..934fc977366d 100644 --- a/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs +++ b/src/tools/miri/tests/genmc/pass/data-structures/ms_queue_dynamic.rs @@ -70,11 +70,6 @@ impl MyStack { (*dummy).next = AtomicPtr::new(std::ptr::null_mut()); self.head = AtomicPtr::new(dummy); self.tail = AtomicPtr::new(dummy); - - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - (*dummy).next.store(std::ptr::null_mut(), Relaxed); - self.head.store(dummy, Relaxed); - self.tail.store(dummy, Relaxed); } pub unsafe fn clear_queue(&mut self, _num_threads: usize) { @@ -93,9 +88,6 @@ impl MyStack { (*node).value = value; (*node).next = AtomicPtr::new(std::ptr::null_mut()); - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - (*node).next.store(std::ptr::null_mut(), Relaxed); - loop { tail = self.tail.load(Acquire); let next = (*tail).next.load(Acquire); diff --git a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs index 2101f9ae66bb..8bdd2a371f51 100644 --- a/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs +++ b/src/tools/miri/tests/genmc/pass/data-structures/treiber_stack_dynamic.rs @@ -104,9 +104,6 @@ impl MyStack { #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - unsafe { STACK.top.store(std::ptr::null_mut(), Relaxed) }; - // We try multiple different parameters for the number and types of threads: let (readers, writers) = if cfg!(any(default_R1W3, spinloop_assume_R1W3)) { (1, 3) diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs index 9d7c823b1e48..cf19e9299442 100644 --- a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs +++ b/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs @@ -58,10 +58,6 @@ fn spin_until(value: u64) { #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - unsafe { X = 0 }; - FLAG.store(0, Relaxed); - unsafe { let t0 = || { X = 42; diff --git a/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs b/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs index d3fdb470ed36..d9b582bb4362 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/2cowr.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs b/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs index 1ec62ff21342..6d2dfd4f273e 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/IRIW-acq-sc.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs b/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs index fa717b4cf18a..6f1d37962d10 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/IRIWish.rs @@ -28,10 +28,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut results = [1234; 5]; let ids = [ diff --git a/src/tools/miri/tests/genmc/pass/litmus/LB.rs b/src/tools/miri/tests/genmc/pass/litmus/LB.rs index 1cee3230b127..107121ef4e3c 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/LB.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/LB.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/MP.rs b/src/tools/miri/tests/genmc/pass/litmus/MP.rs index e245cdd15eef..5f9d1b01c37b 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MP.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MP.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs b/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs index a1b13e3f3c56..6f812bf8a8ac 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MPU2_rels_acqf.rs @@ -22,10 +22,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = Ok(1234); let mut b = Ok(1234); diff --git a/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs b/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs index b36c8a288f42..4f20b2cf9def 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MPU_rels_acq.rs @@ -18,10 +18,6 @@ use crate::genmc::*; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { X.store(1, Relaxed); diff --git a/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs b/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs index cf9f5f2dbfa3..19065d3308f8 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/MP_rels_acqf.rs @@ -18,10 +18,6 @@ use crate::genmc::*; #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { X.store(1, Relaxed); diff --git a/src/tools/miri/tests/genmc/pass/litmus/SB.rs b/src/tools/miri/tests/genmc/pass/litmus/SB.rs index e592fe05c4e4..74d45c22a295 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/SB.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/SB.rs @@ -17,10 +17,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs b/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs index a991ccb0d4e0..cbbaa82d6fb5 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/Z6_U.rs @@ -31,10 +31,6 @@ static Y: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/casdep.rs b/src/tools/miri/tests/genmc/pass/litmus/casdep.rs index c376d96b111c..8b8f6e793c1f 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/casdep.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/casdep.rs @@ -18,11 +18,6 @@ static Z: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - Z.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { let a = X.load(Relaxed); diff --git a/src/tools/miri/tests/genmc/pass/litmus/ccr.rs b/src/tools/miri/tests/genmc/pass/litmus/ccr.rs index 865895b4dcd9..4537f3d6830c 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/ccr.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/ccr.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { let expected = 0; diff --git a/src/tools/miri/tests/genmc/pass/litmus/cii.rs b/src/tools/miri/tests/genmc/pass/litmus/cii.rs index 663c806e667c..18f56860f960 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/cii.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/cii.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { spawn_pthread_closure(|| { let expected = 1; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr.rs b/src/tools/miri/tests/genmc/pass/litmus/corr.rs index d6c95100fc24..b586e2e0fa8a 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr.rs @@ -6,7 +6,6 @@ #[path = "../../../utils/genmc.rs"] mod genmc; - use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering::*; @@ -16,9 +15,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr0.rs b/src/tools/miri/tests/genmc/pass/litmus/corr0.rs index f722131fda84..856d566ca8bd 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr0.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr0.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr1.rs b/src/tools/miri/tests/genmc/pass/litmus/corr1.rs index a4e8249bac30..ccd849802911 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr1.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr1.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corr2.rs b/src/tools/miri/tests/genmc/pass/litmus/corr2.rs index 2f490d363779..36616bf36371 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corr2.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corr2.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/corw.rs b/src/tools/miri/tests/genmc/pass/litmus/corw.rs index 7acc20822a4f..9216a4f8368f 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/corw.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/corw.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let ids = [ diff --git a/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs b/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs index 2387976a8ca6..4034f7634e87 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/cumul-release.rs @@ -18,11 +18,6 @@ static Z: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - Z.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/default.rs b/src/tools/miri/tests/genmc/pass/litmus/default.rs index 55fb1ac34acb..0ab26dce419a 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/default.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/default.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut a = 1234; let mut b = 1234; diff --git a/src/tools/miri/tests/genmc/pass/litmus/detour.rs b/src/tools/miri/tests/genmc/pass/litmus/detour.rs index 7136c029bbb5..85c456d5c54e 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/detour.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/detour.rs @@ -22,11 +22,6 @@ static Z: AtomicI64 = AtomicI64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove these initializing writes once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - Y.store(0, Relaxed); - Z.store(0, Relaxed); - unsafe { // Make these static so we can exit the main thread while the other threads still run. // If these are `let mut` like the other tests, this will cause a use-after-free bug. diff --git a/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs b/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs index 796ffbf97f96..c8d3d409cf04 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/fr_w_w_w_reads.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let mut result = [1234; 4]; let ids = [ diff --git a/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs b/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs index fd8574c4f7c7..eb84304a1986 100644 --- a/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs +++ b/src/tools/miri/tests/genmc/pass/litmus/inc2w.rs @@ -16,9 +16,6 @@ static X: AtomicU64 = AtomicU64::new(0); #[unsafe(no_mangle)] fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(0, Relaxed); - unsafe { let ids = [ spawn_pthread_closure(|| { diff --git a/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr new file mode 100644 index 000000000000..f2c574283cbf --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr @@ -0,0 +1,163 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | handle.join().unwrap(); + | ^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 4 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/arc.rs b/src/tools/miri/tests/genmc/pass/std/arc.rs new file mode 100644 index 000000000000..addf6408c006 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/arc.rs @@ -0,0 +1,49 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows +//@revisions: check_count try_upgrade + +// Check that various operations on `std::sync::Arc` are handled properly in GenMC mode. +// +// The number of explored executions in the expected output of this test may change if +// the implementation of `Arc` is ever changed, or additional optimizations are added to GenMC mode. +// +// The revision that tries to upgrade the `Weak` should never explore fewer executions compared to the revision that just accesses the `strong_count`, +// since `upgrade` needs to access the `strong_count` internally. +// There should always be more than 1 execution for both, since there is always the possibilility that the `Arc` has already been dropped, or it hasn't. + +use std::sync::Arc; + +fn main() { + let data = Arc::new(42); + + // Clone the Arc, drop the original, check that memory still valid. + let data_clone = Arc::clone(&data); + drop(data); + assert!(*data_clone == 42); + + // Create a Weak reference. + let weak = Arc::downgrade(&data_clone); + + // Spawn a thread that uses the Arc. + let weak_ = weak.clone(); + let handle = std::thread::spawn(move || { + // Try to upgrade weak reference. + // Depending on execution schedule, this may fail or succeed depending on whether this runs before or after the `drop` in the main thread. + + #[cfg(check_count)] + let _strong_count = weak_.strong_count(); + + #[cfg(try_upgrade)] + if let Some(strong) = weak_.upgrade() { + assert_eq!(*strong, 42); + } + }); + + // Drop the last strong reference to the data. + drop(data_clone); + + // Wait for the thread to finish. + handle.join().unwrap(); + + // The upgrade should fail now (all Arcs dropped). + assert!(weak.upgrade().is_none()); +} diff --git a/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr new file mode 100644 index 000000000000..a7dacdeba4e4 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr @@ -0,0 +1,191 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | match this.inner().weak.compare_exchange_weak(cur, cur + 1, Acquire, Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::::downgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let weak = Arc::downgrade(&data_clone); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +... | +LL | | }); + | |______^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | handle.join().unwrap(); + | ^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if self.inner()?.strong.fetch_update(Acquire, Relaxed, checked_increment).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside `std::sync::Weak::::upgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | if let Some(strong) = weak_.upgrade() { + | ^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if self.inner()?.strong.fetch_update(Acquire, Relaxed, checked_increment).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside `std::sync::Weak::::upgrade` at RUSTLIB/alloc/src/sync.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/arc.rs:LL:CC + | +LL | if let Some(strong) = weak_.upgrade() { + | ^^^^^^^^^^^^^^^ + +Verification complete with 7 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/empty_main.rs b/src/tools/miri/tests/genmc/pass/std/empty_main.rs new file mode 100644 index 000000000000..2ffc3388fb36 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/empty_main.rs @@ -0,0 +1,5 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// A lot of code runs before main, which we should be able to handle in GenMC mode. + +fn main() {} diff --git a/src/tools/miri/tests/genmc/pass/std/empty_main.stderr b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr new file mode 100644 index 000000000000..36ba5481f472 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr @@ -0,0 +1,60 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs new file mode 100644 index 000000000000..dadbee47b986 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.rs @@ -0,0 +1,13 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows + +// We should be able to spawn and join standard library threads in GenMC mode. +// Since these threads do nothing, we should only explore 1 program execution. + +const N: usize = 2; + +fn main() { + let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + handles.into_iter().for_each(|handle| handle.join().unwrap()); +} + +fn thread_func() {} diff --git a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr new file mode 100644 index 000000000000..d6195b4e9b21 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr @@ -0,0 +1,167 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside ` as std::iter::Iterator>::fold::<(), {closure@std::iter::adapters::map::map_fold, (), {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}, {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::for_each::<{closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `std::vec::Vec::>::extend_trusted::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `> as std::vec::spec_extend::SpecExtend, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::spec_extend` at RUSTLIB/alloc/src/vec/spec_extend.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter_nested::SpecFromIterNested, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter_nested.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter::SpecFromIter, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter.rs:LL:CC + = note: inside `> as std::iter::FromIterator>>::from_iter::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::collect::>>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside ` as std::iter::Iterator>::fold::<(), {closure@std::iter::adapters::map::map_fold, (), {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}, {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>::{closure#0}}>` at RUSTLIB/core/src/iter/adapters/map.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::for_each::<{closure@std::vec::Vec>::extend_trusted, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>::{closure#0}}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `std::vec::Vec::>::extend_trusted::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `> as std::vec::spec_extend::SpecExtend, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::spec_extend` at RUSTLIB/alloc/src/vec/spec_extend.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter_nested::SpecFromIterNested, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter_nested.rs:LL:CC + = note: inside `> as std::vec::spec_from_iter::SpecFromIter, std::iter::Map, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>>::from_iter` at RUSTLIB/alloc/src/vec/spec_from_iter.rs:LL:CC + = note: inside `> as std::iter::FromIterator>>::from_iter::, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>>` at RUSTLIB/alloc/src/vec/mod.rs:LL:CC + = note: inside `, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}> as std::iter::Iterator>::collect::>>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | let handles: Vec<_> = (0..N).map(|_| std::thread::spawn(thread_func)).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside `> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>::{closure#0}}>` at RUSTLIB/alloc/src/vec/into_iter.rs:LL:CC + = note: inside `> as std::iter::Iterator>::for_each::<{closure@tests/genmc/pass/std/spawn_std_threads.rs:LL:CC}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/spawn_std_threads.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 1 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/thread/thread_locals.rs b/src/tools/miri/tests/genmc/pass/std/thread_locals.rs similarity index 53% rename from src/tools/miri/tests/genmc/pass/thread/thread_locals.rs rename to src/tools/miri/tests/genmc/pass/std/thread_locals.rs index cabc8ec92da1..d76975d2e92c 100644 --- a/src/tools/miri/tests/genmc/pass/thread/thread_locals.rs +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.rs @@ -1,17 +1,10 @@ //@compile-flags: -Zmiri-ignore-leaks -Zmiri-genmc -Zmiri-disable-stacked-borrows -#![no_main] - -#[path = "../../../utils/genmc.rs"] -mod genmc; - use std::alloc::{Layout, alloc}; use std::cell::Cell; use std::sync::atomic::AtomicPtr; use std::sync::atomic::Ordering::*; -use crate::genmc::*; - static X: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); thread_local! { @@ -22,26 +15,21 @@ pub unsafe fn malloc() -> *mut u64 { alloc(Layout::new::()) as *mut u64 } -#[unsafe(no_mangle)] -fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { - // FIXME(genmc,HACK): remove this initializing write once Miri-GenMC supports mixed atomic-non-atomic accesses. - X.store(std::ptr::null_mut(), SeqCst); - - unsafe { - spawn_pthread_closure(|| { - R.set(malloc()); +fn main() { + let handles = [ + std::thread::spawn(|| { + R.set(unsafe { malloc() }); let r_ptr = R.get(); let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); - }); - spawn_pthread_closure(|| { - R.set(malloc()); - }); - spawn_pthread_closure(|| { - R.set(malloc()); + }), + std::thread::spawn(|| { + R.set(unsafe { malloc() }); + }), + std::thread::spawn(|| { + R.set(unsafe { malloc() }); let r_ptr = R.get(); let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); - }); - - 0 - } + }), + ]; + handles.into_iter().for_each(|handle| handle.join().unwrap()); } diff --git a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr new file mode 100644 index 000000000000..ff971301bc66 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr @@ -0,0 +1,184 @@ +Running GenMC Verification... +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU64::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::option::Option::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC + = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | / std::thread::spawn(|| { +LL | | R.set(unsafe { malloc() }); +LL | | let r_ptr = R.get(); +LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); +LL | | }), + | |__________^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | / std::thread::spawn(|| { +LL | | R.set(unsafe { malloc() }); +LL | | let r_ptr = R.get(); +LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); +LL | | }), + | |__________^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/thread/mod.rs:LL:CC + | +LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | / std::thread::spawn(|| { +LL | | R.set(unsafe { malloc() }); +LL | | }), + | |__________^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/thread/mod.rs:LL:CC + | +LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | / std::thread::spawn(|| { +LL | | R.set(unsafe { malloc() }); +LL | | let r_ptr = R.get(); +LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); +LL | | }), + | |__________^ + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/alloc/src/sync.rs:LL:CC + | +LL | if this.inner().weak.compare_exchange(1, usize::MAX, Acquire, Relaxed).is_ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::Arc::>::is_unique` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::sync::Arc::>::get_mut` at RUSTLIB/alloc/src/sync.rs:LL:CC + = note: inside `std::thread::JoinInner::<'_, ()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC + = note: inside `std::thread::JoinHandle::<()>::join` at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside closure + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^ + = note: inside closure at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC + = note: inside closure at RUSTLIB/core/src/ops/try_trait.rs:LL:CC + = note: inside closure at RUSTLIB/core/src/array/iter/iter_inner.rs:LL:CC + = note: inside `::try_fold::<(), {closure@std::array::iter::iter_inner::PolymorphicIter<[std::mem::MaybeUninit>]>::try_fold<(), {closure@std::ops::try_trait::NeverShortCircuit<()>::wrap_mut_2<(), std::thread::JoinHandle<()>, {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>::{closure#0}}, std::ops::try_trait::NeverShortCircuit<()>>::{closure#0}}, std::ops::try_trait::NeverShortCircuit<()>>` at RUSTLIB/core/src/ops/index_range.rs:LL:CC + = note: inside `std::array::iter::iter_inner::PolymorphicIter::<[std::mem::MaybeUninit>]>::try_fold::<(), {closure@std::ops::try_trait::NeverShortCircuit<()>::wrap_mut_2<(), std::thread::JoinHandle<()>, {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>::{closure#0}}, std::ops::try_trait::NeverShortCircuit<()>>` at RUSTLIB/core/src/array/iter/iter_inner.rs:LL:CC + = note: inside `std::array::iter::iter_inner::PolymorphicIter::<[std::mem::MaybeUninit>]>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>` at RUSTLIB/core/src/array/iter/iter_inner.rs:LL:CC + = note: inside `, 3> as std::iter::Iterator>::fold::<(), {closure@std::iter::Iterator::for_each::call, {closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>::{closure#0}}>` at RUSTLIB/core/src/array/iter.rs:LL:CC + = note: inside `, 3> as std::iter::Iterator>::for_each::<{closure@tests/genmc/pass/std/thread_locals.rs:LL:CC}>` at RUSTLIB/core/src/iter/traits/iterator.rs:LL:CC +note: inside `main` + --> tests/genmc/pass/std/thread_locals.rs:LL:CC + | +LL | handles.into_iter().for_each(|handle| handle.join().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchgweak::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange_weak::` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicU32::compare_exchange_weak` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::sync::PLATFORM::futex::Once::call` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::Once::call_once::<{closure@std::rt::cleanup::{closure#0}}>` at RUSTLIB/std/src/sync/poison/once.rs:LL:CC + = note: inside `std::rt::cleanup` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/core/src/sync/atomic.rs:LL:CC + | +LL | intrinsics::atomic_cxchg::(dst, old, new) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sync::atomic::atomic_compare_exchange::<*mut i32>` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sync::atomic::AtomicPtr::::compare_exchange` at RUSTLIB/core/src/sync/atomic.rs:LL:CC + = note: inside `std::sys::exit_guard::unique_thread_exit` at RUSTLIB/std/src/sys/exit_guard.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::catch_unwind::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +Verification complete with 2 executions. No errors found. diff --git a/src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr deleted file mode 100644 index bde951866d01..000000000000 --- a/src/tools/miri/tests/genmc/pass/thread/thread_locals.stderr +++ /dev/null @@ -1,2 +0,0 @@ -Running GenMC Verification... -Verification complete with 2 executions. No errors found. From 38d129eeb4162c5b896c56ae68d5a8c80d690d65 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 2 Oct 2025 09:13:29 +0200 Subject: [PATCH 13/35] rustup --- src/tools/miri/rust-version | 2 +- src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr | 2 +- src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr | 2 +- src/tools/miri/tests/genmc/pass/std/empty_main.stderr | 2 +- src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr | 2 +- src/tools/miri/tests/genmc/pass/std/thread_locals.stderr | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 70374af27e9c..4e388e04ea44 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -29b7717de23f3969ceeb5bef5b01d9223f807655 +42b384ec0dfcd528d99a4db0a337d9188a9eecaa diff --git a/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr index f2c574283cbf..3dccd7059538 100644 --- a/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr +++ b/src/tools/miri/tests/genmc/pass/std/arc.check_count.stderr @@ -12,7 +12,7 @@ LL | intrinsics::atomic_cxchgweak::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC - = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr index a7dacdeba4e4..dc59632558c8 100644 --- a/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr +++ b/src/tools/miri/tests/genmc/pass/std/arc.try_upgrade.stderr @@ -12,7 +12,7 @@ LL | intrinsics::atomic_cxchgweak::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC - = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/pass/std/empty_main.stderr b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr index 36ba5481f472..44c307a6b3e4 100644 --- a/src/tools/miri/tests/genmc/pass/std/empty_main.stderr +++ b/src/tools/miri/tests/genmc/pass/std/empty_main.stderr @@ -12,7 +12,7 @@ LL | intrinsics::atomic_cxchgweak::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC - = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr index d6195b4e9b21..22a58f4e9cef 100644 --- a/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr +++ b/src/tools/miri/tests/genmc/pass/std/spawn_std_threads.stderr @@ -12,7 +12,7 @@ LL | intrinsics::atomic_cxchgweak::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC - = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr index ff971301bc66..e9b780fc007f 100644 --- a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr @@ -12,7 +12,7 @@ LL | intrinsics::atomic_cxchgweak::::unwrap_or_else::<{closure@std::thread::current::id::get_or_init::{closure#0}}>` at RUSTLIB/core/src/option.rs:LL:CC = note: inside `std::thread::current::id::get_or_init` at RUSTLIB/std/src/thread/current.rs:LL:CC - = note: inside `std::thread::current::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC + = note: inside `std::thread::current_id` at RUSTLIB/std/src/thread/current.rs:LL:CC = note: inside `std::rt::init` at RUSTLIB/std/src/rt.rs:LL:CC = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC = note: inside `std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC From a5d16293706cdf02ea275e976ce9f6c3a20c3d3f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 2 Oct 2025 09:25:19 +0200 Subject: [PATCH 14/35] make cur_span and current_span harder to mix up --- .../borrow_tracker/stacked_borrows/diagnostics.rs | 8 ++++---- .../miri/src/borrow_tracker/tree_borrows/mod.rs | 12 ++++++------ src/tools/miri/src/concurrency/data_race.rs | 14 +++++++------- src/tools/miri/src/concurrency/genmc/helper.rs | 6 +++++- src/tools/miri/src/concurrency/thread.rs | 4 ++-- src/tools/miri/src/helpers.rs | 4 ++-- src/tools/miri/src/machine.rs | 6 +++--- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 63b18028a5b9..997d7799a5f1 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -221,7 +221,7 @@ impl AllocHistory { pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_>) -> Self { Self { id, - root: (item, machine.current_span()), + root: (item, machine.current_user_relevant_span()), creations: SmallVec::new(), invalidations: SmallVec::new(), protectors: SmallVec::new(), @@ -269,11 +269,11 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { }; self.history .creations - .push(Creation { retag: op.clone(), span: self.machine.current_span() }); + .push(Creation { retag: op.clone(), span: self.machine.current_user_relevant_span() }); } pub fn log_invalidation(&mut self, tag: BorTag) { - let mut span = self.machine.current_span(); + let mut span = self.machine.current_user_relevant_span(); let (range, cause) = match &self.operation { Operation::Retag(RetagOp { info, range, permission, .. }) => { if info.cause == RetagCause::FnEntry { @@ -298,7 +298,7 @@ impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> { }; self.history .protectors - .push(Protection { tag: op.new_tag, span: self.machine.current_span() }); + .push(Protection { tag: op.new_tag, span: self.machine.current_user_relevant_span() }); } pub fn get_logs_relevant_to( diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 6e5b5c807aa2..865097af1018 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -33,7 +33,7 @@ impl<'tcx> Tree { machine: &MiriMachine<'tcx>, ) -> Self { let tag = state.root_ptr_tag(id, machine); // Fresh tag for the root - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); Tree::new(tag, size, span) } @@ -61,7 +61,7 @@ impl<'tcx> Tree { ProvenanceExtra::Wildcard => return interp_ok(()), }; let global = machine.borrow_tracker.as_ref().unwrap(); - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); self.perform_access( tag, Some((range, access_kind, diagnostics::AccessCause::Explicit(access_kind))), @@ -86,7 +86,7 @@ impl<'tcx> Tree { ProvenanceExtra::Wildcard => return interp_ok(()), }; let global = machine.borrow_tracker.as_ref().unwrap(); - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); self.dealloc(tag, alloc_range(Size::ZERO, size), global, alloc_id, span) } @@ -107,7 +107,7 @@ impl<'tcx> Tree { tag: BorTag, alloc_id: AllocId, // diagnostics ) -> InterpResult<'tcx> { - let span = machine.current_span(); + let span = machine.current_user_relevant_span(); // `None` makes it the magic on-protector-end operation self.perform_access(tag, None, global, alloc_id, span) } @@ -360,7 +360,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Some((range_in_alloc, AccessKind::Read, diagnostics::AccessCause::Reborrow)), this.machine.borrow_tracker.as_ref().unwrap(), alloc_id, - this.machine.current_span(), + this.machine.current_user_relevant_span(), )?; // Also inform the data race model (but only if any bytes are actually affected). @@ -386,7 +386,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { inside_perms, new_perm.outside_perm, protected, - this.machine.current_span(), + this.machine.current_user_relevant_span(), )?; drop(tree_borrows); diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 1ad9ace1b5d1..1f30f9d7288a 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -1208,7 +1208,7 @@ impl VClockAlloc { ty: Option>, machine: &MiriMachine<'_>, ) -> InterpResult<'tcx> { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return interp_ok(()); @@ -1250,7 +1250,7 @@ impl VClockAlloc { ty: Option>, machine: &mut MiriMachine<'_>, ) -> InterpResult<'tcx> { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_mut().unwrap(); if !global.race_detecting() { return interp_ok(()); @@ -1304,7 +1304,7 @@ impl Default for LocalClocks { impl FrameState { pub fn local_write(&self, local: mir::Local, storage_live: bool, machine: &MiriMachine<'_>) { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return; @@ -1334,7 +1334,7 @@ impl FrameState { } pub fn local_read(&self, local: mir::Local, machine: &MiriMachine<'_>) { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); let global = machine.data_race.as_vclocks_ref().unwrap(); if !global.race_detecting() { return; @@ -1573,7 +1573,7 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { size.bytes() ); - let current_span = this.machine.current_span(); + let current_span = this.machine.current_user_relevant_span(); // Perform the atomic operation. data_race.maybe_perform_sync_operation( &this.machine.threads, @@ -1827,7 +1827,7 @@ impl GlobalState { machine: &MiriMachine<'tcx>, atomic: AtomicFenceOrd, ) -> InterpResult<'tcx> { - let current_span = machine.current_span(); + let current_span = machine.current_user_relevant_span(); self.maybe_perform_sync_operation(&machine.threads, current_span, |index, mut clocks| { trace!("Atomic fence on {:?} with ordering {:?}", index, atomic); @@ -1915,7 +1915,7 @@ impl GlobalState { callback: impl FnOnce(&VClock) -> R, ) -> R { let thread = threads.active_thread(); - let span = threads.active_thread_ref().current_span(); + let span = threads.active_thread_ref().current_user_relevant_span(); let (index, mut clocks) = self.thread_state_mut(thread); let r = callback(&clocks.clock); // Increment the clock, so that all following events cannot be confused with anything that diff --git a/src/tools/miri/src/concurrency/genmc/helper.rs b/src/tools/miri/src/concurrency/genmc/helper.rs index ebbef23a2a54..b79fe0e94299 100644 --- a/src/tools/miri/src/concurrency/genmc/helper.rs +++ b/src/tools/miri/src/concurrency/genmc/helper.rs @@ -37,7 +37,11 @@ pub(super) fn emit_warning<'tcx>( cache: &WarningCache, diagnostic: impl FnOnce() -> NonHaltingDiagnostic, ) { - let span = ecx.machine.current_span(); + // FIXME: This is not the right span to use (it's always inside the local crates so if the same + // operation is invoked from multiple places it will warn multiple times). `cur_span` is not + // right either though (we should honor `#[track_caller]`). Ultimately what we want is "the + // primary span the warning would point at". + let span = ecx.machine.current_user_relevant_span(); if cache.read().unwrap().contains(&span) { return; } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 246300f8dda1..0d9dbe052e39 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -263,7 +263,7 @@ impl<'tcx> Thread<'tcx> { self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1)) } - pub fn current_span(&self) -> Span { + pub fn current_user_relevant_span(&self) -> Span { self.top_user_relevant_frame() .map(|frame_idx| self.stack[frame_idx].current_span()) .unwrap_or(rustc_span::DUMMY_SP) @@ -867,7 +867,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut state = tls::TlsDtorsState::default(); Box::new(move |m| state.on_stack_empty(m)) }); - let current_span = this.machine.current_span(); + let current_span = this.machine.current_user_relevant_span(); match &mut this.machine.data_race { GlobalDataRaceHandler::None => {} GlobalDataRaceHandler::Vclocks(data_race) => diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index e0c077e99319..3f736435bbc4 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1128,8 +1128,8 @@ impl<'tcx> MiriMachine<'tcx> { /// `#[track_caller]`. /// This function is backed by a cache, and can be assumed to be very fast. /// It will work even when the stack is empty. - pub fn current_span(&self) -> Span { - self.threads.active_thread_ref().current_span() + pub fn current_user_relevant_span(&self) -> Span { + self.threads.active_thread_ref().current_user_relevant_span() } /// Returns the span of the *caller* of the current operation, again diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 53b8fea979c7..a1e63f6ae4ec 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -919,7 +919,7 @@ impl<'tcx> MiriMachine<'tcx> { &ecx.machine.threads, size, kind, - ecx.machine.current_span(), + ecx.machine.current_user_relevant_span(), ), data_race.weak_memory.then(weak_memory::AllocState::new_allocation), ), @@ -943,7 +943,7 @@ impl<'tcx> MiriMachine<'tcx> { ecx.machine .allocation_spans .borrow_mut() - .insert(id, (ecx.machine.current_span(), None)); + .insert(id, (ecx.machine.current_user_relevant_span(), None)); } interp_ok(AllocExtra { borrow_tracker, data_race, backtrace, sync: FxHashMap::default() }) @@ -1566,7 +1566,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { } if let Some((_, deallocated_at)) = machine.allocation_spans.borrow_mut().get_mut(&alloc_id) { - *deallocated_at = Some(machine.current_span()); + *deallocated_at = Some(machine.current_user_relevant_span()); } machine.free_alloc_id(alloc_id, size, align, kind); interp_ok(()) From 8583e1582e3a2ce7e165111c9208be0224174381 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Fri, 3 Oct 2025 04:52:46 +0000 Subject: [PATCH 15/35] Prepare for merging from rust-lang/rust This updates the rust-version file to 3b8665c5ab3aeced9b01672404c3764583e722ca. --- 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 4e388e04ea44..77436ae2f825 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -42b384ec0dfcd528d99a4db0a337d9188a9eecaa +3b8665c5ab3aeced9b01672404c3764583e722ca From 19331daca8c36841acc42656338c35b8abcd1686 Mon Sep 17 00:00:00 2001 From: xizheyin Date: Thu, 7 Aug 2025 19:33:56 +0800 Subject: [PATCH 16/35] Add test sugg-swap-equality-in-macro Signed-off-by: xizheyin --- .../auxiliary/extern-macro-issue-139050.rs | 15 ++++++ ...ugg-swap-equality-in-macro-issue-139050.rs | 34 ++++++++++++++ ...swap-equality-in-macro-issue-139050.stderr | 46 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 tests/ui/typeck/auxiliary/extern-macro-issue-139050.rs create mode 100644 tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.rs create mode 100644 tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr diff --git a/tests/ui/typeck/auxiliary/extern-macro-issue-139050.rs b/tests/ui/typeck/auxiliary/extern-macro-issue-139050.rs new file mode 100644 index 000000000000..9e20c86adabc --- /dev/null +++ b/tests/ui/typeck/auxiliary/extern-macro-issue-139050.rs @@ -0,0 +1,15 @@ +#[macro_export] +macro_rules! eq { + (assert $a:expr, $b:expr) => { + match (&$a, &$b) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + panic!( + "assertion failed: `(left == right)`\n left: `{:?}`,\n right: `{:?}`", + left_val, right_val + ); + } + } + } + }; +} diff --git a/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.rs b/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.rs new file mode 100644 index 000000000000..5aa2bd7fddeb --- /dev/null +++ b/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.rs @@ -0,0 +1,34 @@ +// if we use lhs == rhs in a macro, we should not suggest to swap the equality +// because the origin span of lhs and rhs can not be found. See issue #139050 + +//@ aux-build:extern-macro-issue-139050.rs +//@ aux-crate:ext=extern-macro-issue-139050.rs + +extern crate ext; + +use std::fmt::Debug; + +macro_rules! eq_local { + (assert $a:expr, $b:expr) => { + match (&$a, &$b) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + //~^ ERROR mismatched types [E0308] + panic!( + "assertion failed: `(left == right)`\n left: `{:?}`,\n right: `{:?}`", + left_val, right_val + ); + } + } + } + }; +} + +pub fn foo(mut iter: I, value: &I::Item) +where + Item: Eq + Debug, //~ ERROR cannot find type `Item` in this scope [E0412] +{ + ext::eq!(assert iter.next(), Some(value)); //~ ERROR mismatched types [E0308] + eq_local!(assert iter.next(), Some(value)); +} +fn main() {} diff --git a/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr b/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr new file mode 100644 index 000000000000..01fc980893be --- /dev/null +++ b/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr @@ -0,0 +1,46 @@ +error[E0412]: cannot find type `Item` in this scope + --> $DIR/sugg-swap-equality-in-macro-issue-139050.rs:29:5 + | +LL | Item: Eq + Debug, + | ^^^^ not found in this scope + +error[E0308]: mismatched types + --> $DIR/sugg-swap-equality-in-macro-issue-139050.rs:31:5 + | +LL | ext::eq!(assert iter.next(), Some(value)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option<::Item>`, found `Option<&::Item>` + | + = note: expected enum `Option<_>` + found enum `Option<&_>` + = note: `Option<&::Item>` implements `PartialEq::Item>>` + = note: this error originates in the macro `ext::eq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider swapping the equality + --> $DIR/auxiliary/extern-macro-issue-139050.rs:6:22 + | +LL - if !(*left_val == *right_val) { +LL + if !(*right_val == *left_val) { + | + +error[E0308]: mismatched types + --> $DIR/sugg-swap-equality-in-macro-issue-139050.rs:15:35 + | +LL | if !(*left_val == *right_val) { + | ^^^^^^^^^^ expected `Option<::Item>`, found `Option<&::Item>` +... +LL | eq_local!(assert iter.next(), Some(value)); + | ------------------------------------------ in this macro invocation + | + = note: expected enum `Option<_>` + found enum `Option<&_>` + = note: `Option<&::Item>` implements `PartialEq::Item>>` + = note: this error originates in the macro `eq_local` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider swapping the equality + | +LL - if !(*left_val == *right_val) { +LL + if !(*right_val == *left_val) { + | + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0308, E0412. +For more information about an error, try `rustc --explain E0308`. From 94d66016e4c07a08bc2a04a3fd70a8e82a1ff53b Mon Sep 17 00:00:00 2001 From: xizheyin Date: Thu, 7 Aug 2025 19:46:24 +0800 Subject: [PATCH 17/35] Suppress suggest swapping the equality in extern macro Signed-off-by: xizheyin --- compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs | 6 +++++- .../typeck/sugg-swap-equality-in-macro-issue-139050.stderr | 7 ------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index a5c6a7f34ef9..709a8b74d0b7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -3636,7 +3636,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .must_apply_modulo_regions() { let sm = self.tcx.sess.source_map(); - if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span) + // If the span of rhs_expr or lhs_expr is in an external macro, + // we just suppress the suggestion. See issue #139050 + if !rhs_expr.span.in_external_macro(sm) + && !lhs_expr.span.in_external_macro(sm) + && let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span) && let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span) { err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`")); diff --git a/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr b/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr index 01fc980893be..96a3bc65dfdc 100644 --- a/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr +++ b/tests/ui/typeck/sugg-swap-equality-in-macro-issue-139050.stderr @@ -12,14 +12,7 @@ LL | ext::eq!(assert iter.next(), Some(value)); | = note: expected enum `Option<_>` found enum `Option<&_>` - = note: `Option<&::Item>` implements `PartialEq::Item>>` = note: this error originates in the macro `ext::eq` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider swapping the equality - --> $DIR/auxiliary/extern-macro-issue-139050.rs:6:22 - | -LL - if !(*left_val == *right_val) { -LL + if !(*right_val == *left_val) { - | error[E0308]: mismatched types --> $DIR/sugg-swap-equality-in-macro-issue-139050.rs:15:35 From f28b8d4f15f6e0f2f14c994911e2b2ec7850fd01 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 5 Oct 2025 15:37:04 +0200 Subject: [PATCH 18/35] deduplicate warnings globally --- src/tools/miri/src/alloc_addresses/mod.rs | 11 ++-- .../src/borrow_tracker/stacked_borrows/mod.rs | 4 +- .../miri/src/concurrency/genmc/helper.rs | 41 +------------ src/tools/miri/src/concurrency/genmc/mod.rs | 61 ++++++++----------- src/tools/miri/src/concurrency/genmc/run.rs | 10 +-- src/tools/miri/src/diagnostics.rs | 45 +++++++++++++- src/tools/miri/src/helpers.rs | 7 ++- src/tools/miri/src/lib.rs | 1 + src/tools/miri/src/machine.rs | 18 ------ src/tools/miri/src/shims/native_lib/mod.rs | 5 +- .../tests/genmc/pass/std/thread_locals.stderr | 34 ----------- 11 files changed, 88 insertions(+), 149 deletions(-) diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index ecaa80ebb4da..05d3444a4eb1 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -13,6 +13,7 @@ use rustc_middle::ty::TyCtxt; pub use self::address_generator::AddressGenerator; use self::reuse_pool::ReusePool; use crate::concurrency::VClock; +use crate::diagnostics::SpanDedupDiagnostic; use crate::*; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -341,12 +342,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match global_state.provenance_mode { ProvenanceMode::Default => { // The first time this happens at a particular location, print a warning. - let mut int2ptr_warned = this.machine.int2ptr_warned.borrow_mut(); - let first = int2ptr_warned.is_empty(); - if int2ptr_warned.insert(this.cur_span()) { - // Newly inserted, so first time we see this span. - this.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first }); - } + static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); + this.dedup_diagnostic(&DEDUP, |first| { + NonHaltingDiagnostic::Int2Ptr { details: first } + }); } ProvenanceMode::Strict => { throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance); diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index 5fe00ab02c4b..127d832f73e7 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -6,6 +6,7 @@ mod item; mod stack; use std::fmt::Write; +use std::sync::atomic::AtomicBool; use std::{cmp, mem}; use rustc_abi::{BackendRepr, Size}; @@ -822,7 +823,8 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> { let size = match size { Some(size) => size, None => { - if !this.machine.sb_extern_type_warned.replace(true) { + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { this.emit_diagnostic(NonHaltingDiagnostic::ExternTypeReborrow); } return interp_ok(place.clone()); diff --git a/src/tools/miri/src/concurrency/genmc/helper.rs b/src/tools/miri/src/concurrency/genmc/helper.rs index b79fe0e94299..b2e4b5aea534 100644 --- a/src/tools/miri/src/concurrency/genmc/helper.rs +++ b/src/tools/miri/src/concurrency/genmc/helper.rs @@ -1,58 +1,19 @@ -use std::sync::RwLock; - use genmc_sys::{MemOrdering, RMWBinOp}; use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; -use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir; use rustc_middle::mir::interpret; use rustc_middle::ty::ScalarInt; -use rustc_span::Span; use tracing::debug; use super::GenmcScalar; use crate::alloc_addresses::EvalContextExt as _; -use crate::diagnostics::EvalContextExt as _; use crate::intrinsics::AtomicRmwOp; -use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, BorTag, GenmcCtx, InterpCx, - MiriInterpCx, MiriMachine, NonHaltingDiagnostic, Scalar, machine, throw_unsup_format, -}; +use crate::*; /// Maximum size memory access in bytes that GenMC supports. pub(super) const MAX_ACCESS_SIZE: u64 = 8; -/// Type for storing spans for already emitted warnings. -pub(super) type WarningCache = RwLock>; - -#[derive(Default)] -pub(super) struct Warnings { - pub(super) compare_exchange_failure_ordering: WarningCache, - pub(super) compare_exchange_weak: WarningCache, -} - -/// Emit a warning if it hasn't already been reported for current span. -pub(super) fn emit_warning<'tcx>( - ecx: &InterpCx<'tcx, MiriMachine<'tcx>>, - cache: &WarningCache, - diagnostic: impl FnOnce() -> NonHaltingDiagnostic, -) { - // FIXME: This is not the right span to use (it's always inside the local crates so if the same - // operation is invoked from multiple places it will warn multiple times). `cur_span` is not - // right either though (we should honor `#[track_caller]`). Ultimately what we want is "the - // primary span the warning would point at". - let span = ecx.machine.current_user_relevant_span(); - if cache.read().unwrap().contains(&span) { - return; - } - // This span has not yet been reported, so we insert it into the cache and report it. - let mut cache = cache.write().unwrap(); - if cache.insert(span) { - // Some other thread may have added this span while we didn't hold the lock, so we only emit it if the insertions succeeded. - ecx.emit_diagnostic(diagnostic()); - } -} - /// This function is used to split up a large memory access into aligned, non-overlapping chunks of a limited size. /// Returns an iterator over the chunks, yielding `(base address, size)` of each chunk, ordered by address. pub fn split_access(address: Size, size: Size) -> impl Iterator { diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index b2f8ab6f972e..caec5e921cdf 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -14,18 +14,15 @@ use tracing::{debug, info}; use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler}; use self::helper::{ - MAX_ACCESS_SIZE, Warnings, emit_warning, genmc_scalar_to_scalar, - maybe_upgrade_compare_exchange_success_orderings, scalar_to_genmc_scalar, to_genmc_rmw_op, + MAX_ACCESS_SIZE, genmc_scalar_to_scalar, maybe_upgrade_compare_exchange_success_orderings, + scalar_to_genmc_scalar, to_genmc_rmw_op, }; use self::run::GenmcMode; use self::thread_id_map::ThreadIdMap; use crate::concurrency::genmc::helper::split_access; +use crate::diagnostics::SpanDedupDiagnostic; use crate::intrinsics::AtomicRmwOp; -use crate::{ - AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriConfig, - MiriMachine, MiriMemoryKind, NonHaltingDiagnostic, Scalar, TerminationInfo, ThreadId, - ThreadManager, VisitProvenance, VisitWith, -}; +use crate::*; mod config; mod global_allocations; @@ -104,18 +101,11 @@ struct GlobalState { /// Keep track of global allocations, to ensure they keep the same address across different executions, even if the order of allocations changes. /// The `AllocId` for globals is stable across executions, so we can use it as an identifier. global_allocations: GlobalAllocationHandler, - - /// Cache for which warnings have already been shown to the user. - /// `None` if warnings are disabled. - warning_cache: Option, } impl GlobalState { - fn new(target_usize_max: u64, print_warnings: bool) -> Self { - Self { - global_allocations: GlobalAllocationHandler::new(target_usize_max), - warning_cache: print_warnings.then(Default::default), - } + fn new(target_usize_max: u64) -> Self { + Self { global_allocations: GlobalAllocationHandler::new(target_usize_max) } } } @@ -412,27 +402,24 @@ impl GenmcCtx { let upgraded_success_ordering = maybe_upgrade_compare_exchange_success_orderings(success, fail); - if let Some(warning_cache) = &self.global_state.warning_cache { - // FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`. - let (effective_failure_ordering, _) = - upgraded_success_ordering.split_memory_orderings(); - // Return a warning if the actual orderings don't match the upgraded ones. - if success != upgraded_success_ordering || effective_failure_ordering != fail { - emit_warning(ecx, &warning_cache.compare_exchange_failure_ordering, || { - NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch { - success_ordering: success, - upgraded_success_ordering, - failure_ordering: fail, - effective_failure_ordering, - } - }); - } - // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. - if can_fail_spuriously { - emit_warning(ecx, &warning_cache.compare_exchange_weak, || { - NonHaltingDiagnostic::GenmcCompareExchangeWeak - }); - } + // FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`. + let (effective_failure_ordering, _) = upgraded_success_ordering.split_memory_orderings(); + // Return a warning if the actual orderings don't match the upgraded ones. + if success != upgraded_success_ordering || effective_failure_ordering != fail { + static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); + ecx.dedup_diagnostic(&DEDUP, |_first| { + NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch { + success_ordering: success, + upgraded_success_ordering, + failure_ordering: fail, + effective_failure_ordering, + } + }); + } + // FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`. + if can_fail_spuriously { + static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); + ecx.dedup_diagnostic(&DEDUP, |_first| NonHaltingDiagnostic::GenmcCompareExchangeWeak); } debug!( diff --git a/src/tools/miri/src/concurrency/genmc/run.rs b/src/tools/miri/src/concurrency/genmc/run.rs index 33c5b6b0a005..6721a38c683f 100644 --- a/src/tools/miri/src/concurrency/genmc/run.rs +++ b/src/tools/miri/src/concurrency/genmc/run.rs @@ -16,13 +16,6 @@ pub(super) enum GenmcMode { Verification, } -impl GenmcMode { - /// Return whether warnings on unsupported features should be printed in this mode. - fn print_unsupported_warnings(self) -> bool { - self == GenmcMode::Verification - } -} - /// Do a complete run of the program in GenMC mode. /// This will call `eval_entry` multiple times, until either: /// - An error is detected (indicated by a `None` return value) @@ -57,8 +50,7 @@ fn run_genmc_mode_impl<'tcx>( // There exists only one `global_state` per full run in GenMC mode. // It is shared by all `GenmcCtx` in this run. // FIXME(genmc): implement multithreading once GenMC supports it. - let global_state = - Arc::new(GlobalState::new(tcx.target_usize_max(), mode.print_unsupported_warnings())); + let global_state = Arc::new(GlobalState::new(tcx.target_usize_max())); let genmc_ctx = Rc::new(GenmcCtx::new(config, global_state, mode)); // `rep` is used to report the progress, Miri will panic on wrap-around. diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 15f7ccbabca6..d3486dcbb19c 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -1,9 +1,11 @@ use std::fmt::{self, Write}; use std::num::NonZero; +use std::sync::Mutex; use rustc_abi::{Align, Size}; use rustc_errors::{Diag, DiagMessage, Level}; -use rustc_span::{DUMMY_SP, SpanData, Symbol}; +use rustc_hash::FxHashSet; +use rustc_span::{DUMMY_SP, Span, SpanData, Symbol}; use crate::borrow_tracker::stacked_borrows::diagnostics::TagHistory; use crate::borrow_tracker::tree_borrows::diagnostics as tree_diagnostics; @@ -835,4 +837,45 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &this.machine, ); } + + /// Call `f` only if this is the first time we are seeing this span. + /// The `first` parameter indicates whether this is the first time *ever* that this diagnostic + /// is emitted. + fn dedup_diagnostic( + &self, + dedup: &SpanDedupDiagnostic, + f: impl FnOnce(/*first*/ bool) -> NonHaltingDiagnostic, + ) { + let this = self.eval_context_ref(); + // We want to deduplicate both based on where the error seems to be located "from the user + // perspective", and the location of the actual operation (to avoid warning about the same + // operation called from different places in the local code). + let span1 = this.machine.current_user_relevant_span(); + // For the "location of the operation", we still skip `track_caller` frames, to match the + // span that the diagnostic will point at. + let span2 = this + .active_thread_stack() + .iter() + .rev() + .find(|frame| !frame.instance().def.requires_caller_location(*this.tcx)) + .map(|frame| frame.current_span()) + .unwrap_or(span1); + + let mut lock = dedup.0.lock().unwrap(); + let first = lock.is_empty(); + // Avoid mutating the hashset unless both spans are new. + if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) { + // Both of the two spans were newly inserted. + this.emit_diagnostic(f(first)); + } + } +} + +/// Helps deduplicate a diagnostic to ensure it is only shown once per span. +pub struct SpanDedupDiagnostic(Mutex>); + +impl SpanDedupDiagnostic { + pub const fn new() -> Self { + Self(Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher))) + } } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 3f736435bbc4..53f235a077f8 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1,4 +1,5 @@ use std::num::NonZero; +use std::sync::Mutex; use std::time::Duration; use std::{cmp, iter}; @@ -6,6 +7,7 @@ use rand::RngCore; use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants}; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; +use rustc_hash::FxHashSet; use rustc_hir::Safety; use rustc_hir::def::{DefKind, Namespace}; use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE}; @@ -650,7 +652,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match reject_with { RejectOpWith::Abort => isolation_abort_error(op_name), RejectOpWith::WarningWithoutBacktrace => { - let mut emitted_warnings = this.machine.reject_in_isolation_warned.borrow_mut(); + // Deduplicate these warnings *by shim* (not by span) + static DEDUP: Mutex> = + Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher)); + let mut emitted_warnings = DEDUP.lock().unwrap(); if !emitted_warnings.contains(op_name) { // First time we are seeing this. emitted_warnings.insert(op_name.to_owned()); diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 1f82f154b0b3..ce72fc6e297d 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -60,6 +60,7 @@ extern crate rustc_ast; extern crate rustc_const_eval; extern crate rustc_data_structures; extern crate rustc_errors; +extern crate rustc_hash; extern crate rustc_hir; extern crate rustc_index; extern crate rustc_middle; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index a1e63f6ae4ec..7b93d0194119 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -649,16 +649,6 @@ pub struct MiriMachine<'tcx> { pub(crate) pthread_rwlock_sanity: Cell, pub(crate) pthread_condvar_sanity: Cell, - /// Remembers whether we already warned about an extern type with Stacked Borrows. - pub(crate) sb_extern_type_warned: Cell, - /// Remember whether we already warned about sharing memory with a native call. - #[allow(unused)] - pub(crate) native_call_mem_warned: Cell, - /// Remembers which shims have already shown the warning about erroring in isolation. - pub(crate) reject_in_isolation_warned: RefCell>, - /// Remembers which int2ptr casts we have already warned about. - pub(crate) int2ptr_warned: RefCell>, - /// Cache for `mangle_internal_symbol`. pub(crate) mangle_internal_symbol_cache: FxHashMap<&'static str, String>, @@ -826,10 +816,6 @@ impl<'tcx> MiriMachine<'tcx> { pthread_mutex_sanity: Cell::new(false), pthread_rwlock_sanity: Cell::new(false), pthread_condvar_sanity: Cell::new(false), - sb_extern_type_warned: Cell::new(false), - native_call_mem_warned: Cell::new(false), - reject_in_isolation_warned: Default::default(), - int2ptr_warned: Default::default(), mangle_internal_symbol_cache: Default::default(), force_intrinsic_fallback: config.force_intrinsic_fallback, float_nondet: config.float_nondet, @@ -1003,10 +989,6 @@ impl VisitProvenance for MiriMachine<'_> { pthread_mutex_sanity: _, pthread_rwlock_sanity: _, pthread_condvar_sanity: _, - sb_extern_type_warned: _, - native_call_mem_warned: _, - reject_in_isolation_warned: _, - int2ptr_warned: _, mangle_internal_symbol_cache: _, force_intrinsic_fallback: _, float_nondet: _, diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index a0e871d87d3a..914c666adb30 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -1,6 +1,7 @@ //! Implements calling functions from a native library. use std::ops::Deref; +use std::sync::atomic::AtomicBool; use libffi::low::CodePtr; use libffi::middle::Type as FfiType; @@ -279,8 +280,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Helper to print a warning when a pointer is shared with the native code. let expose = |prov: Provenance| -> InterpResult<'tcx> { - // The first time this happens, print a warning. - if !this.machine.native_call_mem_warned.replace(true) { + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { // Newly set, so first time we get here. this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing }); } diff --git a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr index e9b780fc007f..40faedf49c6e 100644 --- a/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr +++ b/src/tools/miri/tests/genmc/pass/std/thread_locals.stderr @@ -81,40 +81,6 @@ LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCs LL | | }), | |__________^ -warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. - --> RUSTLIB/std/src/thread/mod.rs:LL:CC - | -LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - | - = note: BACKTRACE: - = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC -note: inside `main` - --> tests/genmc/pass/std/thread_locals.rs:LL:CC - | -LL | / std::thread::spawn(|| { -LL | | R.set(unsafe { malloc() }); -LL | | }), - | |__________^ - -warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. - --> RUSTLIB/std/src/thread/mod.rs:LL:CC - | -LL | match COUNTER.compare_exchange_weak(last, id, Ordering::Relaxed, Ordering::Relaxed) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GenMC might miss possible behaviors of this code - | - = note: BACKTRACE: - = note: inside `std::thread::ThreadId::new` at RUSTLIB/std/src/thread/mod.rs:LL:CC -note: inside `main` - --> tests/genmc/pass/std/thread_locals.rs:LL:CC - | -LL | / std::thread::spawn(|| { -LL | | R.set(unsafe { malloc() }); -LL | | let r_ptr = R.get(); -LL | | let _ = X.compare_exchange(std::ptr::null_mut(), r_ptr, SeqCst, SeqCst); -LL | | }), - | |__________^ - warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. --> RUSTLIB/alloc/src/sync.rs:LL:CC | From 4203fe84fb45db376436d1f7f33d68bb024d6fc1 Mon Sep 17 00:00:00 2001 From: Patrick-6 Date: Fri, 12 Sep 2025 14:05:17 +0200 Subject: [PATCH 19/35] Implement std::sync::Mutex interception in GenMC mode. --- src/tools/miri/genmc-sys/build.rs | 1 + .../genmc-sys/cpp/include/MiriInterface.hpp | 31 ++- .../cpp/src/MiriInterface/EventHandling.cpp | 11 +- .../genmc-sys/cpp/src/MiriInterface/Mutex.cpp | 159 ++++++++++++ .../src/MiriInterface/ThreadManagement.cpp | 1 + src/tools/miri/genmc-sys/src/lib.rs | 45 +++- src/tools/miri/src/concurrency/genmc/dummy.rs | 9 + .../miri/src/concurrency/genmc/intercept.rs | 38 --- src/tools/miri/src/concurrency/genmc/mod.rs | 86 +++---- .../miri/src/concurrency/genmc/scheduling.rs | 14 +- src/tools/miri/src/concurrency/genmc/shims.rs | 234 ++++++++++++++++++ src/tools/miri/src/concurrency/thread.rs | 38 +-- src/tools/miri/src/lib.rs | 1 + src/tools/miri/src/machine.rs | 15 +- .../fail/shims/mutex_diff_thread_unlock.rs | 28 +++ .../shims/mutex_diff_thread_unlock.stderr | 86 +++++++ .../genmc/fail/shims/mutex_double_unlock.rs | 22 ++ .../fail/shims/mutex_double_unlock.stderr | 23 ++ .../tests/genmc/pass/shims/mutex_deadlock.rs | 42 ++++ .../genmc/pass/shims/mutex_deadlock.stderr | 5 + .../tests/genmc/pass/shims/mutex_simple.rs | 68 +++++ .../genmc/pass/shims/mutex_simple.stderr | 3 + .../spinloop_assume.bounded123.stderr | 0 .../spinloop_assume.bounded321.stderr | 0 .../spinloop_assume.replaced123.stderr | 0 .../spinloop_assume.replaced321.stderr | 0 .../{intercept => shims}/spinloop_assume.rs | 0 27 files changed, 838 insertions(+), 122 deletions(-) create mode 100644 src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp delete mode 100644 src/tools/miri/src/concurrency/genmc/intercept.rs create mode 100644 src/tools/miri/src/concurrency/genmc/shims.rs create mode 100644 src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs create mode 100644 src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr create mode 100644 src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs create mode 100644 src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr create mode 100644 src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs create mode 100644 src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr create mode 100644 src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs create mode 100644 src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr rename src/tools/miri/tests/genmc/pass/{intercept => shims}/spinloop_assume.bounded123.stderr (100%) rename src/tools/miri/tests/genmc/pass/{intercept => shims}/spinloop_assume.bounded321.stderr (100%) rename src/tools/miri/tests/genmc/pass/{intercept => shims}/spinloop_assume.replaced123.stderr (100%) rename src/tools/miri/tests/genmc/pass/{intercept => shims}/spinloop_assume.replaced321.stderr (100%) rename src/tools/miri/tests/genmc/pass/{intercept => shims}/spinloop_assume.rs (100%) diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs index 964382d82408..f10e151a3d0b 100644 --- a/src/tools/miri/genmc-sys/build.rs +++ b/src/tools/miri/genmc-sys/build.rs @@ -233,6 +233,7 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) { let cpp_files = [ "MiriInterface/EventHandling.cpp", "MiriInterface/Exploration.cpp", + "MiriInterface/Mutex.cpp", "MiriInterface/Setup.cpp", "MiriInterface/ThreadManagement.cpp", ] diff --git a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp index c9da718aabd6..b0bd397ab34b 100644 --- a/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp +++ b/src/tools/miri/genmc-sys/cpp/include/MiriInterface.hpp @@ -12,7 +12,6 @@ // GenMC headers: #include "ExecutionGraph/EventLabel.hpp" -#include "Static/ModuleID.hpp" #include "Support/MemOrdering.hpp" #include "Support/RMWOps.hpp" #include "Verification/Config.hpp" @@ -36,6 +35,7 @@ struct LoadResult; struct StoreResult; struct ReadModifyWriteResult; struct CompareExchangeResult; +struct MutexLockResult; // GenMC uses `int` for its thread IDs. using ThreadId = int; @@ -136,10 +136,13 @@ struct MiriGenmcShim : private GenMCDriver { /**** Blocking instructions ****/ /// Inform GenMC that the thread should be blocked. - /// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user - /// supplied assume statements. This can become a parameter once more types of assumes are - /// added. - void handle_assume_block(ThreadId thread_id); + void handle_assume_block(ThreadId thread_id, AssumeType assume_type); + + /**** Mutex handling ****/ + auto handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) -> MutexLockResult; + auto handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size) + -> MutexLockResult; + auto handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) -> StoreResult; /***** Exploration related functionality *****/ @@ -358,4 +361,22 @@ inline CompareExchangeResult from_error(std::unique_ptr error) { } } // namespace CompareExchangeResultExt +namespace MutexLockResultExt { +inline MutexLockResult ok(bool is_lock_acquired) { + return MutexLockResult { /* error: */ nullptr, /* is_reset: */ false, is_lock_acquired }; +} + +inline MutexLockResult reset() { + return MutexLockResult { /* error: */ nullptr, + /* is_reset: */ true, + /* is_lock_acquired: */ false }; +} + +inline MutexLockResult from_error(std::unique_ptr error) { + return MutexLockResult { /* error: */ std::move(error), + /* is_reset: */ false, + /* is_lock_acquired: */ false }; +} +} // namespace MutexLockResultExt + #endif /* GENMC_MIRI_INTERFACE_HPP */ diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp index a8f8b9cc24f9..2b6e5749d41a 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/EventHandling.cpp @@ -32,8 +32,9 @@ /**** Blocking instructions ****/ -void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { - GenMCDriver::handleAssume(inc_pos(thread_id), AssumeType::User); +void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_type) { + BUG_ON(getExec().getGraph().isThreadBlocked(thread_id)); + GenMCDriver::handleAssume(inc_pos(thread_id), assume_type); } /**** Memory access handling ****/ @@ -59,6 +60,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { if (const auto* err = std::get_if(&ret)) return LoadResultExt::from_error(format_error(*err)); const auto* ret_val = std::get_if(&ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. if (ret_val == nullptr) ERROR("Unimplemented: load returned unexpected result."); return LoadResultExt::from_value(*ret_val); @@ -88,6 +90,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) { return StoreResultExt::from_error(format_error(*err)); const bool* is_coherence_order_maximal_write = std::get_if(&ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. ERROR_ON( nullptr == is_coherence_order_maximal_write, "Unimplemented: Store returned unexpected result." @@ -130,6 +133,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { return ReadModifyWriteResultExt::from_error(format_error(*err)); const auto* ret_val = std::get_if(&load_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. if (nullptr == ret_val) { ERROR("Unimplemented: read-modify-write returned unexpected result."); } @@ -151,6 +155,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { return ReadModifyWriteResultExt::from_error(format_error(*err)); const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. ERROR_ON( nullptr == is_coherence_order_maximal_write, "Unimplemented: RMW store returned unexpected result." @@ -195,6 +200,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { if (const auto* err = std::get_if(&load_ret)) return CompareExchangeResultExt::from_error(format_error(*err)); const auto* ret_val = std::get_if(&load_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. ERROR_ON(nullptr == ret_val, "Unimplemented: load returned unexpected result."); const auto read_old_val = *ret_val; if (read_old_val != expectedVal) @@ -215,6 +221,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) { if (const auto* err = std::get_if(&store_ret)) return CompareExchangeResultExt::from_error(format_error(*err)); const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + // FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values. ERROR_ON( nullptr == is_coherence_order_maximal_write, "Unimplemented: compare-exchange store returned unexpected result." diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp new file mode 100644 index 000000000000..fc3f5e6e09a6 --- /dev/null +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/Mutex.cpp @@ -0,0 +1,159 @@ +/** This file contains functionality related to handling mutexes. */ + +#include "MiriInterface.hpp" + +// GenMC headers: +#include "Static/ModuleID.hpp" + +// CXX.rs generated headers: +#include "genmc-sys/src/lib.rs.h" + +#define MUTEX_UNLOCKED SVal(0) +#define MUTEX_LOCKED SVal(1) + +auto MiriGenmcShim::handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) + -> MutexLockResult { + // This annotation informs GenMC about the condition required to make this lock call succeed. + // It stands for `value_read_by_load != MUTEX_LOCKED`. + const auto size_bits = size * 8; + const auto annot = std::move(Annotation( + AssumeType::Spinloop, + Annotation::ExprVP( + NeExpr::create( + // `RegisterExpr` marks the value of the current expression, i.e., the loaded value. + // The `id` is ignored by GenMC; it is only used by the LLI frontend to substitute + // other variables from previous expressions that may be used here. + RegisterExpr::create(size_bits, /* id */ 0), + ConcreteExpr::create(size_bits, MUTEX_LOCKED) + ) + .release() + ) + )); + + // As usual, we need to tell GenMC which value was stored at this location before this atomic + // access, if there previously was a non-atomic initializing access. We set the initial state of + // a mutex to be "unlocked". + const auto old_val = MUTEX_UNLOCKED; + const auto load_ret = handle_load_reset_if_none( + thread_id, + old_val, + address, + size, + annot, + EventDeps() + ); + if (const auto* err = std::get_if(&load_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + // If we get a `Reset`, GenMC decided that this lock operation should not yet run, since it + // would not acquire the mutex. Like the handling of the case further down where we read a `1` + // ("Mutex already locked"), Miri should call the handle function again once the current thread + // is scheduled by GenMC the next time. + if (std::holds_alternative(load_ret)) + return MutexLockResultExt::reset(); + + const auto* ret_val = std::get_if(&load_ret); + ERROR_ON(!ret_val, "Unimplemented: mutex lock returned unexpected result."); + ERROR_ON( + *ret_val != MUTEX_UNLOCKED && *ret_val != MUTEX_LOCKED, + "Mutex read value was neither 0 nor 1" + ); + const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED; + if (is_lock_acquired) { + const auto store_ret = GenMCDriver::handleStore( + inc_pos(thread_id), + old_val, + address, + size, + EventDeps() + ); + if (const auto* err = std::get_if(&store_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + // We don't update Miri's memory for this operation so we don't need to know if the store + // was the co-maximal store, but we still check that we at least get a boolean as the result + // of the store. + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: store part of mutex try_lock returned unexpected result." + ); + } else { + // We did not acquire the mutex, so we tell GenMC to block the thread until we can acquire + // it. GenMC determines this based on the annotation we pass with the load further up in + // this function, namely when that load will read a value other than `MUTEX_LOCKED`. + this->handle_assume_block(thread_id, AssumeType::Spinloop); + } + return MutexLockResultExt::ok(is_lock_acquired); +} + +auto MiriGenmcShim::handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size) + -> MutexLockResult { + auto& currPos = threads_action_[thread_id].event; + // As usual, we need to tell GenMC which value was stored at this location before this atomic + // access, if there previously was a non-atomic initializing access. We set the initial state of + // a mutex to be "unlocked". + const auto old_val = MUTEX_UNLOCKED; + const auto load_ret = GenMCDriver::handleLoad( + ++currPos, + old_val, + SAddr(address), + ASize(size) + ); + if (const auto* err = std::get_if(&load_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + const auto* ret_val = std::get_if(&load_ret); + if (nullptr == ret_val) { + ERROR("Unimplemented: mutex trylock load returned unexpected result."); + } + + ERROR_ON( + *ret_val != MUTEX_UNLOCKED && *ret_val != MUTEX_LOCKED, + "Mutex read value was neither 0 nor 1" + ); + const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED; + if (!is_lock_acquired) { + return MutexLockResultExt::ok(false); /* Lock already held. */ + } + + const auto store_ret = GenMCDriver::handleStore( + ++currPos, + old_val, + SAddr(address), + ASize(size) + ); + if (const auto* err = std::get_if(&store_ret)) + return MutexLockResultExt::from_error(format_error(*err)); + // We don't update Miri's memory for this operation so we don't need to know if the store was + // co-maximal, but we still check that we get a boolean result. + const bool* is_coherence_order_maximal_write = std::get_if(&store_ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: store part of mutex try_lock returned unexpected result." + ); + return MutexLockResultExt::ok(true); +} + +auto MiriGenmcShim::handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) + -> StoreResult { + const auto pos = inc_pos(thread_id); + const auto ret = GenMCDriver::handleStore( + pos, + // As usual, we need to tell GenMC which value was stored at this location before this + // atomic access, if there previously was a non-atomic initializing access. We set the + // initial state of a mutex to be "unlocked". + /* old_val */ MUTEX_UNLOCKED, + MemOrdering::Release, + SAddr(address), + ASize(size), + AType::Signed, + /* store_value */ MUTEX_UNLOCKED, + EventDeps() + ); + if (const auto* err = std::get_if(&ret)) + return StoreResultExt::from_error(format_error(*err)); + const bool* is_coherence_order_maximal_write = std::get_if(&ret); + ERROR_ON( + nullptr == is_coherence_order_maximal_write, + "Unimplemented: store part of mutex unlock returned unexpected result." + ); + return StoreResultExt::ok(*is_coherence_order_maximal_write); +} diff --git a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp index 352d27adc3e8..d2061fcb406c 100644 --- a/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp +++ b/src/tools/miri/genmc-sys/cpp/src/MiriInterface/ThreadManagement.cpp @@ -38,6 +38,7 @@ void MiriGenmcShim::handle_thread_join(ThreadId thread_id, ThreadId child_id) { if (!std::holds_alternative(ret)) { dec_pos(thread_id); } + // FIXME(genmc): handle `HandleResult::{Invalid, Reset, VerificationError}` return values. // NOTE: Thread return value is ignored, since Miri doesn't need it. } diff --git a/src/tools/miri/genmc-sys/src/lib.rs b/src/tools/miri/genmc-sys/src/lib.rs index 619d4ab67b2f..a34c7f2b3a66 100644 --- a/src/tools/miri/genmc-sys/src/lib.rs +++ b/src/tools/miri/genmc-sys/src/lib.rs @@ -254,6 +254,17 @@ mod ffi { is_coherence_order_maximal_write: bool, } + #[must_use] + #[derive(Debug)] + struct MutexLockResult { + /// If there was an error, it will be stored in `error`, otherwise it is `None`. + error: UniquePtr, + /// If true, GenMC determined that we should retry the mutex lock operation once the thread attempting to lock is scheduled again. + is_reset: bool, + /// Indicate whether the lock was acquired by this thread. + is_lock_acquired: bool, + } + /**** These are GenMC types that we have to copy-paste here since cxx does not support "importing" externally defined C++ types. ****/ @@ -305,6 +316,13 @@ mod ffi { UMin = 10, } + #[derive(Debug)] + enum AssumeType { + User = 0, + Barrier = 1, + Spinloop = 2, + } + // # Safety // // This block is unsafe to allow defining safe methods inside. @@ -323,6 +341,7 @@ mod ffi { (This tells cxx that the enums defined above are already defined on the C++ side; it will emit assertions to ensure that the two definitions agree.) ****/ type ActionKind; + type AssumeType; type MemOrdering; type RMWBinOp; type SchedulePolicy; @@ -430,7 +449,31 @@ mod ffi { /// Inform GenMC that the thread should be blocked. /// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user supplied assume statements. /// This can become a parameter once more types of assumes are added. - fn handle_assume_block(self: Pin<&mut MiriGenmcShim>, thread_id: i32); + fn handle_assume_block( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + assume_type: AssumeType, + ); + + /**** Mutex handling ****/ + fn handle_mutex_lock( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + address: u64, + size: u64, + ) -> MutexLockResult; + fn handle_mutex_try_lock( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + address: u64, + size: u64, + ) -> MutexLockResult; + fn handle_mutex_unlock( + self: Pin<&mut MiriGenmcShim>, + thread_id: i32, + address: u64, + size: u64, + ) -> StoreResult; /***** Exploration related functionality *****/ diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs index ef5b24d5abad..b9e09e34dc36 100644 --- a/src/tools/miri/src/concurrency/genmc/dummy.rs +++ b/src/tools/miri/src/concurrency/genmc/dummy.rs @@ -43,6 +43,15 @@ mod intercept { impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn genmc_intercept_function( + &mut self, + _instance: rustc_middle::ty::Instance<'tcx>, + _args: &[rustc_const_eval::interpret::FnArg<'tcx, crate::Provenance>], + _dest: &crate::PlaceTy<'tcx>, + ) -> InterpResult<'tcx, bool> { + unreachable!() + } + fn handle_genmc_verifier_assume(&mut self, _condition: &OpTy<'tcx>) -> InterpResult<'tcx> { unreachable!(); } diff --git a/src/tools/miri/src/concurrency/genmc/intercept.rs b/src/tools/miri/src/concurrency/genmc/intercept.rs deleted file mode 100644 index 4867b1dc21af..000000000000 --- a/src/tools/miri/src/concurrency/genmc/intercept.rs +++ /dev/null @@ -1,38 +0,0 @@ -use tracing::debug; - -use crate::concurrency::thread::EvalContextExt as _; -use crate::{ - BlockReason, InterpResult, MachineCallback, MiriInterpCx, OpTy, UnblockKind, VisitProvenance, - VisitWith, callback, interp_ok, -}; - -// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`. - -/// Other functionality not directly related to event handling -impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} -pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - /// Handle an `assume` statement. This will tell GenMC to block the current thread if the `condition` is false. - /// Returns `true` if the current thread should be blocked in Miri too. - fn handle_genmc_verifier_assume(&mut self, condition: &OpTy<'tcx>) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - let condition_bool = this.read_scalar(condition)?.to_bool()?; - debug!("GenMC: handle_genmc_verifier_assume, condition: {condition:?} = {condition_bool}"); - if condition_bool { - return interp_ok(()); - } - let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); - genmc_ctx.handle_assume_block(&this.machine)?; - this.block_thread( - BlockReason::Genmc, - None, - callback!( - @capture<'tcx> {} - |_this, unblock: UnblockKind| { - assert_eq!(unblock, UnblockKind::Ready); - unreachable!("GenMC should never unblock a thread blocked by an `assume`."); - } - ), - ); - interp_ok(()) - } -} diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs index caec5e921cdf..bc475c680b5b 100644 --- a/src/tools/miri/src/concurrency/genmc/mod.rs +++ b/src/tools/miri/src/concurrency/genmc/mod.rs @@ -27,16 +27,16 @@ use crate::*; mod config; mod global_allocations; mod helper; -mod intercept; mod run; pub(crate) mod scheduling; +mod shims; mod thread_id_map; pub use genmc_sys::GenmcParams; pub use self::config::GenmcConfig; -pub use self::intercept::EvalContextExt as GenmcEvalContextExt; pub use self::run::run_genmc_mode; +pub use self::shims::EvalContextExt as GenmcEvalContextExt; #[derive(Debug)] pub enum ExecutionEndResult { @@ -200,6 +200,14 @@ impl GenmcCtx { fn get_alloc_data_races(&self) -> bool { self.exec_state.allow_data_races.get() } + + /// Get the GenMC id of the currently active thread. + #[must_use] + fn active_thread_genmc_tid<'tcx>(&self, machine: &MiriMachine<'tcx>) -> i32 { + let thread_infos = self.exec_state.thread_id_manager.borrow(); + let curr_thread = machine.threads.active_thread(); + thread_infos.get_genmc_tid(curr_thread) + } } /// GenMC event handling. These methods are used to inform GenMC about events happening in the program, and to handle scheduling decisions. @@ -309,12 +317,10 @@ impl GenmcCtx { ordering: AtomicFenceOrd, ) -> InterpResult<'tcx> { assert!(!self.get_alloc_data_races(), "atomic fence with data race checking disabled."); - - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread); - - self.handle.borrow_mut().pin_mut().handle_fence(genmc_tid, ordering.to_genmc()); + self.handle + .borrow_mut() + .pin_mut() + .handle_fence(self.active_thread_genmc_tid(machine), ordering.to_genmc()); interp_ok(()) } @@ -425,12 +431,8 @@ impl GenmcCtx { debug!( "GenMC: atomic_compare_exchange, address: {address:?}, size: {size:?} (expect: {expected_old_value:?}, new: {new_value:?}, old_value: {old_value:?}, {success:?}, orderings: {fail:?}), can fail spuriously: {can_fail_spuriously}" ); - - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let genmc_tid = thread_infos.get_genmc_tid(ecx.machine.threads.active_thread()); - let cas_result = self.handle.borrow_mut().pin_mut().handle_compare_exchange( - genmc_tid, + self.active_thread_genmc_tid(&ecx.machine), address.bytes(), size.bytes(), scalar_to_genmc_scalar(ecx, self, expected_old_value)?, @@ -591,14 +593,10 @@ impl GenmcCtx { return ecx .get_global_allocation_address(&self.global_state.global_allocations, alloc_id); } - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread); // GenMC doesn't support ZSTs, so we set the minimum size to 1 byte let genmc_size = size.bytes().max(1); - let chosen_address = self.handle.borrow_mut().pin_mut().handle_malloc( - genmc_tid, + self.active_thread_genmc_tid(machine), genmc_size, alignment.bytes(), ); @@ -632,11 +630,12 @@ impl GenmcCtx { !self.get_alloc_data_races(), "memory deallocation with data race checking disabled." ); - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread); - - if self.handle.borrow_mut().pin_mut().handle_free(genmc_tid, address.bytes()) { + if self + .handle + .borrow_mut() + .pin_mut() + .handle_free(self.active_thread_genmc_tid(machine), address.bytes()) + { // FIXME(genmc): improve error handling. // An error was detected, so we get the error string from GenMC. throw_ub_format!("{}", self.try_get_error().unwrap()); @@ -690,7 +689,7 @@ impl GenmcCtx { let genmc_tid = thread_infos.get_genmc_tid(curr_thread_id); debug!("GenMC: thread {curr_thread_id:?} ({genmc_tid:?}) finished."); - // NOTE: Miri doesn't support return values for threads, but GenMC expects one, so we return 0 + // NOTE: Miri doesn't support return values for threads, but GenMC expects one, so we return 0. self.handle.borrow_mut().pin_mut().handle_thread_finish(genmc_tid, /* ret_val */ 0); } @@ -752,17 +751,12 @@ impl GenmcCtx { "GenMC mode currently does not support atomics larger than {MAX_ACCESS_SIZE} bytes.", ); } - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread_id = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread_id); - debug!( - "GenMC: load, thread: {curr_thread_id:?} ({genmc_tid:?}), address: {addr} == {addr:#x}, size: {size:?}, ordering: {memory_ordering:?}, old_value: {genmc_old_value:x?}", + "GenMC: load, address: {addr} == {addr:#x}, size: {size:?}, ordering: {memory_ordering:?}, old_value: {genmc_old_value:x?}", addr = address.bytes() ); - let load_result = self.handle.borrow_mut().pin_mut().handle_load( - genmc_tid, + self.active_thread_genmc_tid(machine), address.bytes(), size.bytes(), memory_ordering, @@ -803,17 +797,12 @@ impl GenmcCtx { "GenMC mode currently does not support atomics larger than {MAX_ACCESS_SIZE} bytes." ); } - let thread_infos = self.exec_state.thread_id_manager.borrow(); - let curr_thread_id = machine.threads.active_thread(); - let genmc_tid = thread_infos.get_genmc_tid(curr_thread_id); - debug!( - "GenMC: store, thread: {curr_thread_id:?} ({genmc_tid:?}), address: {addr} = {addr:#x}, size: {size:?}, ordering {memory_ordering:?}, value: {genmc_value:?}", + "GenMC: store, address: {addr} = {addr:#x}, size: {size:?}, ordering {memory_ordering:?}, value: {genmc_value:?}", addr = address.bytes() ); - let store_result = self.handle.borrow_mut().pin_mut().handle_store( - genmc_tid, + self.active_thread_genmc_tid(machine), address.bytes(), size.bytes(), genmc_value, @@ -854,14 +843,11 @@ impl GenmcCtx { MAX_ACCESS_SIZE, size.bytes() ); - - let curr_thread_id = ecx.machine.threads.active_thread(); - let genmc_tid = self.exec_state.thread_id_manager.borrow().get_genmc_tid(curr_thread_id); debug!( - "GenMC: atomic_rmw_op, thread: {curr_thread_id:?} ({genmc_tid:?}) (op: {genmc_rmw_op:?}, rhs value: {genmc_rhs_scalar:?}), address: {address:?}, size: {size:?}, ordering: {ordering:?}", + "GenMC: atomic_rmw_op (op: {genmc_rmw_op:?}, rhs value: {genmc_rhs_scalar:?}), address: {address:?}, size: {size:?}, ordering: {ordering:?}", ); let rmw_result = self.handle.borrow_mut().pin_mut().handle_read_modify_write( - genmc_tid, + self.active_thread_genmc_tid(&ecx.machine), address.bytes(), size.bytes(), genmc_rmw_op, @@ -884,20 +870,6 @@ impl GenmcCtx { }; interp_ok((old_value_scalar, new_value_scalar)) } - - /**** Blocking functionality ****/ - - /// Handle a user thread getting blocked. - /// This may happen due to an manual `assume` statement added by a user - /// or added by some automated program transformation, e.g., for spinloops. - fn handle_assume_block<'tcx>(&self, machine: &MiriMachine<'tcx>) -> InterpResult<'tcx> { - let curr_thread = machine.threads.active_thread(); - let genmc_curr_thread = - self.exec_state.thread_id_manager.borrow().get_genmc_tid(curr_thread); - debug!("GenMC: assume statement, blocking thread {curr_thread:?} ({genmc_curr_thread:?})"); - self.handle.borrow_mut().pin_mut().handle_assume_block(genmc_curr_thread); - interp_ok(()) - } } impl VisitProvenance for GenmcCtx { diff --git a/src/tools/miri/src/concurrency/genmc/scheduling.rs b/src/tools/miri/src/concurrency/genmc/scheduling.rs index be7df8682f07..6ccbaf4f2482 100644 --- a/src/tools/miri/src/concurrency/genmc/scheduling.rs +++ b/src/tools/miri/src/concurrency/genmc/scheduling.rs @@ -63,15 +63,25 @@ fn get_function_kind<'tcx>( ) -> InterpResult<'tcx, NextInstrKind> { use NextInstrKind::*; let callee_def_id = match func_ty.kind() { - ty::FnDef(def_id, _args) => def_id, + ty::FnDef(def_id, _args) => *def_id, _ => return interp_ok(MaybeAtomic(ActionKind::Load)), // we don't know the callee, might be pthread_join }; let Some(intrinsic_def) = ecx.tcx.intrinsic(callee_def_id) else { - if ecx.tcx.is_foreign_item(*callee_def_id) { + if ecx.tcx.is_foreign_item(callee_def_id) { // Some shims, like pthread_join, must be considered loads. So just consider them all loads, // these calls are not *that* common. return interp_ok(MaybeAtomic(ActionKind::Load)); } + // NOTE: Functions intercepted by Miri in `concurrency/genmc/intercep.rs` must also be added here. + // Such intercepted functions, like `sys::Mutex::lock`, should be treated as atomics to ensure we call the scheduler when we encounter one of them. + // These functions must also be classified whether they may have load semantics. + if ecx.tcx.is_diagnostic_item(rustc_span::sym::sys_mutex_lock, callee_def_id) + || ecx.tcx.is_diagnostic_item(rustc_span::sym::sys_mutex_try_lock, callee_def_id) + { + return interp_ok(MaybeAtomic(ActionKind::Load)); + } else if ecx.tcx.is_diagnostic_item(rustc_span::sym::sys_mutex_unlock, callee_def_id) { + return interp_ok(MaybeAtomic(ActionKind::NonLoad)); + } // The next step is a call to a regular Rust function. return interp_ok(NonAtomic); }; diff --git a/src/tools/miri/src/concurrency/genmc/shims.rs b/src/tools/miri/src/concurrency/genmc/shims.rs new file mode 100644 index 000000000000..4685dfd1b8dd --- /dev/null +++ b/src/tools/miri/src/concurrency/genmc/shims.rs @@ -0,0 +1,234 @@ +use genmc_sys::AssumeType; +use rustc_middle::ty; +use tracing::debug; + +use crate::concurrency::genmc::MAX_ACCESS_SIZE; +use crate::concurrency::thread::EvalContextExt as _; +use crate::*; + +impl GenmcCtx { + /// Handle a user thread getting blocked. + /// This may happen due to an manual `assume` statement added by a user + /// or added by some automated program transformation, e.g., for spinloops. + fn handle_assume_block<'tcx>( + &self, + machine: &MiriMachine<'tcx>, + assume_type: AssumeType, + ) -> InterpResult<'tcx> { + debug!("GenMC: assume statement, blocking active thread."); + self.handle + .borrow_mut() + .pin_mut() + .handle_assume_block(self.active_thread_genmc_tid(machine), assume_type); + interp_ok(()) + } +} + +// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`. + +impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} +trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Small helper to get the arguments of an intercepted function call. + fn get_fn_args( + &self, + instance: ty::Instance<'tcx>, + args: &[FnArg<'tcx>], + ) -> InterpResult<'tcx, [OpTy<'tcx>; N]> { + let this = self.eval_context_ref(); + let args = this.copy_fn_args(args); // FIXME: Should `InPlace` arguments be reset to uninit? + if let Ok(ops) = args.try_into() { + return interp_ok(ops); + } + panic!("{} is a diagnostic item expected to have {} arguments", instance, N); + } + + /**** Blocking functionality ****/ + + /// Handle a thread getting blocked by a user assume (not an automatically generated assume). + /// Unblocking this thread in the current execution will cause a panic. + /// Miri does not provide GenMC with the annotations to determine when to unblock the thread, so it should never be unblocked. + fn handle_user_assume_block(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + debug!( + "GenMC: block thread {:?} due to failing assume statement.", + this.machine.threads.active_thread() + ); + assert!(this.machine.threads.active_thread_ref().is_enabled()); + // Block the thread on the GenMC side. + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + genmc_ctx.handle_assume_block(&this.machine, AssumeType::User)?; + // Block the thread on the Miri side. + this.block_thread( + BlockReason::Genmc, + None, + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::Ready); + unreachable!("GenMC should never unblock a thread blocked by an `assume`."); + } + ), + ); + interp_ok(()) + } + + fn intercept_mutex_lock(&mut self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> { + debug!("GenMC: handling Mutex::lock()"); + let this = self.eval_context_mut(); + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + + let size = mutex.layout.size.bytes(); + assert!( + size <= MAX_ACCESS_SIZE, + "Mutex is larger than maximal size of a memory access supported by GenMC ({size} > {MAX_ACCESS_SIZE})" + ); + let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_lock( + genmc_ctx.active_thread_genmc_tid(&this.machine), + mutex.ptr().addr().bytes(), + size, + ); + if let Some(error) = result.error.as_ref() { + // FIXME(genmc): improve error handling. + throw_ub_format!("{}", error.to_string_lossy()); + } + if result.is_reset { + debug!("GenMC: Mutex::lock: Reset"); + // GenMC informed us to reset and try the lock again later. + // We block the current thread until GenMC schedules it again. + this.block_thread( + crate::BlockReason::Genmc, + None, + crate::callback!( + @capture<'tcx> { + mutex: MPlaceTy<'tcx>, + } + |this, unblock: crate::UnblockKind| { + debug!("GenMC: Mutex::lock: unblocking callback called, attempting to lock the Mutex again."); + assert_eq!(unblock, crate::UnblockKind::Ready); + this.intercept_mutex_lock(mutex)?; + interp_ok(()) + } + ), + ); + } else if result.is_lock_acquired { + debug!("GenMC: Mutex::lock successfully acquired the Mutex."); + } else { + debug!("GenMC: Mutex::lock failed to acquire the Mutex, permanently blocking thread."); + // NOTE: `handle_mutex_lock` already blocked the current thread on the GenMC side. + this.block_thread( + crate::BlockReason::Genmc, + None, + crate::callback!( + @capture<'tcx> { + mutex: MPlaceTy<'tcx>, + } + |_this, _unblock: crate::UnblockKind| { + unreachable!("A thread blocked on `Mutex::lock` should not be unblocked again."); + } + ), + ); + } + // NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC. + interp_ok(()) + } + + fn intercept_mutex_try_lock( + &mut self, + mutex: MPlaceTy<'tcx>, + dest: &crate::PlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + debug!("GenMC: handling Mutex::try_lock()"); + let this = self.eval_context_mut(); + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + let size = mutex.layout.size.bytes(); + assert!( + size <= MAX_ACCESS_SIZE, + "Mutex is larger than maximal size of a memory access supported by GenMC ({size} > {MAX_ACCESS_SIZE})" + ); + let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_try_lock( + genmc_ctx.active_thread_genmc_tid(&this.machine), + mutex.ptr().addr().bytes(), + size, + ); + if let Some(error) = result.error.as_ref() { + // FIXME(genmc): improve error handling. + throw_ub_format!("{}", error.to_string_lossy()); + } + debug!( + "GenMC: Mutex::try_lock(): is_reset: {}, is_lock_acquired: {}", + result.is_reset, result.is_lock_acquired + ); + assert!(!result.is_reset, "GenMC returned 'reset' for a mutex try_lock."); + // Write the return value of try_lock, i.e., whether we acquired the mutex. + this.write_scalar(Scalar::from_bool(result.is_lock_acquired), dest)?; + // NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC. + interp_ok(()) + } + + fn intercept_mutex_unlock(&self, mutex: MPlaceTy<'tcx>) -> InterpResult<'tcx> { + debug!("GenMC: handling Mutex::unlock()"); + let this = self.eval_context_ref(); + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + let result = genmc_ctx.handle.borrow_mut().pin_mut().handle_mutex_unlock( + genmc_ctx.active_thread_genmc_tid(&this.machine), + mutex.ptr().addr().bytes(), + mutex.layout.size.bytes(), + ); + if let Some(error) = result.error.as_ref() { + // FIXME(genmc): improve error handling. + throw_ub_format!("{}", error.to_string_lossy()); + } + // NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.} + interp_ok(()) + } +} + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Given a `ty::Instance<'tcx>`, do any required special handling. + /// Returns true if this `instance` should be skipped (i.e., no MIR should be executed for it). + fn genmc_intercept_function( + &mut self, + instance: rustc_middle::ty::Instance<'tcx>, + args: &[rustc_const_eval::interpret::FnArg<'tcx, crate::Provenance>], + dest: &crate::PlaceTy<'tcx>, + ) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + assert!( + this.machine.data_race.as_genmc_ref().is_some(), + "This function should only be called in GenMC mode." + ); + + // NOTE: When adding new intercepted functions here, they must also be added to `fn get_function_kind` in `concurrency/genmc/scheduling.rs`. + use rustc_span::sym; + if this.tcx.is_diagnostic_item(sym::sys_mutex_lock, instance.def_id()) { + let [mutex] = this.get_fn_args(instance, args)?; + let mutex = this.deref_pointer(&mutex)?; + this.intercept_mutex_lock(mutex)?; + } else if this.tcx.is_diagnostic_item(sym::sys_mutex_try_lock, instance.def_id()) { + let [mutex] = this.get_fn_args(instance, args)?; + let mutex = this.deref_pointer(&mutex)?; + this.intercept_mutex_try_lock(mutex, dest)?; + } else if this.tcx.is_diagnostic_item(sym::sys_mutex_unlock, instance.def_id()) { + let [mutex] = this.get_fn_args(instance, args)?; + let mutex = this.deref_pointer(&mutex)?; + this.intercept_mutex_unlock(mutex)?; + } else { + // Nothing to intercept. + return interp_ok(false); + } + interp_ok(true) + } + + /// Handle an `assume` statement. This will tell GenMC to block the current thread if the `condition` is false. + /// Returns `true` if the current thread should be blocked in Miri too. + fn handle_genmc_verifier_assume(&mut self, condition: &OpTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let condition_bool = this.read_scalar(condition)?.to_bool()?; + debug!("GenMC: handle_genmc_verifier_assume, condition: {condition:?} = {condition_bool}"); + if condition_bool { + return interp_ok(()); + } + this.handle_user_assume_block() + } +} diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 0d9dbe052e39..13492c99294e 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -708,21 +708,31 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); // In GenMC mode, we let GenMC do the scheduling. - if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() { - let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else { - return interp_ok(SchedulingAction::ExecuteStep); - }; - // If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again. - if this.machine.threads.threads[next_thread_id].state.is_blocked_on(BlockReason::Genmc) - { - info!("GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now."); - this.unblock_thread(next_thread_id, BlockReason::Genmc)?; + if this.machine.data_race.as_genmc_ref().is_some() { + loop { + let genmc_ctx = this.machine.data_race.as_genmc_ref().unwrap(); + let Some(next_thread_id) = genmc_ctx.schedule_thread(this)? else { + return interp_ok(SchedulingAction::ExecuteStep); + }; + // If a thread is blocked on GenMC, we have to implicitly unblock it when it gets scheduled again. + if this.machine.threads.threads[next_thread_id] + .state + .is_blocked_on(BlockReason::Genmc) + { + info!( + "GenMC: scheduling blocked thread {next_thread_id:?}, so we unblock it now." + ); + this.unblock_thread(next_thread_id, BlockReason::Genmc)?; + } + // The thread we just unblocked may have been blocked again during the unblocking callback. + // In that case, we need to ask for a different thread to run next. + let thread_manager = &mut this.machine.threads; + if thread_manager.threads[next_thread_id].state.is_enabled() { + // Set the new active thread. + thread_manager.active_thread = next_thread_id; + return interp_ok(SchedulingAction::ExecuteStep); + } } - // Set the new active thread. - let thread_manager = &mut this.machine.threads; - thread_manager.active_thread = next_thread_id; - assert!(thread_manager.threads[thread_manager.active_thread].state.is_enabled()); - return interp_ok(SchedulingAction::ExecuteStep); } // We are not in GenMC mode, so we control the scheduling. diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index ce72fc6e297d..07af4dcaad11 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -110,6 +110,7 @@ pub type StrictPointer = interpret::Pointer; pub type Scalar = interpret::Scalar; pub type ImmTy<'tcx> = interpret::ImmTy<'tcx, machine::Provenance>; pub type OpTy<'tcx> = interpret::OpTy<'tcx, machine::Provenance>; +pub type FnArg<'tcx> = interpret::FnArg<'tcx, machine::Provenance>; pub type PlaceTy<'tcx> = interpret::PlaceTy<'tcx, machine::Provenance>; pub type MPlaceTy<'tcx> = interpret::MPlaceTy<'tcx, machine::Provenance>; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 7b93d0194119..49c235166536 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -31,7 +31,9 @@ use rustc_target::callconv::FnAbi; use crate::alloc_addresses::EvalContextExt; use crate::concurrency::cpu_affinity::{self, CpuAffinityMask}; use crate::concurrency::data_race::{self, NaReadType, NaWriteType}; -use crate::concurrency::{AllocDataRaceHandler, GenmcCtx, GlobalDataRaceHandler, weak_memory}; +use crate::concurrency::{ + AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory, +}; use crate::*; /// First real-time signal. @@ -1163,7 +1165,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &mut MiriInterpCx<'tcx>, instance: ty::Instance<'tcx>, abi: &FnAbi<'tcx, Ty<'tcx>>, - args: &[FnArg<'tcx, Provenance>], + args: &[FnArg<'tcx>], dest: &PlaceTy<'tcx>, ret: Option, unwind: mir::UnwindAction, @@ -1182,6 +1184,13 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { return ecx.emulate_foreign_item(link_name, abi, &args, dest, ret, unwind); } + if ecx.machine.data_race.as_genmc_ref().is_some() + && ecx.genmc_intercept_function(instance, args, dest)? + { + ecx.return_to_block(ret)?; + return interp_ok(None); + } + // Otherwise, load the MIR. let _trace = enter_trace_span!("load_mir"); interp_ok(Some((ecx.load_mir(instance.def, None)?, instance))) @@ -1192,7 +1201,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &mut MiriInterpCx<'tcx>, fn_val: DynSym, abi: &FnAbi<'tcx, Ty<'tcx>>, - args: &[FnArg<'tcx, Provenance>], + args: &[FnArg<'tcx>], dest: &PlaceTy<'tcx>, ret: Option, unwind: mir::UnwindAction, diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs new file mode 100644 index 000000000000..d2da722f1c02 --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.rs @@ -0,0 +1,28 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows +//@error-in-other-file: Undefined Behavior + +// Test that GenMC throws an error if a `std::sync::Mutex` is unlocked from a different thread than the one that locked it. +// +// This test will cause an error on all targets, even mutexes on that targets allow for unlocking on a different thread. +// GenMC always assumes a `pthread`-like API. + +#![no_main] + +use std::sync::Mutex; + +static MUTEX: Mutex = Mutex::new(0); + +#[derive(Copy, Clone)] +struct EvilSend(pub T); +unsafe impl Send for EvilSend {} + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + let guard = EvilSend(MUTEX.lock().unwrap()); + let handle = std::thread::spawn(move || { + let guard = guard; // avoid field capturing + drop(guard); + }); + handle.join().unwrap(); + 0 +} diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr new file mode 100644 index 000000000000..e74b76ea415e --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_diff_thread_unlock.stderr @@ -0,0 +1,86 @@ +Running GenMC Verification... +warning: GenMC currently does not model the failure ordering for `compare_exchange`. Due to success ordering 'Acquire', the failure ordering 'Relaxed' is treated like 'Acquire'. Miri with GenMC might miss bugs related to this memory access. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `miri_start` + --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +LL | | let guard = guard; // avoid field capturing +LL | | drop(guard); +LL | | }); + | |______^ + +warning: GenMC currently does not model spurious failures of `compare_exchange_weak`. Miri with GenMC might miss bugs related to spurious failures. + --> RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + | +LL | || self + | ________________^ +LL | | .state +LL | | .compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed) + | |____________________________________________________________________________________^ GenMC might miss possible behaviors of this code + | + = note: BACKTRACE: + = note: inside `std::sys::sync::PLATFORM::futex::RwLock::read` at RUSTLIB/std/src/sys/sync/PLATFORM/futex.rs:LL:CC + = note: inside `std::sync::RwLock::<()>::read` at RUSTLIB/std/src/sync/poison/rwlock.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::env_read_lock` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr_stack::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::pal::PLATFORM::small_c_string::run_with_cstr::>` at RUSTLIB/std/src/sys/pal/PLATFORM/small_c_string.rs:LL:CC + = note: inside `std::sys::env::PLATFORM::getenv` at RUSTLIB/std/src/sys/env/PLATFORM.rs:LL:CC + = note: inside `std::env::_var_os` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside `std::env::var_os::<&str>` at RUSTLIB/std/src/env.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/thread/mod.rs:LL:CC +note: inside `miri_start` + --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC + | +LL | let handle = std::thread::spawn(move || { + | __________________^ +LL | | let guard = guard; // avoid field capturing +LL | | drop(guard); +LL | | }); + | |______^ + +error: Undefined Behavior: Invalid unlock() operation + --> RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + | +LL | self.lock.inner.unlock(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE on thread `unnamed-ID`: + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::sync::MutexGuard<'_, u64>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::ptr::drop_in_place::>> - shim(Some(EvilSend>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside closure + --> tests/genmc/fail/shims/mutex_diff_thread_unlock.rs:LL:CC + | +LL | drop(guard); + | ^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error; 2 warnings emitted + diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs new file mode 100644 index 000000000000..3daff38efbfd --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows +//@error-in-other-file: Undefined Behavior + +// Test that GenMC can detect a double unlock of a mutex. +// This test will cause an error even if the program actually would work entirely fine despite the double-unlock +// because GenMC always assumes a `pthread`-like API. + +#![no_main] + +use std::sync::Mutex; + +static MUTEX: Mutex = Mutex::new(0); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + let mut guard = MUTEX.lock().unwrap(); + unsafe { + std::ptr::drop_in_place(&raw mut guard); + } + drop(guard); + 0 +} diff --git a/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr new file mode 100644 index 000000000000..3ba863668f1e --- /dev/null +++ b/src/tools/miri/tests/genmc/fail/shims/mutex_double_unlock.stderr @@ -0,0 +1,23 @@ +Running GenMC Verification... +error: Undefined Behavior: Invalid unlock() operation + --> RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + | +LL | self.lock.inner.unlock(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/std/src/sync/poison/mutex.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::sync::MutexGuard<'_, u64>))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside `miri_start` + --> tests/genmc/fail/shims/mutex_double_unlock.rs:LL:CC + | +LL | drop(guard); + | ^^^^^^^^^^^ + +note: add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs new file mode 100644 index 000000000000..df47fbfbc167 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.rs @@ -0,0 +1,42 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// Test that we can detect a deadlock involving `std::sync::Mutex` in GenMC mode. +// FIXME(genmc): We cannot detect the deadlock currently. Instead, the deadlocked execution is treated like any other blocked execution. +// This behavior matches GenMC's on an equivalent program, and additional analysis is required to detect such deadlocks. +// This should become a `fail` test once this deadlock can be detected. +// +// FIXME(genmc): use `std::thread` once GenMC mode performance is better and produces fewer warnings for compare_exchange. + +#![no_main] +#![feature(abort_unwind)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::sync::Mutex; + +use crate::genmc::*; + +static X: Mutex = Mutex::new(0); +static Y: Mutex = Mutex::new(0); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + let t0 = spawn_pthread_closure(|| { + let mut x = X.lock().unwrap(); + let mut y = Y.lock().unwrap(); + *x += 1; + *y += 1; + }); + let t1 = spawn_pthread_closure(|| { + let mut y = Y.lock().unwrap(); + let mut x = X.lock().unwrap(); + *x += 1; + *y += 1; + }); + join_pthreads([t0, t1]); + 0 + } +} diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr new file mode 100644 index 000000000000..8b3957d18c01 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_deadlock.stderr @@ -0,0 +1,5 @@ +Running GenMC Verification... +Verification complete with 3 executions. No errors found. +Number of complete executions explored: 2 +Number of blocked executions seen: 1 +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs new file mode 100644 index 000000000000..1f8bc81d85eb --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.rs @@ -0,0 +1,68 @@ +//@compile-flags: -Zmiri-genmc -Zmiri-disable-stacked-borrows -Zmiri-genmc-verbose +//@normalize-stderr-test: "Verification took .*s" -> "Verification took [TIME]s" + +// Test various features of the `std::sync::Mutex` API with GenMC. +// Miri running with GenMC intercepts the Mutex functions `lock`, `try_lock` and `unlock`, instead of running their actual implementation. +// This interception should not break any functionality. +// +// FIXME(genmc): Once GenMC supports mixed size accesses, add stack/heap allocated Mutexes to the test. +// FIXME(genmc): Once the actual implementation of mutexes can be used in GenMC mode and there is a setting to disable Mutex interception: Add test revision without interception. +// +// Miri provides annotations to GenMC for the condition required to unblock a thread blocked on a Mutex lock call. +// This massively reduces the number of blocked executions we need to explore (in this test we require zero blocked execution). +// We use verbose output to check that this test always explores zero blocked executions. + +#![no_main] +#![feature(abort_unwind)] + +#[path = "../../../utils/genmc.rs"] +mod genmc; + +use std::sync::Mutex; + +use crate::genmc::*; + +const REPS: u64 = 3; + +static LOCK: Mutex = Mutex::new(0); +static OTHER_LOCK: Mutex = Mutex::new(1234); + +#[unsafe(no_mangle)] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + std::panic::abort_unwind(main_); + 0 +} + +fn main_() { + // Two mutexes should not interfere, holding this guard does not affect the other mutex. + let other_guard = OTHER_LOCK.lock().unwrap(); + + let guard = LOCK.lock().unwrap(); + // Trying to lock should fail if the mutex is already held. + assert!(LOCK.try_lock().is_err()); + // Dropping the guard should unlock the mutex correctly. + drop(guard); + // Trying to lock now should succeed. + assert!(LOCK.try_lock().is_ok()); + + // Spawn multiple threads interacting with the same mutex. + unsafe { + let ids = [ + spawn_pthread_closure(|| { + for _ in 0..REPS { + *LOCK.lock().unwrap() += 2; + } + }), + spawn_pthread_closure(|| { + for _ in 0..REPS { + *LOCK.lock().unwrap() += 4; + } + }), + ]; + join_pthreads(ids); + } + // Due to the Mutex, all increments should be visible in every explored execution. + assert!(*LOCK.lock().unwrap() == REPS * 6); + + drop(other_guard); +} diff --git a/src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr new file mode 100644 index 000000000000..76ddd42addf9 --- /dev/null +++ b/src/tools/miri/tests/genmc/pass/shims/mutex_simple.stderr @@ -0,0 +1,3 @@ +Running GenMC Verification... +Verification complete with 20 executions. No errors found. +Verification took [TIME]s. diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded123.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded123.stderr similarity index 100% rename from src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded123.stderr rename to src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded123.stderr diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded321.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded321.stderr similarity index 100% rename from src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.bounded321.stderr rename to src/tools/miri/tests/genmc/pass/shims/spinloop_assume.bounded321.stderr diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced123.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced123.stderr similarity index 100% rename from src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced123.stderr rename to src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced123.stderr diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced321.stderr b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced321.stderr similarity index 100% rename from src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.replaced321.stderr rename to src/tools/miri/tests/genmc/pass/shims/spinloop_assume.replaced321.stderr diff --git a/src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs b/src/tools/miri/tests/genmc/pass/shims/spinloop_assume.rs similarity index 100% rename from src/tools/miri/tests/genmc/pass/intercept/spinloop_assume.rs rename to src/tools/miri/tests/genmc/pass/shims/spinloop_assume.rs From 27afeb0085db8f066636a512241ed8e2003a3fe2 Mon Sep 17 00:00:00 2001 From: vishruth-thimmaiah Date: Wed, 8 Oct 2025 01:59:44 +0530 Subject: [PATCH 20/35] feat: add support for libc::memset Signed-off-by: vishruth-thimmaiah --- src/tools/miri/src/shims/foreign_items.rs | 16 +++++++ .../miri/tests/pass-dep/libc/libc-mem.rs | 42 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 1d086906e7a5..3fd57d3c8db2 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -827,6 +827,22 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.mem_copy(ptr_src, ptr_dest, Size::from_bytes(n), true)?; this.write_pointer(ptr_dest, dest)?; } + "memset" => { + let [ptr_dest, val, n] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let ptr_dest = this.read_pointer(ptr_dest)?; + let val = this.read_scalar(val)?.to_i32()?; + let n = this.read_target_usize(n)?; + // The docs say val is "interpreted as unsigned char". + #[expect(clippy::as_conversions)] + let val = val as u8; + + this.ptr_get_alloc_id(ptr_dest, 0)?; + + let bytes = std::iter::repeat_n(val, n.try_into().unwrap()); + this.write_bytes_ptr(ptr_dest, bytes)?; + this.write_pointer(ptr_dest, dest)?; + } // LLVM intrinsics "llvm.prefetch" => { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs index 727533a9de61..531d637d1f24 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs @@ -78,6 +78,47 @@ fn test_strcpy() { } } +fn test_memset() { + unsafe { + let val = 1; + let dest = libc::calloc(3, 1); + libc::memset(dest, val, 3); + let slc = std::slice::from_raw_parts(dest as *const i8, 3); + assert_eq!(*slc, [1i8, 1, 1]); + libc::free(dest); + } + + unsafe { + let val = 1; + let dest = libc::calloc(4, 1); + libc::memset(dest, val, 3); + let slc = std::slice::from_raw_parts(dest as *const i8, 4); + assert_eq!(*slc, [1i8, 1, 1, 0]); + libc::free(dest); + } + + unsafe { + let val = 1; + let mut dest = 0_i8; + libc::memset(&mut dest as *mut i8 as *mut libc::c_void, val, mem::size_of::()); + assert_eq!(dest, val as i8); + } + + unsafe { + let val = 1; + let mut dest = 0_i16; + libc::memset(&mut dest as *mut i16 as *mut libc::c_void, val, mem::size_of::()); + assert_eq!(dest, 257); + } + + unsafe { + let val = 257; + let mut dest = 0_i16; + libc::memset(&mut dest as *mut i16 as *mut libc::c_void, val, mem::size_of::()); + assert_eq!(dest, 257); + } +} + fn test_malloc() { // Test that small allocations sometimes *are* not very aligned. let saw_unaligned = (0..64).any(|_| unsafe { @@ -310,4 +351,5 @@ fn main() { test_memcpy(); test_strcpy(); + test_memset(); } From 5446a0ae0afa8678afff93fb2fdc7db860fbc102 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 8 Oct 2025 04:52:55 +0000 Subject: [PATCH 21/35] Prepare for merging from rust-lang/rust This updates the rust-version file to 4fd31815524baba0bf368f151f757101f432e3de. --- 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 77436ae2f825..692cbc0a56bc 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -3b8665c5ab3aeced9b01672404c3764583e722ca +4fd31815524baba0bf368f151f757101f432e3de From 8066cbc07a469a25b927b7206a6405c885e414b7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 8 Oct 2025 11:31:28 +0200 Subject: [PATCH 22/35] readme: document how to directly invoke the driver --- src/tools/miri/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index a5214e213b3c..d47967c0a4d3 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -246,6 +246,21 @@ such races. Note: `cargo-nextest` does not support doctests, see https://github.com/nextest-rs/nextest/issues/16 +### Directly invoking the `miri` driver + +The recommended way to invoke Miri is via `cargo miri`. Directly invoking the underlying `miri` +driver is not supported, which is why that binary is not even installed into the PATH. However, if +you need to run Miri on many small tests and want to invoke it directly like you would invoke +`rustc`, that is still possible with a bit of extra effort: + +```sh +# one-time setup +cargo +nightly miri setup +SYSROOT=$(cargo +nightly miri setup --print-sysroot) +# per file +~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/miri --sysroot "$SYSROOT" file.rs +``` + ### Common Problems When using the above instructions, you may encounter a number of confusing compiler From 444eb4cc12ad94935ba8047c3e79da789b9e7d94 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 8 Oct 2025 03:03:55 -0700 Subject: [PATCH 23/35] assert that non-extended temporaries have scopes --- compiler/rustc_hir_analysis/src/check/region.rs | 11 ++++++----- compiler/rustc_middle/src/middle/region.rs | 13 ++++++------- compiler/rustc_middle/src/ty/rvalue_scopes.rs | 10 ++++------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 43e6f5fe1047..f99fefcf56ac 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -498,7 +498,8 @@ fn resolve_local<'tcx>( // Iterate up to the enclosing destruction scope to find the same scope that will also // be used for the result of the block itself. if let Some(inner_scope) = visitor.cx.var_parent { - (visitor.cx.var_parent, _) = visitor.scope_tree.default_temporary_scope(inner_scope) + visitor.cx.var_parent = + Some(visitor.scope_tree.default_temporary_scope(inner_scope).0) } // Don't lifetime-extend child `super let`s or block tail expressions' temporaries in // the initializer when this `super let` is not itself extended by a parent `let` @@ -752,10 +753,10 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { // The body of the every fn is a root scope. resolve_expr(this, body.value, true); } else { - // Only functions have an outer terminating (drop) scope, while - // temporaries in constant initializers may be 'static, but only - // according to rvalue lifetime semantics, using the same - // syntactical rules used for let initializers. + // All bodies have an outer temporary drop scope, but temporaries + // and `super let` bindings in constant initializers may be extended + // to have 'static lifetimes, using the same syntactical rules used + // for `let` initializers. // // e.g., in `let x = &f();`, the temporary holding the result from // the `f()` call lives for the entirety of the surrounding block. diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 5367e5edd496..3ed8d9a36e10 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -16,7 +16,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_span::{DUMMY_SP, Span}; use tracing::debug; -use crate::ty::TyCtxt; +use crate::ty::{self, TyCtxt}; /// Represents a statically-describable scope that can be used to /// bound the lifetime/region for values. @@ -302,8 +302,8 @@ impl ScopeTree { /// Returns the scope of non-lifetime-extended temporaries within a given scope, as well as /// whether we've recorded a potential backwards-incompatible change to lint on. - /// Returns `None` when no enclosing temporary scope is found, such as for static items. - pub fn default_temporary_scope(&self, inner: Scope) -> (Option, Option) { + /// Panics if no enclosing temporary scope is found. + pub fn default_temporary_scope(&self, inner: Scope) -> (Scope, Option) { let mut id = inner; let mut backwards_incompatible = None; @@ -311,11 +311,11 @@ impl ScopeTree { match p.data { ScopeData::Destruction => { debug!("temporary_scope({inner:?}) = {id:?} [enclosing]"); - return (Some(id), backwards_incompatible); + return (id, backwards_incompatible); } ScopeData::IfThenRescope | ScopeData::MatchGuard => { debug!("temporary_scope({inner:?}) = {p:?} [enclosing]"); - return (Some(p), backwards_incompatible); + return (p, backwards_incompatible); } ScopeData::Node | ScopeData::CallSite @@ -335,7 +335,6 @@ impl ScopeTree { } } - debug!("temporary_scope({inner:?}) = None"); - (None, backwards_incompatible) + span_bug!(ty::tls::with(|tcx| inner.span(tcx, self)), "no enclosing temporary scope") } } diff --git a/compiler/rustc_middle/src/ty/rvalue_scopes.rs b/compiler/rustc_middle/src/ty/rvalue_scopes.rs index 8b92e48ed1a0..df4e29d45754 100644 --- a/compiler/rustc_middle/src/ty/rvalue_scopes.rs +++ b/compiler/rustc_middle/src/ty/rvalue_scopes.rs @@ -31,12 +31,10 @@ impl RvalueScopes { return (s, None); } - // Otherwise, locate the innermost terminating scope - // if there's one. Static items, for instance, won't - // have an enclosing scope, hence no scope will be - // returned. - region_scope_tree - .default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node }) + // Otherwise, locate the innermost terminating scope. + let (scope, backward_incompatible) = region_scope_tree + .default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node }); + (Some(scope), backward_incompatible) } /// Make an association between a sub-expression and an extended lifetime From 513c9b7b86c48cc879b3119d1fcc3174bfa81f95 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 9 Oct 2025 13:43:25 +0000 Subject: [PATCH 24/35] Add debugging instrumentation. --- compiler/rustc_mir_transform/src/coroutine.rs | 32 ++++++++++++++----- .../rustc_mir_transform/src/coroutine/drop.rs | 8 +++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index c136df812a3f..fa72b2e94fd9 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -159,6 +159,7 @@ impl<'tcx> MutVisitor<'tcx> for SelfArgVisitor<'tcx> { } } +#[tracing::instrument(level = "trace", skip(tcx))] fn replace_base<'tcx>(place: &mut Place<'tcx>, new_base: Place<'tcx>, tcx: TyCtxt<'tcx>) { place.local = new_base.local; @@ -166,6 +167,7 @@ fn replace_base<'tcx>(place: &mut Place<'tcx>, new_base: Place<'tcx>, tcx: TyCtx new_projection.append(&mut place.projection.to_vec()); place.projection = tcx.mk_place_elems(&new_projection); + tracing::trace!(?place); } const SELF_ARG: Local = Local::from_u32(1); @@ -270,6 +272,7 @@ impl<'tcx> TransformVisitor<'tcx> { // `core::ops::CoroutineState` only has single element tuple variants, // so we can just write to the downcasted first field and then set the // discriminant to the appropriate variant. + #[tracing::instrument(level = "trace", skip(self, statements))] fn make_state( &self, val: Operand<'tcx>, @@ -348,6 +351,7 @@ impl<'tcx> TransformVisitor<'tcx> { } // Create a Place referencing a coroutine struct field + #[tracing::instrument(level = "trace", skip(self), ret)] fn make_field(&self, variant_index: VariantIdx, idx: FieldIdx, ty: Ty<'tcx>) -> Place<'tcx> { let self_place = Place::from(SELF_ARG); let base = self.tcx.mk_place_downcast_unnamed(self_place, variant_index); @@ -358,6 +362,7 @@ impl<'tcx> TransformVisitor<'tcx> { } // Create a statement which changes the discriminant + #[tracing::instrument(level = "trace", skip(self))] fn set_discr(&self, state_disc: VariantIdx, source_info: SourceInfo) -> Statement<'tcx> { let self_place = Place::from(SELF_ARG); Statement::new( @@ -370,6 +375,7 @@ impl<'tcx> TransformVisitor<'tcx> { } // Create a statement which reads the discriminant into a temporary + #[tracing::instrument(level = "trace", skip(self, body))] fn get_discr(&self, body: &mut Body<'tcx>) -> (Statement<'tcx>, Place<'tcx>) { let temp_decl = LocalDecl::new(self.discr_ty, body.span); let local_decls_len = body.local_decls.push(temp_decl); @@ -389,22 +395,20 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { self.tcx } - fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) { + #[tracing::instrument(level = "trace", skip(self), ret)] + fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _location: Location) { assert!(!self.remap.contains(*local)); } - fn visit_place( - &mut self, - place: &mut Place<'tcx>, - _context: PlaceContext, - _location: Location, - ) { + #[tracing::instrument(level = "trace", skip(self), ret)] + fn visit_place(&mut self, place: &mut Place<'tcx>, _: PlaceContext, _location: Location) { // Replace an Local in the remap with a coroutine struct access if let Some(&Some((ty, variant_index, idx))) = self.remap.get(place.local) { replace_base(place, self.make_field(variant_index, idx, ty), self.tcx); } } + #[tracing::instrument(level = "trace", skip(self, data), ret)] fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) { // Remove StorageLive and StorageDead statements for remapped locals for s in &mut data.statements { @@ -483,6 +487,7 @@ fn make_aggregate_adt<'tcx>( Rvalue::Aggregate(Box::new(AggregateKind::Adt(def_id, variant_idx, args, None, None)), operands) } +#[tracing::instrument(level = "trace", skip(tcx, body))] fn make_coroutine_state_argument_indirect<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let coroutine_ty = body.local_decls.raw[1].ty; @@ -495,6 +500,7 @@ fn make_coroutine_state_argument_indirect<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Bo SelfArgVisitor::new(tcx, ProjectionElem::Deref).visit_body(body); } +#[tracing::instrument(level = "trace", skip(tcx, body))] fn make_coroutine_state_argument_pinned<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let ref_coroutine_ty = body.local_decls.raw[1].ty; @@ -553,6 +559,7 @@ fn replace_local<'tcx>( /// The async lowering step and the type / lifetime inference / checking are /// still using the `ResumeTy` indirection for the time being, and that indirection /// is removed here. After this transform, the coroutine body only knows about `&mut Context<'_>`. +#[tracing::instrument(level = "trace", skip(tcx, body), ret)] fn transform_async_context<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> Ty<'tcx> { let context_mut_ref = Ty::new_task_context(tcx); @@ -606,6 +613,7 @@ fn eliminate_get_context_call<'tcx>(bb_data: &mut BasicBlockData<'tcx>) -> Local } #[cfg_attr(not(debug_assertions), allow(unused))] +#[tracing::instrument(level = "trace", skip(tcx, body), ret)] fn replace_resume_ty_local<'tcx>( tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, @@ -670,6 +678,7 @@ struct LivenessInfo { /// case none exist, the local is considered to be always live. /// - a local has to be stored if it is either directly used after the /// the suspend point, or if it is live and has been previously borrowed. +#[tracing::instrument(level = "trace", skip(tcx, body))] fn locals_live_across_suspend_points<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, @@ -945,6 +954,7 @@ impl StorageConflictVisitor<'_, '_> { } } +#[tracing::instrument(level = "trace", skip(liveness, body))] fn compute_layout<'tcx>( liveness: LivenessInfo, body: &Body<'tcx>, @@ -1049,7 +1059,9 @@ fn compute_layout<'tcx>( variant_source_info, storage_conflicts, }; + debug!(?remap); debug!(?layout); + debug!(?storage_liveness); (remap, layout, storage_liveness) } @@ -1221,6 +1233,7 @@ fn generate_poison_block_and_redirect_unwinds_there<'tcx>( } } +#[tracing::instrument(level = "trace", skip(tcx, transform, body))] fn create_coroutine_resume_function<'tcx>( tcx: TyCtxt<'tcx>, transform: TransformVisitor<'tcx>, @@ -1299,7 +1312,7 @@ fn create_coroutine_resume_function<'tcx>( } /// An operation that can be performed on a coroutine. -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, Debug)] enum Operation { Resume, Drop, @@ -1314,6 +1327,7 @@ impl Operation { } } +#[tracing::instrument(level = "trace", skip(transform, body))] fn create_cases<'tcx>( body: &mut Body<'tcx>, transform: &TransformVisitor<'tcx>, @@ -1445,6 +1459,8 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { // This only applies to coroutines return; }; + tracing::trace!(def_id = ?body.source.def_id()); + let old_ret_ty = body.return_ty(); assert!(body.coroutine_drop().is_none() && body.coroutine_drop_async().is_none()); diff --git a/compiler/rustc_mir_transform/src/coroutine/drop.rs b/compiler/rustc_mir_transform/src/coroutine/drop.rs index fd2d8b2b0563..41c588f0d8d3 100644 --- a/compiler/rustc_mir_transform/src/coroutine/drop.rs +++ b/compiler/rustc_mir_transform/src/coroutine/drop.rs @@ -126,6 +126,7 @@ fn build_pin_fut<'tcx>( // Ready() => ready_block // Pending => yield_block //} +#[tracing::instrument(level = "trace", skip(tcx, body), ret)] fn build_poll_switch<'tcx>( tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, @@ -179,6 +180,7 @@ fn build_poll_switch<'tcx>( } // Gather blocks, reachable through 'drop' targets of Yield and Drop terminators (chained) +#[tracing::instrument(level = "trace", skip(body), ret)] fn gather_dropline_blocks<'tcx>(body: &mut Body<'tcx>) -> DenseBitSet { let mut dropline: DenseBitSet = DenseBitSet::new_empty(body.basic_blocks.len()); for (bb, data) in traversal::reverse_postorder(body) { @@ -249,6 +251,7 @@ pub(super) fn has_expandable_async_drops<'tcx>( } /// Expand Drop terminator for async drops into mainline poll-switch and dropline poll-switch +#[tracing::instrument(level = "trace", skip(tcx, body), ret)] pub(super) fn expand_async_drops<'tcx>( tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, @@ -259,6 +262,7 @@ pub(super) fn expand_async_drops<'tcx>( let dropline = gather_dropline_blocks(body); // Clean drop and async_fut fields if potentially async drop is not expanded (stays sync) let remove_asyncness = |block: &mut BasicBlockData<'tcx>| { + tracing::trace!("remove_asyncness"); if let TerminatorKind::Drop { place: _, target: _, @@ -461,6 +465,7 @@ pub(super) fn expand_async_drops<'tcx>( } } +#[tracing::instrument(level = "trace", skip(tcx, body))] pub(super) fn elaborate_coroutine_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { use crate::elaborate_drop::{Unwind, elaborate_drop}; use crate::patch::MirPatch; @@ -519,6 +524,7 @@ pub(super) fn elaborate_coroutine_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body elaborator.patch.apply(body); } +#[tracing::instrument(level = "trace", skip(tcx, body), ret)] pub(super) fn insert_clean_drop<'tcx>( tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, @@ -550,6 +556,7 @@ pub(super) fn insert_clean_drop<'tcx>( .push(BasicBlockData::new(Some(Terminator { source_info, kind: term }), false)) } +#[tracing::instrument(level = "trace", skip(tcx, transform, body))] pub(super) fn create_coroutine_drop_shim<'tcx>( tcx: TyCtxt<'tcx>, transform: &TransformVisitor<'tcx>, @@ -621,6 +628,7 @@ pub(super) fn create_coroutine_drop_shim<'tcx>( } // Create async drop shim function to drop coroutine itself +#[tracing::instrument(level = "trace", skip(tcx, transform, body))] pub(super) fn create_coroutine_drop_shim_async<'tcx>( tcx: TyCtxt<'tcx>, transform: &TransformVisitor<'tcx>, From 1ad657ed5f2e2a9a965283ae8a096099cd7d0ebc Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 9 Oct 2025 13:50:06 +0000 Subject: [PATCH 25/35] Simplify TransformVisitor. --- compiler/rustc_mir_transform/src/coroutine.rs | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index fa72b2e94fd9..e7f9eeddee8c 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -408,33 +408,39 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { } } + #[tracing::instrument(level = "trace", skip(self, stmt), ret)] + fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, location: Location) { + // Remove StorageLive and StorageDead statements for remapped locals + if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = stmt.kind + && self.remap.contains(l) + { + stmt.make_nop(true); + } + self.super_statement(stmt, location); + } + #[tracing::instrument(level = "trace", skip(self, data), ret)] fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) { - // Remove StorageLive and StorageDead statements for remapped locals - for s in &mut data.statements { - if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = s.kind - && self.remap.contains(l) - { - s.make_nop(true); - } - } - - let ret_val = match data.terminator().kind { + match data.terminator().kind { TerminatorKind::Return => { - Some((true, None, Operand::Move(Place::from(self.old_ret_local)), None)) + let source_info = data.terminator().source_info; + // We must assign the value first in case it gets declared dead below + self.make_state( + Operand::Move(Place::from(self.old_ret_local)), + source_info, + true, + &mut data.statements, + ); + // Return state. + let state = VariantIdx::new(CoroutineArgs::RETURNED); + data.statements.push(self.set_discr(state, source_info)); + data.terminator_mut().kind = TerminatorKind::Return; } - TerminatorKind::Yield { ref value, resume, resume_arg, drop } => { - Some((false, Some((resume, resume_arg)), value.clone(), drop)) - } - _ => None, - }; - - if let Some((is_return, resume, v, drop)) = ret_val { - let source_info = data.terminator().source_info; - // We must assign the value first in case it gets declared dead below - self.make_state(v, source_info, is_return, &mut data.statements); - let state = if let Some((resume, mut resume_arg)) = resume { - // Yield + TerminatorKind::Yield { ref value, resume, mut resume_arg, drop } => { + let source_info = data.terminator().source_info; + // We must assign the value first in case it gets declared dead below + self.make_state(value.clone(), source_info, false, &mut data.statements); + // Yield state. let state = CoroutineArgs::RESERVED_VARIANTS + self.suspension_points.len(); // The resume arg target location might itself be remapped if its base local is @@ -465,13 +471,11 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { storage_liveness, }); - VariantIdx::new(state) - } else { - // Return - VariantIdx::new(CoroutineArgs::RETURNED) // state for returned - }; - data.statements.push(self.set_discr(state, source_info)); - data.terminator_mut().kind = TerminatorKind::Return; + let state = VariantIdx::new(state); + data.statements.push(self.set_discr(state, source_info)); + data.terminator_mut().kind = TerminatorKind::Return; + } + _ => {} } self.super_basic_block_data(block, data); From 6d800ae35b4458461d99b18bd7c20f4c1ec40d06 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 9 Oct 2025 14:12:26 +0000 Subject: [PATCH 26/35] Renumber locals after state transform. --- compiler/rustc_mir_transform/src/coroutine.rs | 72 +++++++++++-------- ...#0}.coroutine_drop_async.0.panic-abort.mir | 34 ++++----- ...0}.coroutine_drop_async.0.panic-unwind.mir | 34 ++++----- 3 files changed, 75 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index e7f9eeddee8c..5af05f221dc9 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -68,7 +68,7 @@ use rustc_hir::lang_items::LangItem; use rustc_hir::{CoroutineDesugaring, CoroutineKind}; use rustc_index::bit_set::{BitMatrix, DenseBitSet, GrowableBitSet}; use rustc_index::{Idx, IndexVec}; -use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; +use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::util::Discr; use rustc_middle::ty::{ @@ -110,6 +110,8 @@ impl<'tcx> MutVisitor<'tcx> for RenameLocalVisitor<'tcx> { fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) { if *local == self.from { *local = self.to; + } else if *local == self.to { + *local = self.from; } } @@ -206,8 +208,8 @@ struct TransformVisitor<'tcx> { // The set of locals that have no `StorageLive`/`StorageDead` annotations. always_live_locals: DenseBitSet, - // The original RETURN_PLACE local - old_ret_local: Local, + // New local we just create to hold the `CoroutineState` value. + new_ret_local: Local, old_yield_ty: Ty<'tcx>, @@ -344,9 +346,10 @@ impl<'tcx> TransformVisitor<'tcx> { } }; + // Assign to `new_ret_local`, which will be replaced by `RETURN_PLACE` later. statements.push(Statement::new( source_info, - StatementKind::Assign(Box::new((Place::return_place(), rvalue))), + StatementKind::Assign(Box::new((self.new_ret_local.into(), rvalue))), )); } @@ -388,6 +391,20 @@ impl<'tcx> TransformVisitor<'tcx> { ); (assign, temp) } + + /// Swaps all references of `old_local` and `new_local`. + #[tracing::instrument(level = "trace", skip(self, body))] + fn replace_local(&mut self, old_local: Local, new_local: Local, body: &mut Body<'tcx>) { + body.local_decls.swap(old_local, new_local); + + let mut visitor = RenameLocalVisitor { from: old_local, to: new_local, tcx: self.tcx }; + visitor.visit_body(body); + for suspension in &mut self.suspension_points { + let ctxt = PlaceContext::MutatingUse(MutatingUseContext::Yield); + let location = Location { block: START_BLOCK, statement_index: 0 }; + visitor.visit_place(&mut suspension.resume_arg, ctxt, location); + } + } } impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { @@ -419,6 +436,16 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { self.super_statement(stmt, location); } + #[tracing::instrument(level = "trace", skip(self, term), ret)] + fn visit_terminator(&mut self, term: &mut Terminator<'tcx>, location: Location) { + if let TerminatorKind::Return = term.kind { + // `visit_basic_block_data` introduces `Return` terminators which read `RETURN_PLACE`. + // But this `RETURN_PLACE` is already remapped, so we should not touch it again. + return; + } + self.super_terminator(term, location); + } + #[tracing::instrument(level = "trace", skip(self, data), ret)] fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) { match data.terminator().kind { @@ -426,7 +453,7 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { let source_info = data.terminator().source_info; // We must assign the value first in case it gets declared dead below self.make_state( - Operand::Move(Place::from(self.old_ret_local)), + Operand::Move(Place::return_place()), source_info, true, &mut data.statements, @@ -521,27 +548,6 @@ fn make_coroutine_state_argument_pinned<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body .visit_body(body); } -/// Allocates a new local and replaces all references of `local` with it. Returns the new local. -/// -/// `local` will be changed to a new local decl with type `ty`. -/// -/// Note that the new local will be uninitialized. It is the caller's responsibility to assign some -/// valid value to it before its first use. -fn replace_local<'tcx>( - local: Local, - ty: Ty<'tcx>, - body: &mut Body<'tcx>, - tcx: TyCtxt<'tcx>, -) -> Local { - let new_decl = LocalDecl::new(ty, body.span); - let new_local = body.local_decls.push(new_decl); - body.local_decls.swap(local, new_local); - - RenameLocalVisitor { from: local, to: new_local, tcx }.visit_body(body); - - new_local -} - /// Transforms the `body` of the coroutine applying the following transforms: /// /// - Eliminates all the `get_context` calls that async lowering created. @@ -1511,10 +1517,6 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { } }; - // We rename RETURN_PLACE which has type mir.return_ty to old_ret_local - // RETURN_PLACE then is a fresh unused local with type ret_ty. - let old_ret_local = replace_local(RETURN_PLACE, new_ret_ty, body, tcx); - // We need to insert clean drop for unresumed state and perform drop elaboration // (finally in open_drop_for_tuple) before async drop expansion. // Async drops, produced by this drop elaboration, will be expanded, @@ -1561,6 +1563,11 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { let can_return = can_return(tcx, body, body.typing_env(tcx)); + // We rename RETURN_PLACE which has type mir.return_ty to new_ret_local + // RETURN_PLACE then is a fresh unused local with type ret_ty. + let new_ret_local = body.local_decls.push(LocalDecl::new(new_ret_ty, body.span)); + tracing::trace!(?new_ret_local); + // Run the transformation which converts Places from Local to coroutine struct // accesses for locals in `remap`. // It also rewrites `return x` and `yield y` as writing a new coroutine state and returning @@ -1573,13 +1580,16 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { storage_liveness, always_live_locals, suspension_points: Vec::new(), - old_ret_local, discr_ty, + new_ret_local, old_ret_ty, old_yield_ty, }; transform.visit_body(body); + // Swap the actual `RETURN_PLACE` and the provisional `new_ret_local`. + transform.replace_local(RETURN_PLACE, new_ret_local, body); + // MIR parameters are not explicitly assigned-to when entering the MIR body. // If we want to save their values inside the coroutine state, we need to do so explicitly. let source_info = SourceInfo::outermost(body.span); diff --git a/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-abort.mir b/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-abort.mir index 050aac7c3ff0..541b9b9895b3 100644 --- a/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-abort.mir +++ b/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-abort.mir @@ -10,16 +10,16 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a()}>, _2: &mut Context<'_>) let mut _6: std::pin::Pin<&mut T>; let mut _7: &mut T; let mut _8: *mut T; - let mut _9: (); - let mut _10: std::task::Poll<()>; - let mut _11: &mut std::task::Context<'_>; - let mut _12: &mut impl std::future::Future; - let mut _13: std::pin::Pin<&mut impl std::future::Future>; - let mut _14: isize; - let mut _15: &mut std::task::Context<'_>; - let mut _16: &mut impl std::future::Future; - let mut _17: std::pin::Pin<&mut impl std::future::Future>; - let mut _18: isize; + let mut _9: std::task::Poll<()>; + let mut _10: &mut std::task::Context<'_>; + let mut _11: &mut impl std::future::Future; + let mut _12: std::pin::Pin<&mut impl std::future::Future>; + let mut _13: isize; + let mut _14: &mut std::task::Context<'_>; + let mut _15: &mut impl std::future::Future; + let mut _16: std::pin::Pin<&mut impl std::future::Future>; + let mut _17: isize; + let mut _18: (); let mut _19: u32; scope 1 { debug x => (((*(_1.0: &mut {async fn body of a()})) as variant#4).0: T); @@ -48,9 +48,9 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a()}>, _2: &mut Context<'_>) } bb4: { - StorageLive(_17); - _16 = &mut (((*(_1.0: &mut {async fn body of a()})) as variant#4).1: impl std::future::Future); - _17 = Pin::<&mut impl Future>::new_unchecked(move _16) -> [return: bb7, unwind unreachable]; + StorageLive(_16); + _15 = &mut (((*(_1.0: &mut {async fn body of a()})) as variant#4).1: impl std::future::Future); + _16 = Pin::<&mut impl Future>::new_unchecked(move _15) -> [return: bb7, unwind unreachable]; } bb5: { @@ -58,13 +58,13 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a()}>, _2: &mut Context<'_>) } bb6: { - StorageDead(_17); - _18 = discriminant(_10); - switchInt(move _18) -> [0: bb1, 1: bb3, otherwise: bb5]; + StorageDead(_16); + _17 = discriminant(_9); + switchInt(move _17) -> [0: bb1, 1: bb3, otherwise: bb5]; } bb7: { - _10 = as Future>::poll(move _17, move _15) -> [return: bb6, unwind unreachable]; + _9 = as Future>::poll(move _16, move _14) -> [return: bb6, unwind unreachable]; } bb8: { diff --git a/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-unwind.mir b/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-unwind.mir index 796e95ff3d82..f2e1fb099756 100644 --- a/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-unwind.mir +++ b/tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-unwind.mir @@ -10,16 +10,16 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a()}>, _2: &mut Context<'_>) let mut _6: std::pin::Pin<&mut T>; let mut _7: &mut T; let mut _8: *mut T; - let mut _9: (); - let mut _10: std::task::Poll<()>; - let mut _11: &mut std::task::Context<'_>; - let mut _12: &mut impl std::future::Future; - let mut _13: std::pin::Pin<&mut impl std::future::Future>; - let mut _14: isize; - let mut _15: &mut std::task::Context<'_>; - let mut _16: &mut impl std::future::Future; - let mut _17: std::pin::Pin<&mut impl std::future::Future>; - let mut _18: isize; + let mut _9: std::task::Poll<()>; + let mut _10: &mut std::task::Context<'_>; + let mut _11: &mut impl std::future::Future; + let mut _12: std::pin::Pin<&mut impl std::future::Future>; + let mut _13: isize; + let mut _14: &mut std::task::Context<'_>; + let mut _15: &mut impl std::future::Future; + let mut _16: std::pin::Pin<&mut impl std::future::Future>; + let mut _17: isize; + let mut _18: (); let mut _19: u32; scope 1 { debug x => (((*(_1.0: &mut {async fn body of a()})) as variant#4).0: T); @@ -62,9 +62,9 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a()}>, _2: &mut Context<'_>) } bb7: { - StorageLive(_17); - _16 = &mut (((*(_1.0: &mut {async fn body of a()})) as variant#4).1: impl std::future::Future); - _17 = Pin::<&mut impl Future>::new_unchecked(move _16) -> [return: bb10, unwind: bb15]; + StorageLive(_16); + _15 = &mut (((*(_1.0: &mut {async fn body of a()})) as variant#4).1: impl std::future::Future); + _16 = Pin::<&mut impl Future>::new_unchecked(move _15) -> [return: bb10, unwind: bb15]; } bb8: { @@ -72,13 +72,13 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a()}>, _2: &mut Context<'_>) } bb9: { - StorageDead(_17); - _18 = discriminant(_10); - switchInt(move _18) -> [0: bb1, 1: bb6, otherwise: bb8]; + StorageDead(_16); + _17 = discriminant(_9); + switchInt(move _17) -> [0: bb1, 1: bb6, otherwise: bb8]; } bb10: { - _10 = as Future>::poll(move _17, move _15) -> [return: bb9, unwind: bb3]; + _9 = as Future>::poll(move _16, move _14) -> [return: bb9, unwind: bb3]; } bb11: { From afea346b3548a9f995b2aacf389dfb094d3997b4 Mon Sep 17 00:00:00 2001 From: vishruth-thimmaiah Date: Fri, 10 Oct 2025 14:55:09 +0530 Subject: [PATCH 27/35] feat: add failing test for zero-sized memset Signed-off-by: vishruth-thimmaiah --- src/tools/miri/src/shims/foreign_items.rs | 1 + src/tools/miri/tests/fail-dep/libc/memset_null.rs | 8 ++++++++ .../miri/tests/fail-dep/libc/memset_null.stderr | 15 +++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/tools/miri/tests/fail-dep/libc/memset_null.rs create mode 100644 src/tools/miri/tests/fail-dep/libc/memset_null.stderr diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 3fd57d3c8db2..08964ba7b329 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -837,6 +837,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { #[expect(clippy::as_conversions)] let val = val as u8; + // C requires that this must always be a valid pointer, even if `n` is zero, so we better check that. this.ptr_get_alloc_id(ptr_dest, 0)?; let bytes = std::iter::repeat_n(val, n.try_into().unwrap()); diff --git a/src/tools/miri/tests/fail-dep/libc/memset_null.rs b/src/tools/miri/tests/fail-dep/libc/memset_null.rs new file mode 100644 index 000000000000..c3fa9973e762 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/memset_null.rs @@ -0,0 +1,8 @@ +use std::ptr; + +// null is explicitly called out as UB in the C docs for `memset`. +fn main() { + unsafe { + libc::memset(ptr::null_mut(), 0, 0); //~ERROR: null pointer + } +} diff --git a/src/tools/miri/tests/fail-dep/libc/memset_null.stderr b/src/tools/miri/tests/fail-dep/libc/memset_null.stderr new file mode 100644 index 000000000000..fdc8f3a29f94 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/memset_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: pointer not dereferenceable: pointer must point to some allocation, but got null pointer + --> tests/fail-dep/libc/memset_null.rs:LL:CC + | +LL | libc::memset(ptr::null_mut(), 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail-dep/libc/memset_null.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 61594ca4aa5eeba01a0a66843b8002f34ac65de7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 12 Oct 2025 17:03:35 +0200 Subject: [PATCH 28/35] remove a bunch of unnecessary 'pub' from tests --- .../tests/fail/both_borrows/aliasing_mut1.rs | 2 +- .../both_borrows/aliasing_mut1.stack.stderr | 8 ++--- .../both_borrows/aliasing_mut1.tree.stderr | 8 ++--- .../tests/fail/both_borrows/aliasing_mut2.rs | 2 +- .../both_borrows/aliasing_mut2.stack.stderr | 8 ++--- .../both_borrows/aliasing_mut2.tree.stderr | 4 +-- .../tests/fail/both_borrows/aliasing_mut3.rs | 2 +- .../both_borrows/aliasing_mut3.stack.stderr | 4 +-- .../both_borrows/aliasing_mut3.tree.stderr | 8 ++--- .../tests/fail/both_borrows/aliasing_mut4.rs | 2 +- .../both_borrows/aliasing_mut4.stack.stderr | 8 ++--- .../both_borrows/aliasing_mut4.tree.stderr | 4 +-- .../tests/fail/data_race/alloc_read_race.rs | 2 +- .../tests/fail/data_race/alloc_write_race.rs | 2 +- .../data_race/atomic_read_na_write_race1.rs | 2 +- .../data_race/atomic_read_na_write_race2.rs | 2 +- .../data_race/atomic_write_na_read_race1.rs | 2 +- .../data_race/atomic_write_na_read_race2.rs | 2 +- .../data_race/atomic_write_na_write_race1.rs | 2 +- .../data_race/atomic_write_na_write_race2.rs | 2 +- .../fail/data_race/dealloc_read_race1.rs | 2 +- .../fail/data_race/dealloc_read_race2.rs | 2 +- .../fail/data_race/dealloc_read_race_stack.rs | 2 +- .../fail/data_race/dealloc_write_race1.rs | 2 +- .../fail/data_race/dealloc_write_race2.rs | 2 +- .../data_race/dealloc_write_race_stack.rs | 2 +- .../data_race/enable_after_join_to_main.rs | 2 +- .../tests/fail/data_race/read_write_race.rs | 2 +- .../fail/data_race/read_write_race_stack.rs | 2 +- .../fail/data_race/relax_acquire_race.rs | 2 +- .../tests/fail/data_race/release_seq_race.rs | 2 +- .../data_race/release_seq_race_same_thread.rs | 2 +- .../miri/tests/fail/data_race/rmw_race.rs | 2 +- .../tests/fail/data_race/write_write_race.rs | 2 +- .../fail/data_race/write_write_race_stack.rs | 2 +- ...um-set-discriminant-niche-variant-wrong.rs | 2 +- .../arg_inplace_locals_alias.rs | 2 +- .../arg_inplace_locals_alias_ret.rs | 2 +- .../fail/function_calls/arg_inplace_mutate.rs | 2 +- .../arg_inplace_observe_after.rs | 2 +- .../arg_inplace_observe_during.rs | 2 +- .../return_pointer_aliasing_read.rs | 2 +- .../return_pointer_aliasing_write.rs | 2 +- ...return_pointer_aliasing_write_tail_call.rs | 2 +- .../simd_feature_flag_difference.rs | 2 +- .../tests/fail/intrinsics/ctlz_nonzero.rs | 2 +- .../tests/fail/intrinsics/cttz_nonzero.rs | 2 +- src/tools/miri/tests/fail/issue-miri-1112.rs | 2 +- .../miri/tests/fail/overlapping_assignment.rs | 4 +-- .../retag_data_race_read.tree.stderr | 2 +- src/tools/miri/tests/fail/tls_macro_leak.rs | 2 +- src/tools/miri/tests/fail/tls_static_leak.rs | 2 +- .../fail/tree_borrows/alternate-read-write.rs | 2 +- .../fail/tree_borrows/cell-inside-struct.rs | 2 +- .../repeated_foreign_read_lazy_conflicted.rs | 2 +- .../fail/tree_borrows/write-during-2phase.rs | 2 +- .../fail/uninit/padding-struct-in-union.rs | 2 +- .../tests/fail/validity/invalid_char_cast.rs | 2 +- .../tests/fail/validity/invalid_char_match.rs | 2 +- .../tests/fail/validity/invalid_enum_cast.rs | 2 +- .../tests/fail/weak_memory/weak_uninit.rs | 2 +- src/tools/miri/tests/panic/mir-validation.rs | 2 +- .../concurrency/tls_pthread_drop_order.rs | 2 +- .../tests/pass-dep/foreign-fn-linkname.rs | 2 +- .../miri/tests/pass-dep/regions-mock-trans.rs | 2 +- src/tools/miri/tests/pass-dep/wcslen.rs | 2 +- .../tests/pass/0weak_memory/consistency.rs | 4 +-- .../tests/pass/0weak_memory/consistency_sc.rs | 2 +- .../miri/tests/pass/0weak_memory/extra_cpp.rs | 2 +- .../miri/tests/pass/0weak_memory/weak.rs | 4 +-- .../align_repeat_into_well_aligned_array.rs | 2 +- .../miri/tests/pass/async-closure-captures.rs | 2 +- .../miri/tests/pass/async-closure-drop.rs | 4 +-- src/tools/miri/tests/pass/async-closure.rs | 4 +-- src/tools/miri/tests/pass/binops.rs | 2 +- src/tools/miri/tests/pass/btreemap.rs | 2 +- src/tools/miri/tests/pass/calls.rs | 2 +- .../miri/tests/pass/concurrency/data_race.rs | 6 ++-- .../concurrency/disable_data_race_detector.rs | 2 +- src/tools/miri/tests/pass/const-vec-of-fns.rs | 2 +- .../tests/pass/drop_type_without_drop_glue.rs | 2 +- src/tools/miri/tests/pass/dst-raw.rs | 2 +- src/tools/miri/tests/pass/dst-struct-sole.rs | 2 +- src/tools/miri/tests/pass/dst-struct.rs | 2 +- .../enum-nullable-const-null-with-fields.rs | 2 +- src/tools/miri/tests/pass/float.rs | 30 +++++++++---------- .../function_calls/return_place_on_heap.rs | 2 +- src/tools/miri/tests/pass/integer-ops.rs | 2 +- .../miri/tests/pass/intrinsics/integer.rs | 2 +- .../miri/tests/pass/intrinsics/volatile.rs | 2 +- .../miri/tests/pass/issues/issue-30530.rs | 2 +- .../miri/tests/pass/issues/issue-3794.rs | 2 +- .../miri/tests/pass/issues/issue-5917.rs | 2 +- .../miri/tests/pass/issues/issue-miri-184.rs | 2 +- .../miri/tests/pass/issues/issue-miri-2068.rs | 2 +- ...iri-3541-dyn-vtable-trait-normalization.rs | 2 +- .../miri/tests/pass/last-use-in-cap-clause.rs | 2 +- src/tools/miri/tests/pass/loop-break-value.rs | 2 +- .../miri/tests/pass/move-arg-2-unique.rs | 2 +- .../miri/tests/pass/move-arg-3-unique.rs | 2 +- src/tools/miri/tests/pass/mpsc.rs | 2 +- .../miri/tests/pass/panic/unwind_dwarf.rs | 4 +-- .../regions-lifetime-nonfree-late-bound.rs | 2 +- src/tools/miri/tests/pass/sendable-class.rs | 2 +- .../miri/tests/pass/tag-align-dyn-u64.rs | 2 +- .../pass/tls/tls_leak_main_thread_allowed.rs | 2 +- .../pass/tree_borrows/cell-inside-box.rs | 2 +- .../pass/tree_borrows/cell-inside-struct.rs | 2 +- .../pass/tree_borrows/copy-nonoverlapping.rs | 2 +- .../tests/pass/tree_borrows/tree-borrows.rs | 4 +-- src/tools/miri/tests/pass/unsized.rs | 8 ++--- .../miri/tests/pass/vec-matching-fold.rs | 2 +- src/tools/miri/tests/ui.rs | 5 ---- 113 files changed, 156 insertions(+), 161 deletions(-) diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs index a9efe17fddfa..a8494eaf0aa4 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs @@ -2,7 +2,7 @@ //@[tree]compile-flags: -Zmiri-tree-borrows use std::mem; -pub fn safe(x: &mut i32, y: &mut i32) { +fn safe(x: &mut i32, y: &mut i32) { //~[stack]^ ERROR: protect *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ *y = 2; diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr index b7fdc7bc414e..196eaeb3fb6c 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ Undefined Behavior occurred here +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information @@ -14,8 +14,8 @@ LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; help: is this argument --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut1.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr index 207ed3131af3..b9e6e2547806 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr @@ -10,13 +10,13 @@ LL | *x = 1; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ help: the accessed tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut1.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &mut i32) { - | ^ +LL | fn safe(x: &mut i32, y: &mut i32) { + | ^ = help: this transition corresponds to a temporary loss of write permissions until function exit = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut1.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs index 74ea2b28627c..c1320a25cafa 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs @@ -2,7 +2,7 @@ //@[tree]compile-flags: -Zmiri-tree-borrows use std::mem; -pub fn safe(x: &i32, y: &mut i32) { +fn safe(x: &i32, y: &mut i32) { //~[stack]^ ERROR: protect let _v = *x; *y = 2; //~[tree] ERROR: /write access through .* is forbidden/ diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr index a13cbec66552..e70e5b10793c 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut i32) { - | ^ Undefined Behavior occurred here +LL | fn safe(x: &i32, y: &mut i32) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information @@ -14,8 +14,8 @@ LL | let xref = &mut x; help: is this argument --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut i32) { - | ^ +LL | fn safe(x: &i32, y: &mut i32) { + | ^ = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut2.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr index 90b1b1294c7f..aed59b21f137 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr @@ -10,8 +10,8 @@ LL | *y = 2; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut i32) { - | ^ +LL | fn safe(x: &i32, y: &mut i32) { + | ^ help: the accessed tag later transitioned to Reserved (conflicted) due to a foreign read access at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut2.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs index 8cb60faf2d07..555e4478224c 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs @@ -2,7 +2,7 @@ //@[tree]compile-flags: -Zmiri-tree-borrows use std::mem; -pub fn safe(x: &mut i32, y: &i32) { +fn safe(x: &mut i32, y: &i32) { //~[stack]^ ERROR: borrow stack *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ let _v = *y; diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr index 0e9382be2e8f..9980d14e1054 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &i32) { - | ^ this error occurs as part of function-entry retag at ALLOC[0x0..0x4] +LL | fn safe(x: &mut i32, y: &i32) { + | ^ this error occurs as part of function-entry retag at ALLOC[0x0..0x4] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr index 73a502764638..357d7d220192 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr @@ -10,13 +10,13 @@ LL | *x = 1; help: the accessed tag was created here, in the initial state Reserved --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &i32) { - | ^ +LL | fn safe(x: &mut i32, y: &i32) { + | ^ help: the accessed tag later transitioned to Reserved (conflicted) due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] --> tests/fail/both_borrows/aliasing_mut3.rs:LL:CC | -LL | pub fn safe(x: &mut i32, y: &i32) { - | ^ +LL | fn safe(x: &mut i32, y: &i32) { + | ^ = help: this transition corresponds to a temporary loss of write permissions until function exit = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut3.rs:LL:CC diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs index c656a5096445..22484972f4d1 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs @@ -5,7 +5,7 @@ use std::cell::Cell; use std::mem; // Make sure &mut UnsafeCell also is exclusive -pub fn safe(x: &i32, y: &mut Cell) { +fn safe(x: &i32, y: &mut Cell) { //~[stack]^ ERROR: protect y.set(1); let _load = *x; diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr index c5ad269b39ac..eb2514df588a 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut Cell) { - | ^ Undefined Behavior occurred here +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ Undefined Behavior occurred here | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information @@ -14,8 +14,8 @@ LL | let xref = &mut x; help: is this argument --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut Cell) { - | ^ +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ = note: BACKTRACE (of the first span): = note: inside `safe` at tests/fail/both_borrows/aliasing_mut4.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr index a6a6da3fa2ae..c06ae0e92138 100644 --- a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr @@ -17,8 +17,8 @@ LL | y.set(1); help: the protected tag was created here, in the initial state Frozen --> tests/fail/both_borrows/aliasing_mut4.rs:LL:CC | -LL | pub fn safe(x: &i32, y: &mut Cell) { - | ^ +LL | fn safe(x: &i32, y: &mut Cell) { + | ^ = note: BACKTRACE (of the first span): = note: inside `std::mem::replace::` at RUSTLIB/core/src/mem/mod.rs:LL:CC = note: inside `std::cell::Cell::::replace` at RUSTLIB/core/src/cell.rs:LL:CC diff --git a/src/tools/miri/tests/fail/data_race/alloc_read_race.rs b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs index 7c5116989943..65622b7f7783 100644 --- a/src/tools/miri/tests/fail/data_race/alloc_read_race.rs +++ b/src/tools/miri/tests/fail/data_race/alloc_read_race.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::>()); let ptr = EvilSend(&pointer as *const AtomicPtr>); diff --git a/src/tools/miri/tests/fail/data_race/alloc_write_race.rs b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs index ba8a888de9ea..a07d284c6c8c 100644 --- a/src/tools/miri/tests/fail/data_race/alloc_write_race.rs +++ b/src/tools/miri/tests/fail/data_race/alloc_write_race.rs @@ -11,7 +11,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs index 8cce54603ce5..90fd535f1cb6 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race1.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs index b6c0ef37cb92..8c59713010af 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_read_na_write_race2.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs index 03ae6895c574..95b216a6fe64 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race1.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs index 4a5edf5cc14d..c4714e632c3b 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_read_race2.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs index e8d930a51dee..3ef60074fd43 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race1.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs index 4c67d2d76541..b704468165a4 100644 --- a/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/atomic_write_na_write_race2.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = AtomicUsize::new(0); let b = &mut a as *mut AtomicUsize; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs index 999cc2392f5a..64bababe0c85 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race1.rs @@ -16,7 +16,7 @@ extern "Rust" { fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs index bd3b037e5838..6e85bcf03aa5 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs @@ -16,7 +16,7 @@ extern "Rust" { fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs index e3d06660aab3..76c26da05782 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs index 90e87f8c4956..fd71ef09b125 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race1.rs @@ -15,7 +15,7 @@ extern "Rust" { #[rustc_std_internal_symbol] fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs index d9b1af80af49..5c8bbc14a492 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs @@ -15,7 +15,7 @@ extern "Rust" { #[rustc_std_internal_symbol] fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize); } -pub fn main() { +fn main() { // Shared atomic pointer let pointer: *mut usize = Box::into_raw(Box::new(0usize)); let ptr = EvilSend(pointer); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs index c1ab1942c688..bdc25100abaf 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs index 67af6862737d..4d716f7db6fd 100644 --- a/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs +++ b/src/tools/miri/tests/fail/data_race/enable_after_join_to_main.rs @@ -9,7 +9,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Enable and then join with multiple threads. let t1 = spawn(|| ()); let t2 = spawn(|| ()); diff --git a/src/tools/miri/tests/fail/data_race/read_write_race.rs b/src/tools/miri/tests/fail/data_race/read_write_race.rs index 2aadef36c5b9..e7961a4d849c 100644 --- a/src/tools/miri/tests/fail/data_race/read_write_race.rs +++ b/src/tools/miri/tests/fail/data_race/read_write_race.rs @@ -9,7 +9,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs index cca39bb002c1..4555a82df6c9 100644 --- a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs index 262c039e4ae1..67e1d65126fa 100644 --- a/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs +++ b/src/tools/miri/tests/fail/data_race/relax_acquire_race.rs @@ -12,7 +12,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race.rs b/src/tools/miri/tests/fail/data_race/release_seq_race.rs index 8aeb6ee6ef1d..5016617e5d7f 100644 --- a/src/tools/miri/tests/fail/data_race/release_seq_race.rs +++ b/src/tools/miri/tests/fail/data_race/release_seq_race.rs @@ -13,7 +13,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs index f465160718f4..ae6a6154e3c6 100644 --- a/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs +++ b/src/tools/miri/tests/fail/data_race/release_seq_race_same_thread.rs @@ -12,7 +12,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/rmw_race.rs b/src/tools/miri/tests/fail/data_race/rmw_race.rs index 39588c15ec7e..51fddbd68472 100644 --- a/src/tools/miri/tests/fail/data_race/rmw_race.rs +++ b/src/tools/miri/tests/fail/data_race/rmw_race.rs @@ -12,7 +12,7 @@ unsafe impl Sync for EvilSend {} static SYNC: AtomicUsize = AtomicUsize::new(0); -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/write_write_race.rs b/src/tools/miri/tests/fail/data_race/write_write_race.rs index b1a6b08b4c88..2070c43b7cff 100644 --- a/src/tools/miri/tests/fail/data_race/write_write_race.rs +++ b/src/tools/miri/tests/fail/data_race/write_write_race.rs @@ -9,7 +9,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs index cd21b0a8fa6c..b92d17bf8bcd 100644 --- a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs @@ -12,7 +12,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { // Shared atomic pointer let pointer = AtomicPtr::new(null_mut::()); let ptr = EvilSend(&pointer as *const AtomicPtr); diff --git a/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs index eca6d908b448..221ea106538e 100644 --- a/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs +++ b/src/tools/miri/tests/fail/enum-set-discriminant-niche-variant-wrong.rs @@ -25,7 +25,7 @@ fn set_discriminant(ptr: &mut Option>) { } } -pub fn main() { +fn main() { let mut v = None; set_discriminant(&mut v); } diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs index 744d64b9b1e8..8858658eddbf 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs @@ -28,7 +28,7 @@ fn main() { } } -pub fn callee(x: S, mut y: S) { +fn callee(x: S, mut y: S) { // With the setup above, if `x` and `y` are both moved, // then writing to `y` will change the value stored in `x`! y.0 = 0; diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs index dff724f8d965..3cb8ee2b407c 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias_ret.rs @@ -29,6 +29,6 @@ fn main() { } } -pub fn callee(x: S) -> S { +fn callee(x: S) -> S { x } diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs index 4f7a12ebd2e8..c61083c17a6b 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs @@ -22,7 +22,7 @@ fn main() { } } -pub fn callee(x: S, ptr: *mut S) { +fn callee(x: S, ptr: *mut S) { // With the setup above, if `x` is indeed moved in // (i.e. we actually just get a pointer to the underlying storage), // then writing to `ptr` will change the value stored in `x`! diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs index 18daf9497a15..d1b8f3eed059 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs @@ -22,6 +22,6 @@ fn main() { } } -pub fn change_arg(mut x: S) { +fn change_arg(mut x: S) { x.0 = 0; } diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs index 2201bf17bfc7..97c5cb75893e 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs @@ -23,7 +23,7 @@ fn main() { } } -pub fn change_arg(mut x: S, ptr: *mut S) { +fn change_arg(mut x: S, ptr: *mut S) { x.0 = 0; // If `x` got passed in-place, we'd see the write through `ptr`! // Make sure we are not allowed to do that read. diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs index dc22e129e18a..d981286a141a 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_read.rs @@ -7,7 +7,7 @@ use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let _x = 0; diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs index 2fddaf37235b..a4e48b0aac6f 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write.rs @@ -7,7 +7,7 @@ use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let _x = 0; diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs index 5f3ecb650227..3390ddad9b85 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing_write_tail_call.rs @@ -9,7 +9,7 @@ use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let _x = 0; diff --git a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs index 200f1062a3e8..0dd5d09abdea 100644 --- a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs +++ b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs @@ -9,7 +9,7 @@ pub unsafe extern "C" fn foo(_y: f32, x: __m256) -> __m256 { x } -pub fn bar(x: __m256) -> __m256 { +fn bar(x: __m256) -> __m256 { // The first and second argument get mixed up here since caller // and callee do not have the same feature flags. // In Miri, we don't have a concept of "dynamically available feature flags", diff --git a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs index 3da54b918826..8ea029190276 100644 --- a/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs +++ b/src/tools/miri/tests/fail/intrinsics/ctlz_nonzero.rs @@ -1,6 +1,6 @@ #![feature(core_intrinsics)] -pub fn main() { +fn main() { unsafe { use std::intrinsics::*; diff --git a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs index 2b68f6713d80..471347a752d2 100644 --- a/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs +++ b/src/tools/miri/tests/fail/intrinsics/cttz_nonzero.rs @@ -1,6 +1,6 @@ #![feature(core_intrinsics)] -pub fn main() { +fn main() { unsafe { use std::intrinsics::*; diff --git a/src/tools/miri/tests/fail/issue-miri-1112.rs b/src/tools/miri/tests/fail/issue-miri-1112.rs index 387253a3f987..fc21fff9a9f9 100644 --- a/src/tools/miri/tests/fail/issue-miri-1112.rs +++ b/src/tools/miri/tests/fail/issue-miri-1112.rs @@ -11,7 +11,7 @@ pub struct Meta { } impl Meta { - pub fn new() -> Self { + fn new() -> Self { Meta { drop_fn: |_| {}, size: 0, align: 1 } } } diff --git a/src/tools/miri/tests/fail/overlapping_assignment.rs b/src/tools/miri/tests/fail/overlapping_assignment.rs index 84994c179f9e..237d674513f0 100644 --- a/src/tools/miri/tests/fail/overlapping_assignment.rs +++ b/src/tools/miri/tests/fail/overlapping_assignment.rs @@ -7,7 +7,7 @@ use std::intrinsics::mir::*; // which wants to prevent overlapping assignments... // So we use two separate pointer arguments, and then arrange for them to alias. #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { +fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { mir! { { *ptr1 = *ptr2; //~ERROR: overlapping ranges @@ -16,7 +16,7 @@ pub fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { } } -pub fn main() { +fn main() { let mut x = [0; 4]; let ptr = std::ptr::addr_of_mut!(x); self_copy(ptr, ptr); diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr index 675bb01b5e75..e6c1745d321c 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr @@ -16,7 +16,7 @@ LL | panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_e help: the protected tag was created here, in the initial state Active --> RUSTLIB/std/src/panic.rs:LL:CC | -LL | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { +LL | fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { | ^ = note: BACKTRACE (of the first span): = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tls_macro_leak.rs b/src/tools/miri/tests/fail/tls_macro_leak.rs index b8a4b81911ac..1aa161061c02 100644 --- a/src/tools/miri/tests/fail/tls_macro_leak.rs +++ b/src/tools/miri/tests/fail/tls_macro_leak.rs @@ -2,7 +2,7 @@ use std::cell::Cell; -pub fn main() { +fn main() { thread_local! { static TLS: Cell> = Cell::new(None); } diff --git a/src/tools/miri/tests/fail/tls_static_leak.rs b/src/tools/miri/tests/fail/tls_static_leak.rs index 4d5280336377..4e29b3afe3b1 100644 --- a/src/tools/miri/tests/fail/tls_static_leak.rs +++ b/src/tools/miri/tests/fail/tls_static_leak.rs @@ -6,7 +6,7 @@ use std::cell::Cell; /// Ensure that leaks through `thread_local` statics *not in the main thread* /// are detected. -pub fn main() { +fn main() { #[thread_local] static TLS: Cell> = Cell::new(None); diff --git a/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs index fee88cf3486a..8d49a3cdbf5f 100644 --- a/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs +++ b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs @@ -2,7 +2,7 @@ // Check that TB properly rejects alternating Reads and Writes, but tolerates // alternating only Reads to Reserved mutable references. -pub fn main() { +fn main() { let x = &mut 0u8; let y = unsafe { &mut *(x as *mut u8) }; // Foreign Read, but this is a no-op from the point of view of y (still Reserved) diff --git a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs index ff7978776822..1aefa217e2d4 100644 --- a/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs +++ b/src/tools/miri/tests/fail/tree_borrows/cell-inside-struct.rs @@ -11,7 +11,7 @@ struct Foo { field2: Cell, } -pub fn main() { +fn main() { let root = Foo { field1: 42, field2: Cell::new(88) }; unsafe { let a = &root; diff --git a/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs b/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs index 36b47a33b181..4f0d97b4a109 100644 --- a/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs +++ b/src/tools/miri/tests/fail/tree_borrows/repeated_foreign_read_lazy_conflicted.rs @@ -11,7 +11,7 @@ unsafe fn access_after_sub_1(x: &mut u8, orig_ptr: *mut u8) { *(x as *mut u8).byte_sub(1) = 42; //~ ERROR: /write access through .* is forbidden/ } -pub fn main() { +fn main() { unsafe { let mut alloc = [0u8, 0u8]; let orig_ptr = addr_of_mut!(alloc) as *mut u8; diff --git a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs index a2e8a533c43a..ac9b7d7e528e 100644 --- a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs +++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs @@ -14,7 +14,7 @@ impl Foo { } } -pub fn main() { +fn main() { let mut f = Foo(0); let alias = &mut f.0 as *mut u64; let res = f.add(unsafe { diff --git a/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs index 132b85828362..8343952a2046 100644 --- a/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs +++ b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs @@ -18,7 +18,7 @@ union FooBar { bar: Bar, } -pub fn main() { +fn main() { // Initialize as u8 to ensure padding bytes are zeroed. let mut foobar = FooBar { bar: Bar { bytes: [0u8; 8] } }; // Reading either field is ok. diff --git a/src/tools/miri/tests/fail/validity/invalid_char_cast.rs b/src/tools/miri/tests/fail/validity/invalid_char_cast.rs index 6a590dc7ba10..94e1a87fba75 100644 --- a/src/tools/miri/tests/fail/validity/invalid_char_cast.rs +++ b/src/tools/miri/tests/fail/validity/invalid_char_cast.rs @@ -15,7 +15,7 @@ fn cast(ptr: *const char) -> u32 { } } -pub fn main() { +fn main() { let v = u32::MAX; cast(&v as *const u32 as *const char); } diff --git a/src/tools/miri/tests/fail/validity/invalid_char_match.rs b/src/tools/miri/tests/fail/validity/invalid_char_match.rs index 6c2e65b2bb74..6ec5768162be 100644 --- a/src/tools/miri/tests/fail/validity/invalid_char_match.rs +++ b/src/tools/miri/tests/fail/validity/invalid_char_match.rs @@ -20,7 +20,7 @@ fn switch_int(ptr: *const char) { } } -pub fn main() { +fn main() { let v = u32::MAX; switch_int(&v as *const u32 as *const char); } diff --git a/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs b/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs index ed451a435b95..ba110ca96d69 100644 --- a/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs +++ b/src/tools/miri/tests/fail/validity/invalid_enum_cast.rs @@ -15,7 +15,7 @@ fn cast(ptr: *const E) { } } -pub fn main() { +fn main() { let v = u32::MAX; cast(&v as *const u32 as *const E); } diff --git a/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs b/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs index b4b4b0849877..7a4e038fabf7 100644 --- a/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs +++ b/src/tools/miri/tests/fail/weak_memory/weak_uninit.rs @@ -34,7 +34,7 @@ fn relaxed() { j2.join().unwrap(); } -pub fn main() { +fn main() { // If we try often enough, we should hit UB. for _ in 0..100 { relaxed(); diff --git a/src/tools/miri/tests/panic/mir-validation.rs b/src/tools/miri/tests/panic/mir-validation.rs index 2d0d530754d8..11c4e395920c 100644 --- a/src/tools/miri/tests/panic/mir-validation.rs +++ b/src/tools/miri/tests/panic/mir-validation.rs @@ -13,7 +13,7 @@ use core::intrinsics::mir::*; #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { let x: i32; let tuple: (*mut i32,); diff --git a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs index b57386000404..df4278002161 100644 --- a/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs +++ b/src/tools/miri/tests/pass-dep/concurrency/tls_pthread_drop_order.rs @@ -29,7 +29,7 @@ pub unsafe fn set(key: Key, value: *mut u8) { assert_eq!(r, 0); } -pub fn record(r: usize) { +fn record(r: usize) { assert!(r < 10); unsafe { RECORD = RECORD * 10 + r }; } diff --git a/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs index 9f090a4eff5d..afd5bc0d3b56 100644 --- a/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs +++ b/src/tools/miri/tests/pass-dep/foreign-fn-linkname.rs @@ -14,7 +14,7 @@ fn strlen(str: String) -> usize { unsafe { mlibc::my_strlen(s.as_ptr()) as usize } } -pub fn main() { +fn main() { let len = strlen("Rust".to_string()); assert_eq!(len, 4); } diff --git a/src/tools/miri/tests/pass-dep/regions-mock-trans.rs b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs index 57f1b75f4d52..7defea3c4876 100644 --- a/src/tools/miri/tests/pass-dep/regions-mock-trans.rs +++ b/src/tools/miri/tests/pass-dep/regions-mock-trans.rs @@ -39,7 +39,7 @@ fn f(ccx: &Ccx) { return g(&fcx); } -pub fn main() { +fn main() { let ccx = Ccx { x: 0 }; f(&ccx); } diff --git a/src/tools/miri/tests/pass-dep/wcslen.rs b/src/tools/miri/tests/pass-dep/wcslen.rs index c5c9d9924796..2ec4da79d727 100644 --- a/src/tools/miri/tests/pass-dep/wcslen.rs +++ b/src/tools/miri/tests/pass-dep/wcslen.rs @@ -13,7 +13,7 @@ fn to_c_wchar_t_str(s: &str) -> Vec { r } -pub fn main() { +fn main() { let s = to_c_wchar_t_str("Rust"); let len = unsafe { libc::wcslen(s.as_ptr()) }; assert_eq!(len, 4); diff --git a/src/tools/miri/tests/pass/0weak_memory/consistency.rs b/src/tools/miri/tests/pass/0weak_memory/consistency.rs index 16a38ebd9d4d..83674cca1bde 100644 --- a/src/tools/miri/tests/pass/0weak_memory/consistency.rs +++ b/src/tools/miri/tests/pass/0weak_memory/consistency.rs @@ -212,7 +212,7 @@ fn test_single_thread() { fn test_sync_through_rmw_and_fences() { // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905 #[no_mangle] - pub fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 { + fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 { storing.store(1, Relaxed); fence(Release); sync.fetch_add(0, Relaxed); @@ -245,7 +245,7 @@ fn test_sync_through_rmw_and_fences() { assert_ne!((a, b), (0, 0)); } -pub fn main() { +fn main() { for _ in 0..50 { test_single_thread(); test_mixed_access(); diff --git a/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs b/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs index cb8535b8ad74..d92c0d177997 100644 --- a/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs +++ b/src/tools/miri/tests/pass/0weak_memory/consistency_sc.rs @@ -348,7 +348,7 @@ fn test_sc_relaxed() { assert!(!bad); } -pub fn main() { +fn main() { for _ in 0..32 { test_sc_store_buffering(); test_iriw_sc_rlx(); diff --git a/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs b/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs index 94df73080806..6791382f8e0f 100644 --- a/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs +++ b/src/tools/miri/tests/pass/0weak_memory/extra_cpp.rs @@ -72,7 +72,7 @@ fn from_mut_split() { assert_eq!(x_lo_atomic.load(Relaxed), u16::from_be(0xfafa)); } -pub fn main() { +fn main() { get_mut_write(); from_mut_split(); assign_to_mut(); diff --git a/src/tools/miri/tests/pass/0weak_memory/weak.rs b/src/tools/miri/tests/pass/0weak_memory/weak.rs index 611733d0dac5..e329bbff1aae 100644 --- a/src/tools/miri/tests/pass/0weak_memory/weak.rs +++ b/src/tools/miri/tests/pass/0weak_memory/weak.rs @@ -115,7 +115,7 @@ fn initialization_write(add_fence: bool) { fn faa_replaced_by_load() { check_all_outcomes([true, false], || { // Example from https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905 - pub fn rdmw(storing: &AtomicUsize, sync: &AtomicUsize, loading: &AtomicUsize) -> usize { + fn rdmw(storing: &AtomicUsize, sync: &AtomicUsize, loading: &AtomicUsize) -> usize { storing.store(1, Relaxed); fence(Release); // sync.fetch_add(0, Relaxed); @@ -226,7 +226,7 @@ fn old_release_store() { }); } -pub fn main() { +fn main() { relaxed(); seq_cst(); initialization_write(false); diff --git a/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs b/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs index 735251039f77..6f1f28c46da2 100644 --- a/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs +++ b/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs @@ -24,7 +24,7 @@ pub const KEYBYTES: usize = 8 * size_of::(); pub const BLOCKBYTES: usize = 16 * size_of::(); impl Params { - pub fn new() -> Self { + fn new() -> Self { Self { hash_length: OUTBYTES as u8, key_length: 0, diff --git a/src/tools/miri/tests/pass/async-closure-captures.rs b/src/tools/miri/tests/pass/async-closure-captures.rs index ed6b7b205b54..785ff2bc0212 100644 --- a/src/tools/miri/tests/pass/async-closure-captures.rs +++ b/src/tools/miri/tests/pass/async-closure-captures.rs @@ -6,7 +6,7 @@ use std::future::Future; use std::pin::pin; use std::task::*; -pub fn block_on(fut: impl Future) -> T { +fn block_on(fut: impl Future) -> T { let mut fut = pin!(fut); let ctx = &mut Context::from_waker(Waker::noop()); diff --git a/src/tools/miri/tests/pass/async-closure-drop.rs b/src/tools/miri/tests/pass/async-closure-drop.rs index 105aa434b0ad..d1fd92814d95 100644 --- a/src/tools/miri/tests/pass/async-closure-drop.rs +++ b/src/tools/miri/tests/pass/async-closure-drop.rs @@ -4,7 +4,7 @@ use std::future::Future; use std::pin::pin; use std::task::*; -pub fn block_on(fut: impl Future) -> T { +fn block_on(fut: impl Future) -> T { let mut fut = pin!(fut); let ctx = &mut Context::from_waker(Waker::noop()); @@ -29,7 +29,7 @@ impl Drop for DropMe { } } -pub fn main() { +fn main() { block_on(async { let b = DropMe("hello"); let async_closure = async move |a: DropMe| { diff --git a/src/tools/miri/tests/pass/async-closure.rs b/src/tools/miri/tests/pass/async-closure.rs index 4c0fb356f9db..1b38f06eb7cd 100644 --- a/src/tools/miri/tests/pass/async-closure.rs +++ b/src/tools/miri/tests/pass/async-closure.rs @@ -6,7 +6,7 @@ use std::ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}; use std::pin::pin; use std::task::*; -pub fn block_on(fut: impl Future) -> T { +fn block_on(fut: impl Future) -> T { let mut fut = pin!(fut); let ctx = &mut Context::from_waker(Waker::noop()); @@ -38,7 +38,7 @@ async fn call_normal_mut>(f: &mut impl FnMut(i32) -> F) { f(1).await; } -pub fn main() { +fn main() { block_on(async { let b = 2i32; let mut async_closure = async move |a: i32| { diff --git a/src/tools/miri/tests/pass/binops.rs b/src/tools/miri/tests/pass/binops.rs index fcbe6c85b7b8..fa8993c34219 100644 --- a/src/tools/miri/tests/pass/binops.rs +++ b/src/tools/miri/tests/pass/binops.rs @@ -71,7 +71,7 @@ fn test_class() { assert!(q != r); } -pub fn main() { +fn main() { test_nil(); test_bool(); test_ptr(); diff --git a/src/tools/miri/tests/pass/btreemap.rs b/src/tools/miri/tests/pass/btreemap.rs index 7af6d7b5551c..cfd01ce28719 100644 --- a/src/tools/miri/tests/pass/btreemap.rs +++ b/src/tools/miri/tests/pass/btreemap.rs @@ -25,7 +25,7 @@ fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator } } -pub fn main() { +fn main() { let mut b = BTreeSet::new(); b.insert(Foo::A("\'")); b.insert(Foo::A("/=")); diff --git a/src/tools/miri/tests/pass/calls.rs b/src/tools/miri/tests/pass/calls.rs index 8db3d3590cc1..4f7b27c41c5c 100644 --- a/src/tools/miri/tests/pass/calls.rs +++ b/src/tools/miri/tests/pass/calls.rs @@ -35,7 +35,7 @@ fn const_fn_call() -> i64 { } fn call_return_into_passed_reference() { - pub fn func(v: &mut T, f: fn(&T) -> T) { + fn func(v: &mut T, f: fn(&T) -> T) { // MIR building will introduce a temporary, so this becomes // `let temp = f(v); *v = temp;`. // If this got optimized to `*v = f(v)` on the MIR level we'd have UB diff --git a/src/tools/miri/tests/pass/concurrency/data_race.rs b/src/tools/miri/tests/pass/concurrency/data_race.rs index d5dd1deb2d9d..9c434e433d80 100644 --- a/src/tools/miri/tests/pass/concurrency/data_race.rs +++ b/src/tools/miri/tests/pass/concurrency/data_race.rs @@ -57,7 +57,7 @@ fn test_multiple_reads() { assert_eq!(var, 10); } -pub fn test_rmw_no_block() { +fn test_rmw_no_block() { static SYNC: AtomicUsize = AtomicUsize::new(0); let mut a = 0u32; @@ -89,7 +89,7 @@ pub fn test_rmw_no_block() { } } -pub fn test_simple_release() { +fn test_simple_release() { static SYNC: AtomicUsize = AtomicUsize::new(0); let mut a = 0u32; @@ -214,7 +214,7 @@ fn failing_rmw_is_read() { }); } -pub fn main() { +fn main() { test_fence_sync(); test_multiple_reads(); test_rmw_no_block(); diff --git a/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs index ecc4ca59bd18..26c63161b55b 100644 --- a/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs +++ b/src/tools/miri/tests/pass/concurrency/disable_data_race_detector.rs @@ -10,7 +10,7 @@ struct EvilSend(pub T); unsafe impl Send for EvilSend {} unsafe impl Sync for EvilSend {} -pub fn main() { +fn main() { let mut a = 0u32; let b = &mut a as *mut u32; let c = EvilSend(b); diff --git a/src/tools/miri/tests/pass/const-vec-of-fns.rs b/src/tools/miri/tests/pass/const-vec-of-fns.rs index 7f0782fe3224..4bed4bb849e2 100644 --- a/src/tools/miri/tests/pass/const-vec-of-fns.rs +++ b/src/tools/miri/tests/pass/const-vec-of-fns.rs @@ -8,7 +8,7 @@ fn f() {} static mut CLOSURES: &'static mut [fn()] = &mut [f as fn(), f as fn()]; -pub fn main() { +fn main() { unsafe { for closure in &mut *CLOSURES { (*closure)() diff --git a/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs b/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs index 2f8665e8d62c..9c4121baa637 100644 --- a/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs +++ b/src/tools/miri/tests/pass/drop_type_without_drop_glue.rs @@ -15,7 +15,7 @@ fn drop_in_place_with_terminator(ptr: *mut i32) { } } -pub fn main() { +fn main() { drop_in_place_with_terminator(std::ptr::without_provenance_mut(0)); drop_in_place_with_terminator(std::ptr::without_provenance_mut(1)); } diff --git a/src/tools/miri/tests/pass/dst-raw.rs b/src/tools/miri/tests/pass/dst-raw.rs index 3d0b843b3da2..7ffef3320e02 100644 --- a/src/tools/miri/tests/pass/dst-raw.rs +++ b/src/tools/miri/tests/pass/dst-raw.rs @@ -19,7 +19,7 @@ struct Foo { f: T, } -pub fn main() { +fn main() { // raw trait object let x = A { f: 42 }; let z: *const dyn Trait = &x; diff --git a/src/tools/miri/tests/pass/dst-struct-sole.rs b/src/tools/miri/tests/pass/dst-struct-sole.rs index 4b25fbb06300..931601bcd954 100644 --- a/src/tools/miri/tests/pass/dst-struct-sole.rs +++ b/src/tools/miri/tests/pass/dst-struct-sole.rs @@ -33,7 +33,7 @@ impl ToBar for Bar { } } -pub fn main() { +fn main() { // With a vec of ints. let f1 = Fat { ptr: [1, 2, 3] }; foo(&f1); diff --git a/src/tools/miri/tests/pass/dst-struct.rs b/src/tools/miri/tests/pass/dst-struct.rs index 59763bbbfdd3..cf92ebfa0445 100644 --- a/src/tools/miri/tests/pass/dst-struct.rs +++ b/src/tools/miri/tests/pass/dst-struct.rs @@ -48,7 +48,7 @@ impl ToBar for Bar { } } -pub fn main() { +fn main() { // With a vec of ints. let f1: Fat<[isize; 3]> = Fat { f1: 5, f2: "some str", ptr: [1, 2, 3] }; foo(&f1); diff --git a/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs index 86f30f42b629..8a320fcdb4d4 100644 --- a/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs +++ b/src/tools/miri/tests/pass/enum-nullable-const-null-with-fields.rs @@ -3,6 +3,6 @@ static C: Result<(), Box> = Ok(()); // This is because of yet another bad assertion (ICE) about the null side of a nullable enum. // So we won't actually compile if the bug is present, but we check the value in main anyway. -pub fn main() { +fn main() { assert!(C.is_ok()); } diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 67a14c2b3895..7b23518d73da 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -1037,7 +1037,7 @@ fn mul_add() { assert_eq!(f.to_bits(), f32::to_bits(-0.0)); } -pub fn libm() { +fn libm() { fn ldexp(a: f64, b: i32) -> f64 { extern "C" { fn ldexp(x: f64, n: i32) -> f64; @@ -1300,7 +1300,7 @@ fn test_fast() { use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast}; #[inline(never)] - pub fn test_operations_f16(a: f16, b: f16) { + fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1312,7 +1312,7 @@ fn test_fast() { } #[inline(never)] - pub fn test_operations_f32(a: f32, b: f32) { + fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1324,7 +1324,7 @@ fn test_fast() { } #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64) { + fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1336,7 +1336,7 @@ fn test_fast() { } #[inline(never)] - pub fn test_operations_f128(a: f128, b: f128) { + fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation unsafe { assert_approx_eq!(fadd_fast(a, b), a + b); @@ -1363,7 +1363,7 @@ fn test_algebraic() { }; #[inline(never)] - pub fn test_operations_f16(a: f16, b: f16) { + fn test_operations_f16(a: f16, b: f16) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1373,7 +1373,7 @@ fn test_algebraic() { } #[inline(never)] - pub fn test_operations_f32(a: f32, b: f32) { + fn test_operations_f32(a: f32, b: f32) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1383,7 +1383,7 @@ fn test_algebraic() { } #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64) { + fn test_operations_f64(a: f64, b: f64) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1393,7 +1393,7 @@ fn test_algebraic() { } #[inline(never)] - pub fn test_operations_f128(a: f128, b: f128) { + fn test_operations_f128(a: f128, b: f128) { // make sure they all map to the correct operation assert_approx_eq!(fadd_algebraic(a, b), a + b); assert_approx_eq!(fsub_algebraic(a, b), a - b); @@ -1418,12 +1418,12 @@ fn test_fmuladd() { // FIXME(f16_f128): add when supported #[inline(never)] - pub fn test_operations_f32(a: f32, b: f32, c: f32) { + fn test_operations_f32(a: f32, b: f32, c: f32) { assert_approx_eq!(fmuladdf32(a, b, c), a * b + c); } #[inline(never)] - pub fn test_operations_f64(a: f64, b: f64, c: f64) { + fn test_operations_f64(a: f64, b: f64, c: f64) { assert_approx_eq!(fmuladdf64(a, b, c), a * b + c); } @@ -1468,10 +1468,10 @@ fn test_non_determinism() { }; } - pub fn test_operations_f16(a: f16, b: f16) { + fn test_operations_f16(a: f16, b: f16) { test_operations_f!(a, b); } - pub fn test_operations_f32(a: f32, b: f32) { + fn test_operations_f32(a: f32, b: f32) { test_operations_f!(a, b); check_nondet(|| a.powf(b)); check_nondet(|| a.powi(2)); @@ -1507,7 +1507,7 @@ fn test_non_determinism() { check_nondet(|| 5.0f32.erf()); check_nondet(|| 5.0f32.erfc()); } - pub fn test_operations_f64(a: f64, b: f64) { + fn test_operations_f64(a: f64, b: f64) { test_operations_f!(a, b); check_nondet(|| a.powf(b)); check_nondet(|| a.powi(2)); @@ -1538,7 +1538,7 @@ fn test_non_determinism() { check_nondet(|| 5.0f64.erf()); check_nondet(|| 5.0f64.erfc()); } - pub fn test_operations_f128(a: f128, b: f128) { + fn test_operations_f128(a: f128, b: f128) { test_operations_f!(a, b); } diff --git a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs index 04a55d7007ce..6eee5f21e1bb 100644 --- a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs +++ b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs @@ -5,7 +5,7 @@ use std::intrinsics::mir::*; // Make sure calls with the return place "on the heap" work. #[custom_mir(dialect = "runtime", phase = "optimized")] -pub fn main() { +fn main() { mir! { { let x = 0; diff --git a/src/tools/miri/tests/pass/integer-ops.rs b/src/tools/miri/tests/pass/integer-ops.rs index 3f8ac34e7d10..1792d16734fd 100644 --- a/src/tools/miri/tests/pass/integer-ops.rs +++ b/src/tools/miri/tests/pass/integer-ops.rs @@ -56,7 +56,7 @@ fn basic() { assert_eq!(match_int_range(), 4); } -pub fn main() { +fn main() { basic(); // This tests that we do (not) do sign extension properly when loading integers diff --git a/src/tools/miri/tests/pass/intrinsics/integer.rs b/src/tools/miri/tests/pass/intrinsics/integer.rs index 8727b6d3c87e..a67c52f7b420 100644 --- a/src/tools/miri/tests/pass/intrinsics/integer.rs +++ b/src/tools/miri/tests/pass/intrinsics/integer.rs @@ -4,7 +4,7 @@ #![feature(core_intrinsics, funnel_shifts)] use std::intrinsics::*; -pub fn main() { +fn main() { unsafe { [assert_eq!(ctpop(0u8), 0), assert_eq!(ctpop(0i8), 0)]; [assert_eq!(ctpop(0u16), 0), assert_eq!(ctpop(0i16), 0)]; diff --git a/src/tools/miri/tests/pass/intrinsics/volatile.rs b/src/tools/miri/tests/pass/intrinsics/volatile.rs index c9799801455c..b72020cb83d2 100644 --- a/src/tools/miri/tests/pass/intrinsics/volatile.rs +++ b/src/tools/miri/tests/pass/intrinsics/volatile.rs @@ -2,7 +2,7 @@ #![feature(core_intrinsics)] use std::intrinsics::{volatile_load, volatile_store}; -pub fn main() { +fn main() { unsafe { let i: &mut (isize, isize) = &mut (0, 0); volatile_store(i, (1, 2)); diff --git a/src/tools/miri/tests/pass/issues/issue-30530.rs b/src/tools/miri/tests/pass/issues/issue-30530.rs index b50a43ffd83b..af338e8032d1 100644 --- a/src/tools/miri/tests/pass/issues/issue-30530.rs +++ b/src/tools/miri/tests/pass/issues/issue-30530.rs @@ -21,7 +21,7 @@ fn main() { } #[inline(never)] -pub fn take(h: Handler, f: Box) -> Box { +fn take(h: Handler, f: Box) -> Box { unsafe { match h { Handler::Custom(ptr) => *Box::from_raw(ptr), diff --git a/src/tools/miri/tests/pass/issues/issue-3794.rs b/src/tools/miri/tests/pass/issues/issue-3794.rs index 860d72bb586f..3cc78b5f99d7 100644 --- a/src/tools/miri/tests/pass/issues/issue-3794.rs +++ b/src/tools/miri/tests/pass/issues/issue-3794.rs @@ -22,7 +22,7 @@ fn print_s(s: &S) { s.print(); } -pub fn main() { +fn main() { let s: Box = Box::new(S { s: 5 }); print_s(&*s); let t: Box = s as Box; diff --git a/src/tools/miri/tests/pass/issues/issue-5917.rs b/src/tools/miri/tests/pass/issues/issue-5917.rs index f7bbb4350e2b..9155c859b050 100644 --- a/src/tools/miri/tests/pass/issues/issue-5917.rs +++ b/src/tools/miri/tests/pass/issues/issue-5917.rs @@ -1,6 +1,6 @@ struct T(&'static [isize]); static STATIC: T = T(&[5, 4, 3]); -pub fn main() { +fn main() { let T(ref v) = STATIC; assert_eq!(v[0], 5); } diff --git a/src/tools/miri/tests/pass/issues/issue-miri-184.rs b/src/tools/miri/tests/pass/issues/issue-miri-184.rs index 964d850298fb..5233b441b990 100644 --- a/src/tools/miri/tests/pass/issues/issue-miri-184.rs +++ b/src/tools/miri/tests/pass/issues/issue-miri-184.rs @@ -1,5 +1,5 @@ #![allow(unnecessary_transmutes)] -pub fn main() { +fn main() { let bytes: [u8; 8] = unsafe { ::std::mem::transmute(0u64) }; let _val: &[u8] = &bytes; } diff --git a/src/tools/miri/tests/pass/issues/issue-miri-2068.rs b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs index 1931b6c9d79f..471031e59ac3 100644 --- a/src/tools/miri/tests/pass/issues/issue-miri-2068.rs +++ b/src/tools/miri/tests/pass/issues/issue-miri-2068.rs @@ -2,7 +2,7 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll, Waker}; -pub fn fuzzing_block_on>(fut: F) -> O { +fn fuzzing_block_on>(fut: F) -> O { let mut fut = std::pin::pin!(fut); let mut context = Context::from_waker(Waker::noop()); loop { diff --git a/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs b/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs index 123fe6ed6425..d88df18295ab 100644 --- a/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs +++ b/src/tools/miri/tests/pass/issues/issue-miri-3541-dyn-vtable-trait-normalization.rs @@ -25,7 +25,7 @@ where } } -pub fn box_new_with() +fn box_new_with() where T: ?Sized, { diff --git a/src/tools/miri/tests/pass/last-use-in-cap-clause.rs b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs index 2160aea16346..d71593bfcbdf 100644 --- a/src/tools/miri/tests/pass/last-use-in-cap-clause.rs +++ b/src/tools/miri/tests/pass/last-use-in-cap-clause.rs @@ -12,6 +12,6 @@ fn foo() -> Box isize + 'static> { Box::new(result) } -pub fn main() { +fn main() { assert_eq!(foo()(), 22); } diff --git a/src/tools/miri/tests/pass/loop-break-value.rs b/src/tools/miri/tests/pass/loop-break-value.rs index bc4c967d26a5..74ab487b342a 100644 --- a/src/tools/miri/tests/pass/loop-break-value.rs +++ b/src/tools/miri/tests/pass/loop-break-value.rs @@ -8,7 +8,7 @@ fn never_returns() { } } -pub fn main() { +fn main() { let value = 'outer: loop { if 1 == 1 { break 13; diff --git a/src/tools/miri/tests/pass/move-arg-2-unique.rs b/src/tools/miri/tests/pass/move-arg-2-unique.rs index de21d67eb4f6..7792b8bb1162 100644 --- a/src/tools/miri/tests/pass/move-arg-2-unique.rs +++ b/src/tools/miri/tests/pass/move-arg-2-unique.rs @@ -2,7 +2,7 @@ fn test(foo: Box>) { assert_eq!((*foo)[0], 10); } -pub fn main() { +fn main() { let x = Box::new(vec![10]); // Test forgetting a local by move-in test(x); diff --git a/src/tools/miri/tests/pass/move-arg-3-unique.rs b/src/tools/miri/tests/pass/move-arg-3-unique.rs index 6025481c32e2..1b6e7ba7cf32 100644 --- a/src/tools/miri/tests/pass/move-arg-3-unique.rs +++ b/src/tools/miri/tests/pass/move-arg-3-unique.rs @@ -1,4 +1,4 @@ -pub fn main() { +fn main() { let x = Box::new(10); let y = x; assert_eq!(*y, 10); diff --git a/src/tools/miri/tests/pass/mpsc.rs b/src/tools/miri/tests/pass/mpsc.rs index 3824a0de907c..b60d32002db2 100644 --- a/src/tools/miri/tests/pass/mpsc.rs +++ b/src/tools/miri/tests/pass/mpsc.rs @@ -1,6 +1,6 @@ use std::sync::mpsc::channel; -pub fn main() { +fn main() { let (tx, rx) = channel::>(); tx.send(Box::new(100)).unwrap(); let v = rx.recv().unwrap(); diff --git a/src/tools/miri/tests/pass/panic/unwind_dwarf.rs b/src/tools/miri/tests/pass/panic/unwind_dwarf.rs index ca90e4f4d94f..1e0cd3a43a2f 100644 --- a/src/tools/miri/tests/pass/panic/unwind_dwarf.rs +++ b/src/tools/miri/tests/pass/panic/unwind_dwarf.rs @@ -15,7 +15,7 @@ struct Exception { cause: Box, } -pub fn panic(data: Box) -> u32 { +fn panic(data: Box) -> u32 { extern "C" fn exception_cleanup( _unwind_code: uw::_Unwind_Reason_Code, _exception: *mut uw::_Unwind_Exception, @@ -53,7 +53,7 @@ fn miri_exception_class() -> uw::_Unwind_Exception_Class { 0x4d4f5a_00_4d495249 } -pub fn catch_unwind R>(f: F) -> Result> { +fn catch_unwind R>(f: F) -> Result> { struct Data { f: Option, r: Option, diff --git a/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs index 445dd43febb1..8645fde77f01 100644 --- a/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs +++ b/src/tools/miri/tests/pass/regions-lifetime-nonfree-late-bound.rs @@ -12,7 +12,7 @@ // doing region-folding, when really all clients of the region-folding // case only want to see *free* lifetime variables, not bound ones. -pub fn main() { +fn main() { fn explicit() { fn test(_x: Option>) where diff --git a/src/tools/miri/tests/pass/sendable-class.rs b/src/tools/miri/tests/pass/sendable-class.rs index a05278f1855a..5784bc3a3931 100644 --- a/src/tools/miri/tests/pass/sendable-class.rs +++ b/src/tools/miri/tests/pass/sendable-class.rs @@ -12,7 +12,7 @@ fn foo(i: isize, j: char) -> Foo { Foo { i: i, j: j } } -pub fn main() { +fn main() { let (tx, rx) = channel(); tx.send(foo(42, 'c')).unwrap(); let _val = rx; diff --git a/src/tools/miri/tests/pass/tag-align-dyn-u64.rs b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs index 81a43cc8bcc6..e4abc3895008 100644 --- a/src/tools/miri/tests/pass/tag-align-dyn-u64.rs +++ b/src/tools/miri/tests/pass/tag-align-dyn-u64.rs @@ -24,7 +24,7 @@ fn is_u64_aligned(u: &Tag) -> bool { return (p & (u64_align - 1)) == 0; } -pub fn main() { +fn main() { let x = mk_rec(); assert!(is_u64_aligned(&x.t)); } diff --git a/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs b/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs index abc0968f7c4c..634b8af02aa9 100644 --- a/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs +++ b/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs @@ -6,7 +6,7 @@ use std::cell::Cell; // as long as the program does), so make sure we treat them the same for leak purposes. // // The test covers both TLS statics and the TLS macro. -pub fn main() { +fn main() { #[thread_local] static TLS: Cell> = Cell::new(None); diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs index 4a868455c849..ed8cbbf0e273 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-box.rs @@ -6,7 +6,7 @@ mod utils; use std::cell::UnsafeCell; -pub fn main() { +fn main() { let cell = UnsafeCell::new(42); let box1 = Box::new(cell); diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs index fd68685a2f44..adeedc653b99 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs +++ b/src/tools/miri/tests/pass/tree_borrows/cell-inside-struct.rs @@ -12,7 +12,7 @@ struct Foo { field2: Cell, } -pub fn main() { +fn main() { let root = Foo { field1: 42, field2: Cell::new(88) }; unsafe { let a = &root; diff --git a/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs b/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs index 23250d6e6dfc..4edf80e9561e 100644 --- a/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs +++ b/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs @@ -2,7 +2,7 @@ // copy_nonoverlapping works regardless of the order in which we construct // the arguments. -pub fn main() { +fn main() { test_to_from(); test_from_to(); } diff --git a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs index 87eb447049d6..0ed1dbacc707 100644 --- a/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs +++ b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs @@ -28,7 +28,7 @@ fn local_addr_of_mut() { // Tree Borrows has no issue with several mutable references existing // at the same time, as long as they are used only immutably. // I.e. multiple Reserved can coexist. -pub fn aliasing_read_only_mutable_refs() { +fn aliasing_read_only_mutable_refs() { unsafe { let base = &mut 42u64; let r1 = &mut *(base as *mut u64); @@ -38,7 +38,7 @@ pub fn aliasing_read_only_mutable_refs() { } } -pub fn string_as_mut_ptr() { +fn string_as_mut_ptr() { // This errors in Stacked Borrows since as_mut_ptr restricts the provenance, // but with Tree Borrows it should work. unsafe { diff --git a/src/tools/miri/tests/pass/unsized.rs b/src/tools/miri/tests/pass/unsized.rs index 6ad063543029..1e62cd7f3239 100644 --- a/src/tools/miri/tests/pass/unsized.rs +++ b/src/tools/miri/tests/pass/unsized.rs @@ -4,10 +4,10 @@ #![feature(custom_mir, core_intrinsics)] fn unsized_params() { - pub fn f0(_f: dyn FnOnce()) {} - pub fn f1(_s: str) {} - pub fn f2(_x: i32, _y: [i32]) {} - pub fn f3(_p: dyn Send) {} + fn f0(_f: dyn FnOnce()) {} + fn f1(_s: str) {} + fn f2(_x: i32, _y: [i32]) {} + fn f3(_p: dyn Send) {} let c: Box = Box::new(|| {}); f0(*c); diff --git a/src/tools/miri/tests/pass/vec-matching-fold.rs b/src/tools/miri/tests/pass/vec-matching-fold.rs index 3a869703bf96..48e750a3102a 100644 --- a/src/tools/miri/tests/pass/vec-matching-fold.rs +++ b/src/tools/miri/tests/pass/vec-matching-fold.rs @@ -29,7 +29,7 @@ where } } -pub fn main() { +fn main() { let x = &[1, 2, 3, 4, 5]; let product = foldl(x, 1, |a, b| a * *b); diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs index efaaf9fc8417..1f8d98a4d339 100644 --- a/src/tools/miri/tests/ui.rs +++ b/src/tools/miri/tests/ui.rs @@ -30,11 +30,6 @@ fn miri_path() -> PathBuf { PathBuf::from(env::var("MIRI").unwrap_or_else(|_| env!("CARGO_BIN_EXE_miri").into())) } -pub fn flagsplit(flags: &str) -> Vec { - // This code is taken from `RUSTFLAGS` handling in cargo. - flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect() -} - // Build the shared object file for testing native function calls. fn build_native_lib(target: &str) -> PathBuf { // Loosely follow the logic of the `cc` crate for finding the compiler. From 1e382a172fc08e03ac2330e9a2932f2d0fc185d1 Mon Sep 17 00:00:00 2001 From: Xiangfei Ding Date: Sun, 12 Oct 2025 18:29:46 +0000 Subject: [PATCH 29/35] move EnvFilter into its own layer `tracing` at the time of writing has a feature (?) in its Filter implementation, so that filters like EnvFilter are consulted for status of a span or event and whether it is marked as interesting for logging. Combining a Filter with another layer through the `with_filter` combinator produces a filtered layer that enables an event unless it is statically determined that the event is uninteresting. However, if the filter is dynamic, because of filtering on span names or field values as an example, events are **always** enabled. There is an `event_enabled` predicate on `EnvFilter` implementation but it falls back to default and, thus, the dynamic filters are **unused**. This patch re-enables span- and field-based filters. --- compiler/rustc_log/src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_log/src/lib.rs b/compiler/rustc_log/src/lib.rs index 26475eec1c10..8e72022671ce 100644 --- a/compiler/rustc_log/src/lib.rs +++ b/compiler/rustc_log/src/lib.rs @@ -39,11 +39,11 @@ use std::io::{self, IsTerminal}; use tracing::dispatcher::SetGlobalDefaultError; use tracing::{Event, Subscriber}; +use tracing_subscriber::Registry; use tracing_subscriber::filter::{Directive, EnvFilter, LevelFilter}; use tracing_subscriber::fmt::FmtContext; use tracing_subscriber::fmt::format::{self, FormatEvent, FormatFields}; use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::{Layer, Registry}; /// The values of all the environment variables that matter for configuring a logger. /// Errors are explicitly preserved so that we can share error handling. @@ -155,18 +155,19 @@ where Err(_) => {} // no wraptree } - let subscriber = build_subscriber().with(layer.with_filter(filter)); + let subscriber = build_subscriber(); + // NOTE: It is important to make sure that the filter is applied on the last layer match cfg.backtrace { Ok(backtrace_target) => { let fmt_layer = tracing_subscriber::fmt::layer() .with_writer(io::stderr) .without_time() .event_format(BacktraceFormatter { backtrace_target }); - let subscriber = subscriber.with(fmt_layer); + let subscriber = subscriber.with(layer).with(fmt_layer).with(filter); tracing::subscriber::set_global_default(subscriber)?; } Err(_) => { - tracing::subscriber::set_global_default(subscriber)?; + tracing::subscriber::set_global_default(subscriber.with(layer).with(filter))?; } }; From 09ef5eb8af5cdc128ba003b8a9d6e91407e294c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sun, 12 Oct 2025 22:13:47 +0200 Subject: [PATCH 30/35] reduce calls to attr.span() in old doc attr parsing --- compiler/rustc_passes/src/check_attr.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index cba6243fa109..c4ceba0840f1 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -296,6 +296,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { [sym::thread_local, ..] => self.check_thread_local(attr, span, target), [sym::doc, ..] => self.check_doc_attrs( attr, + attr.span(), attr_item.style, hir_id, target, @@ -1089,18 +1090,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// Checks that an attribute is used at the crate level. Returns `true` if valid. fn check_attr_crate_level( &self, - attr: &Attribute, + attr_span: Span, style: AttrStyle, meta: &MetaItemInner, hir_id: HirId, ) -> bool { if hir_id != CRATE_HIR_ID { // insert a bang between `#` and `[...` - let bang_span = attr.span().lo() + BytePos(1); + let bang_span = attr_span.lo() + BytePos(1); let sugg = (style == AttrStyle::Outer && self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID) .then_some(errors::AttrCrateLevelOnlySugg { - attr: attr.span().with_lo(bang_span).with_hi(bang_span), + attr: attr_span.with_lo(bang_span).with_hi(bang_span), }); self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, @@ -1116,7 +1117,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place. fn check_test_attr( &self, - attr: &Attribute, + attr_span: Span, style: AttrStyle, meta: &MetaItemInner, hir_id: HirId, @@ -1128,7 +1129,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // Allowed everywhere like `#[doc]` } (Some(sym::no_crate_inject), _) => { - self.check_attr_crate_level(attr, style, meta, hir_id); + self.check_attr_crate_level(attr_span, style, meta, hir_id); } (_, Some(m)) => { self.tcx.emit_node_span_lint( @@ -1225,6 +1226,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { fn check_doc_attrs( &self, attr: &Attribute, + attr_span: Span, style: AttrStyle, hir_id: HirId, target: Target, @@ -1274,7 +1276,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } Some(sym::test) => { - self.check_test_attr(attr, style, meta, hir_id); + self.check_test_attr(attr_span, style, meta, hir_id); } Some( @@ -1285,7 +1287,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::html_root_url | sym::html_no_source, ) => { - self.check_attr_crate_level(attr, style, meta, hir_id); + self.check_attr_crate_level(attr_span, style, meta, hir_id); } Some(sym::auto_cfg) => { @@ -1301,7 +1303,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Some(sym::cfg | sym::hidden | sym::notable_trait) => {} Some(sym::rust_logo) => { - if self.check_attr_crate_level(attr, style, meta, hir_id) + if self.check_attr_crate_level(attr_span, style, meta, hir_id) && !self.tcx.features().rustdoc_internals() { feature_err( From 18a468ed61cc6c3b5393f755ed5a1a71255d4b4a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 13 Oct 2025 08:50:43 +0200 Subject: [PATCH 31/35] native-lib args: also reject wide pointers --- src/tools/miri/src/shims/native_lib/mod.rs | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/tools/miri/src/shims/native_lib/mod.rs b/src/tools/miri/src/shims/native_lib/mod.rs index 914c666adb30..47102c30bc3b 100644 --- a/src/tools/miri/src/shims/native_lib/mod.rs +++ b/src/tools/miri/src/shims/native_lib/mod.rs @@ -6,7 +6,8 @@ use std::sync::atomic::AtomicBool; use libffi::low::CodePtr; use libffi::middle::Type as FfiType; use rustc_abi::{HasDataLayout, Size}; -use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy}; +use rustc_middle::ty::layout::HasTypingEnv; +use rustc_middle::ty::{self, IntTy, Ty, UintTy}; use rustc_span::Symbol; use serde::{Deserialize, Serialize}; @@ -373,15 +374,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { adt_def: ty::AdtDef<'tcx>, args: &'tcx ty::List>, ) -> InterpResult<'tcx, FfiType> { - // TODO: Certain non-C reprs should be okay also. - if !adt_def.repr().c() { - throw_unsup_format!("passing a non-#[repr(C)] struct over FFI: {orig_ty}") - } // TODO: unions, etc. if !adt_def.is_struct() { - throw_unsup_format!( - "unsupported argument type for native call: {orig_ty} is an enum or union" - ); + throw_unsup_format!("passing an enum or union over FFI: {orig_ty}"); + } + // TODO: Certain non-C reprs should be okay also. + if !adt_def.repr().c() { + throw_unsup_format!("passing a non-#[repr(C)] {} over FFI: {orig_ty}", adt_def.descr()) } let this = self.eval_context_ref(); @@ -395,19 +394,24 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { /// Gets the matching libffi type for a given Ty. fn ty_to_ffitype(&self, ty: Ty<'tcx>) -> InterpResult<'tcx, FfiType> { + let this = self.eval_context_ref(); interp_ok(match ty.kind() { ty::Int(IntTy::I8) => FfiType::i8(), ty::Int(IntTy::I16) => FfiType::i16(), ty::Int(IntTy::I32) => FfiType::i32(), ty::Int(IntTy::I64) => FfiType::i64(), ty::Int(IntTy::Isize) => FfiType::isize(), - // the uints ty::Uint(UintTy::U8) => FfiType::u8(), ty::Uint(UintTy::U16) => FfiType::u16(), ty::Uint(UintTy::U32) => FfiType::u32(), ty::Uint(UintTy::U64) => FfiType::u64(), ty::Uint(UintTy::Usize) => FfiType::usize(), - ty::RawPtr(..) => FfiType::pointer(), + ty::RawPtr(pointee_ty, _mut) => { + if !pointee_ty.is_sized(*this.tcx, this.typing_env()) { + throw_unsup_format!("passing a pointer to an unsized type over FFI: {}", ty); + } + FfiType::pointer() + } ty::Adt(adt_def, args) => self.adt_to_ffitype(ty, *adt_def, args)?, _ => throw_unsup_format!("unsupported argument type for native call: {}", ty), }) From 1ef88639e76a548c8c1e49d8cde19e0446a3297c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 13 Oct 2025 10:02:03 +0200 Subject: [PATCH 32/35] Prepare for merging from rust-lang/rust This updates the rust-version file to 36e4f5d1fe1d63953a5bf1758ce2b64172623e2e. --- 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 692cbc0a56bc..f877706520ea 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -4fd31815524baba0bf368f151f757101f432e3de +36e4f5d1fe1d63953a5bf1758ce2b64172623e2e From aec62f83bd9bfda37287a5a40a87b1c43f4695e7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 13 Oct 2025 10:36:03 +0200 Subject: [PATCH 33/35] fmt --- src/tools/miri/src/intrinsics/simd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 5f75657e0a22..1e7366b5a826 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -1,7 +1,7 @@ use rand::Rng; use rustc_apfloat::Float; -use rustc_middle::ty::FloatTy; use rustc_middle::ty; +use rustc_middle::ty::FloatTy; use super::check_intrinsic_arg_count; use crate::helpers::{ToHost, ToSoft}; @@ -79,7 +79,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } FloatTy::F128 => unimplemented!("f16_f128"), }; - + this.write_scalar(val, &dest)?; } } From 0c2e30bc1a5030095686c97650482553698e02b2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 13 Oct 2025 10:44:31 +0200 Subject: [PATCH 34/35] avoid blanket allow(unused) --- .../miri/tests/fail/function_calls/arg_inplace_locals_alias.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs index 9dad85b0b062..b6cda6007536 100644 --- a/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_locals_alias.rs @@ -6,7 +6,6 @@ //@compile-flags: -Zmiri-disable-validation #![feature(custom_mir, core_intrinsics)] -#![allow(unused)] use std::intrinsics::mir::*; @@ -31,6 +30,7 @@ fn main() { } } +#[expect(unused_variables, unused_assignments)] fn callee(x: S, mut y: S) { // With the setup above, if `x` and `y` are both moved, // then writing to `y` will change the value stored in `x`! From 30bedc74d4812d8618fb31dbf002f44346979fd6 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 10 Oct 2025 19:56:57 +0100 Subject: [PATCH 35/35] in opaque type handling lift region vars to static if they outlive placeholders --- .../rustc_borrowck/src/handle_placeholders.rs | 2 +- .../opaque_types/member_constraints.rs | 3 +- .../src/region_infer/opaque_types/mod.rs | 7 + .../region_infer/opaque_types/region_ctxt.rs | 36 +++++- .../placeholders_lift_to_static.rs | 33 +++++ tests/ui/impl-trait/nested-rpit-hrtb.rs | 1 - tests/ui/impl-trait/nested-rpit-hrtb.stderr | 14 +- tests/ui/nll/ice-106874.stderr | 120 +++++++++--------- 8 files changed, 137 insertions(+), 79 deletions(-) create mode 100644 tests/ui/impl-trait/member-constraints/placeholders_lift_to_static.rs diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs index 6be90994015f..d23ecf6c7079 100644 --- a/compiler/rustc_borrowck/src/handle_placeholders.rs +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -344,7 +344,7 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( } } -fn rewrite_placeholder_outlives<'tcx>( +pub(crate) fn rewrite_placeholder_outlives<'tcx>( sccs: &Sccs, annotations: &SccAnnotations<'_, '_, RegionTracker>, fr_static: RegionVid, diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs index 667fc440ac00..a2e2b61ae2d3 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs @@ -39,7 +39,7 @@ pub(super) fn apply_member_constraints<'tcx>( debug!(?member_constraints); for scc_a in rcx.constraint_sccs.all_sccs() { debug!(?scc_a); - // Start by adding the region values required by outlives constraints. This + // Start by adding the region values required by outlives constraints. This // matches how we compute the final region values in `fn compute_regions`. // // We need to do this here to get a lower bound when applying member constraints. @@ -64,6 +64,7 @@ fn apply_member_constraint<'tcx>( // If the member region lives in a higher universe, we currently choose // the most conservative option by leaving it unchanged. if !rcx.max_placeholder_universe_reached(member).is_root() { + debug!("member region reached non root universe, bailing"); return; } diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs index 8d89f3e0d870..0f9787927538 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -253,6 +253,10 @@ fn collect_defining_uses<'tcx>( } } else { errors.push(DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err)); + debug!( + "collect_defining_uses: InvalidOpaqueTypeArgs for {:?} := {:?}", + non_nll_opaque_type_key, hidden_type + ); } continue; } @@ -276,6 +280,7 @@ fn collect_defining_uses<'tcx>( defining_uses } +#[instrument(level = "debug", skip(rcx, hidden_types, defining_uses, errors))] fn compute_definition_site_hidden_types_from_defining_uses<'tcx>( rcx: &RegionCtxt<'_, 'tcx>, hidden_types: &mut DefinitionSiteHiddenTypes<'tcx>, @@ -287,6 +292,7 @@ fn compute_definition_site_hidden_types_from_defining_uses<'tcx>( let mut decls_modulo_regions: FxIndexMap, (OpaqueTypeKey<'tcx>, Span)> = FxIndexMap::default(); for &DefiningUse { opaque_type_key, ref arg_regions, hidden_type } in defining_uses { + debug!(?opaque_type_key, ?arg_regions, ?hidden_type); // After applying member constraints, we now map all regions in the hidden type // to the `arg_regions` of this defining use. In case a region in the hidden type // ended up not being equal to any such region, we error. @@ -294,6 +300,7 @@ fn compute_definition_site_hidden_types_from_defining_uses<'tcx>( match hidden_type.try_fold_with(&mut ToArgRegionsFolder::new(rcx, arg_regions)) { Ok(hidden_type) => hidden_type, Err(r) => { + debug!("UnexpectedHiddenRegion: {:?}", r); errors.push(DeferredOpaqueTypeError::UnexpectedHiddenRegion { hidden_type, opaque_type_key, diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs index 88326e4eebfc..90b15cbdd2cc 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs @@ -11,7 +11,9 @@ use crate::constraints::ConstraintSccIndex; use crate::handle_placeholders::{SccAnnotations, region_definitions}; use crate::region_infer::reverse_sccs::ReverseSccGraph; use crate::region_infer::values::RegionValues; -use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker, Representative}; +use crate::region_infer::{ + ConstraintSccs, OutlivesConstraintSet, RegionDefinition, RegionTracker, Representative, +}; use crate::type_check::MirTypeckRegionConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -39,16 +41,36 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> { location_map: Rc, constraints: &MirTypeckRegionConstraints<'tcx>, ) -> RegionCtxt<'a, 'tcx> { + let mut outlives_constraints = constraints.outlives_constraints.clone(); let universal_regions = &universal_region_relations.universal_regions; let (definitions, _has_placeholders) = region_definitions(infcx, universal_regions); + + let compute_sccs = + |outlives_constraints: &OutlivesConstraintSet<'tcx>, + annotations: &mut SccAnnotations<'_, 'tcx, RegionTracker>| { + ConstraintSccs::new_with_annotation( + &outlives_constraints + .graph(definitions.len()) + .region_graph(outlives_constraints, universal_regions.fr_static), + annotations, + ) + }; + let mut scc_annotations = SccAnnotations::init(&definitions); - let constraint_sccs = ConstraintSccs::new_with_annotation( - &constraints - .outlives_constraints - .graph(definitions.len()) - .region_graph(&constraints.outlives_constraints, universal_regions.fr_static), - &mut scc_annotations, + let mut constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations); + + let added_constraints = crate::handle_placeholders::rewrite_placeholder_outlives( + &constraint_sccs, + &scc_annotations, + universal_regions.fr_static, + &mut outlives_constraints, ); + + if added_constraints { + scc_annotations = SccAnnotations::init(&definitions); + constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations); + } + let scc_annotations = scc_annotations.scc_to_annotation; // Unlike the `RegionInferenceContext`, we only care about free regions diff --git a/tests/ui/impl-trait/member-constraints/placeholders_lift_to_static.rs b/tests/ui/impl-trait/member-constraints/placeholders_lift_to_static.rs new file mode 100644 index 000000000000..ff52b70c2528 --- /dev/null +++ b/tests/ui/impl-trait/member-constraints/placeholders_lift_to_static.rs @@ -0,0 +1,33 @@ +//@ check-pass + +// We have some `RPIT` with an item bound of `for<'a> Outlives<'a>`. We +// infer a hidden type of `&'?x i32` where `'?x` is required to outlive +// some placeholder `'!a` due to the `for<'a> Outlives<'a>` item bound. +// +// We previously did not write constraints of the form `'?x: '!a` into +// `'?x: 'static`. This caused member constraints to bail and not consider +// `'?x` to be constrained to an arg region. + +pub trait Outlives<'a> {} +impl<'a, T: 'a> Outlives<'a> for T {} + +pub fn foo() -> impl for<'a> Outlives<'a> { + let x: &'static i32 = &1; + x +} + +// This *didn't* regress but feels like it's "the same thing" so +// test it anyway +pub fn bar() -> impl Sized { + let x: &'static i32 = &1; + hr_outlives(x) +} + +fn hr_outlives(v: T) -> T +where + for<'a> T: 'a +{ + v +} + +fn main() {} diff --git a/tests/ui/impl-trait/nested-rpit-hrtb.rs b/tests/ui/impl-trait/nested-rpit-hrtb.rs index f4ff13d6c20f..11d79bcff737 100644 --- a/tests/ui/impl-trait/nested-rpit-hrtb.rs +++ b/tests/ui/impl-trait/nested-rpit-hrtb.rs @@ -48,7 +48,6 @@ fn one_hrtb_mention_fn_trait_param_uses<'b>() -> impl for<'a> Bar<'a, Assoc = im // This should resolve. fn one_hrtb_mention_fn_outlives_uses<'b>() -> impl for<'a> Bar<'a, Assoc = impl Sized + 'b> {} //~^ ERROR implementation of `Bar` is not general enough -//~| ERROR lifetime may not live long enough // This should resolve. fn two_htrb_trait_param() -> impl for<'a> Foo<'a, Assoc = impl for<'b> Qux<'b>> {} diff --git a/tests/ui/impl-trait/nested-rpit-hrtb.stderr b/tests/ui/impl-trait/nested-rpit-hrtb.stderr index 93cd7bd788f4..2e95ef370c7f 100644 --- a/tests/ui/impl-trait/nested-rpit-hrtb.stderr +++ b/tests/ui/impl-trait/nested-rpit-hrtb.stderr @@ -1,5 +1,5 @@ error[E0261]: use of undeclared lifetime name `'b` - --> $DIR/nested-rpit-hrtb.rs:57:77 + --> $DIR/nested-rpit-hrtb.rs:56:77 | LL | fn two_htrb_outlives() -> impl for<'a> Foo<'a, Assoc = impl for<'b> Sized + 'b> {} | ^^ undeclared lifetime @@ -15,7 +15,7 @@ LL | fn two_htrb_outlives<'b>() -> impl for<'a> Foo<'a, Assoc = impl for<'b> Siz | ++++ error[E0261]: use of undeclared lifetime name `'b` - --> $DIR/nested-rpit-hrtb.rs:65:82 + --> $DIR/nested-rpit-hrtb.rs:64:82 | LL | fn two_htrb_outlives_uses() -> impl for<'a> Bar<'a, Assoc = impl for<'b> Sized + 'b> {} | ^^ undeclared lifetime @@ -87,12 +87,6 @@ LL | fn one_hrtb_mention_fn_trait_param_uses<'b>() -> impl for<'a> Bar<'a, Assoc but trait `Qux<'_>` is implemented for `()` = help: for that trait implementation, expected `()`, found `&'a ()` -error: lifetime may not live long enough - --> $DIR/nested-rpit-hrtb.rs:49:93 - | -LL | fn one_hrtb_mention_fn_outlives_uses<'b>() -> impl for<'a> Bar<'a, Assoc = impl Sized + 'b> {} - | -- lifetime `'b` defined here ^^ opaque type requires that `'b` must outlive `'static` - error: implementation of `Bar` is not general enough --> $DIR/nested-rpit-hrtb.rs:49:93 | @@ -103,7 +97,7 @@ LL | fn one_hrtb_mention_fn_outlives_uses<'b>() -> impl for<'a> Bar<'a, Assoc = = note: ...but it actually implements `Bar<'0>`, for some specific lifetime `'0` error[E0277]: the trait bound `for<'a, 'b> &'a (): Qux<'b>` is not satisfied - --> $DIR/nested-rpit-hrtb.rs:61:64 + --> $DIR/nested-rpit-hrtb.rs:60:64 | LL | fn two_htrb_trait_param_uses() -> impl for<'a> Bar<'a, Assoc = impl for<'b> Qux<'b>> {} | ^^^^^^^^^^^^^^^^^^^^ the trait `for<'a, 'b> Qux<'b>` is not implemented for `&'a ()` @@ -112,7 +106,7 @@ LL | fn two_htrb_trait_param_uses() -> impl for<'a> Bar<'a, Assoc = impl for<'b> but trait `Qux<'_>` is implemented for `()` = help: for that trait implementation, expected `()`, found `&'a ()` -error: aborting due to 10 previous errors +error: aborting due to 9 previous errors Some errors have detailed explanations: E0261, E0277, E0657. For more information about an error, try `rustc --explain E0261`. diff --git a/tests/ui/nll/ice-106874.stderr b/tests/ui/nll/ice-106874.stderr index 0edbd7b44ef1..629570b602ed 100644 --- a/tests/ui/nll/ice-106874.stderr +++ b/tests/ui/nll/ice-106874.stderr @@ -17,75 +17,77 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: ...but it actually implements `FnOnce<(&'1 mut V,)>`, for some specific lifetime `'1` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error: implementation of `FnOnce` is not general enough - --> $DIR/ice-106874.rs:8:7 +error: higher-ranked subtype error + --> $DIR/ice-106874.rs:8:5 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` - -error: implementation of `Fn` is not general enough - --> $DIR/ice-106874.rs:8:7 - | -LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` - -error: implementation of `FnOnce` is not general enough - --> $DIR/ice-106874.rs:8:7 - | -LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: implementation of `Fn` is not general enough - --> $DIR/ice-106874.rs:8:7 - | -LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: implementation of `FnOnce` is not general enough - --> $DIR/ice-106874.rs:8:7 - | -LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: implementation of `Fn` is not general enough - --> $DIR/ice-106874.rs:8:7 - | -LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: higher-ranked subtype error + --> $DIR/ice-106874.rs:8:5 + | +LL | A(B(C::new(D::new(move |st| f(st))))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: implementation of `FnOnce` is not general enough --> $DIR/ice-106874.rs:8:7 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` -error: higher-ranked subtype error - --> $DIR/ice-106874.rs:8:41 +error: implementation of `Fn` is not general enough + --> $DIR/ice-106874.rs:8:7 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` + +error: implementation of `FnOnce` is not general enough + --> $DIR/ice-106874.rs:8:7 + | +LL | A(B(C::new(D::new(move |st| f(st))))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: implementation of `Fn` is not general enough + --> $DIR/ice-106874.rs:8:7 + | +LL | A(B(C::new(D::new(move |st| f(st))))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: implementation of `FnOnce` is not general enough + --> $DIR/ice-106874.rs:8:7 + | +LL | A(B(C::new(D::new(move |st| f(st))))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: implementation of `Fn` is not general enough + --> $DIR/ice-106874.rs:8:7 + | +LL | A(B(C::new(D::new(move |st| f(st))))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: aborting due to 10 previous errors