Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
446629d
Prepare for merging from rust-lang/rust
Apr 5, 2026
1d807ed
Merge ref 'c92036b45bab' from rust-lang/rust
Apr 5, 2026
82b637b
float test: increase ULP tolerance
RalfJung Apr 5, 2026
97010af
Merge pull request #4941 from rust-lang/rustup-2026-04-05
RalfJung Apr 5, 2026
0b6423d
float test: double error tolerance with imprecise ops on both sides
RalfJung Apr 6, 2026
7f35239
Merge pull request #4942 from RalfJung/float-err-bounds
RalfJung Apr 6, 2026
7a501c5
Prepare for merging from rust-lang/rust
Apr 7, 2026
315c167
Merge ref 'bcded331651b' from rust-lang/rust
Apr 7, 2026
51dec75
fmt
Apr 7, 2026
0fbaf47
Merge pull request #4945 from rust-lang/rustup-2026-04-07
RalfJung Apr 7, 2026
bff890b
Prepare for merging from rust-lang/rust
Apr 8, 2026
1a2291f
Merge ref '30d0309fa821' from rust-lang/rust
Apr 8, 2026
f373acf
disable new build dir layout again due to problems
RalfJung Apr 8, 2026
3fda203
Merge pull request #4946 from rust-lang/rustup-2026-04-08
RalfJung Apr 8, 2026
66580a3
genmc-sys: remove always_configure hack
RalfJung Apr 8, 2026
e44bb8d
Merge pull request #4948 from RalfJung/genmc-always-configure
RalfJung Apr 8, 2026
df1b8b8
fix: fix panic when multiple threads block on same fd
WhySoBad Apr 8, 2026
3c4dfb4
docs: add comment when registering new source
WhySoBad Apr 8, 2026
beaf7ee
feat: also check that received buffers match
WhySoBad Apr 8, 2026
5c57c5a
fix the bitfield meanings of stacked_borrow item
zjp-CN Apr 9, 2026
a33321c
Merge pull request #4951 from KMiri-rs/stack-borrow-bitfields
RalfJung Apr 9, 2026
630cdae
Prepare for merging from rust-lang/rust
RalfJung Apr 9, 2026
67fd846
Merge ref '4c4205163abc' from rust-lang/rust
RalfJung Apr 9, 2026
4e6323b
re-enable new cargo build dir layout testing
RalfJung Apr 9, 2026
5474218
Merge pull request #4952 from RalfJung/rustup
RalfJung Apr 9, 2026
c3c931a
genmc tests: remove a hack that is no longer needed
RalfJung Apr 9, 2026
a75e80c
remove unused type
RalfJung Apr 9, 2026
6428be1
Merge pull request #4953 from RalfJung/hack-no-more
RalfJung Apr 9, 2026
4e7ec91
fix: implement feedback
WhySoBad Apr 9, 2026
085e4f2
fix: fix doc comment
WhySoBad Apr 9, 2026
0b7f225
fix: implement feedback
WhySoBad Apr 10, 2026
6d4d7a6
fix: deregister I/O sources in blocking I/O manager
WhySoBad Apr 10, 2026
edf04e1
Merge pull request #4950 from WhySoBad/fix-blocking-io-manager
RalfJung Apr 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions src/tools/miri/genmc-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ mod downloading {
pub(crate) const GENMC_COMMIT: &str = "22d3d0b44dedb4e8e1aae3330e546465e4664529";

/// 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.
pub(crate) fn download_genmc() -> (PathBuf, bool) {
/// Return the path of the GenMC repo clone.
pub(crate) fn download_genmc() -> PathBuf {
let Ok(genmc_download_path) = PathBuf::from_str(GENMC_DOWNLOAD_PATH);
let commit_oid = Oid::from_str(GENMC_COMMIT).expect("Commit should be valid.");

Expand All @@ -44,7 +44,7 @@ mod downloading {
&& head_commit.id() == commit_oid
{
// Fast path: The expected commit is already checked out.
return (genmc_download_path, false);
return genmc_download_path;
}
// Check if the local repository already contains the commit we need, download it otherwise.
let commit = update_local_repo(&repo, commit_oid);
Expand All @@ -61,7 +61,7 @@ mod downloading {
}
};

(genmc_download_path, true)
genmc_download_path
}

fn get_remote(repo: &Repository) -> Remote<'_> {
Expand Down Expand Up @@ -141,7 +141,7 @@ mod downloading {
}

/// Build the GenMC model checker library and the Rust-C++ interop library with cxx.rs
fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
fn compile_cpp_dependencies(genmc_path: &Path) {
// Give each step a separate build directory to prevent interference.
let out_dir = PathBuf::from(std::env::var("OUT_DIR").as_deref().unwrap());
let genmc_build_dir = out_dir.join("genmc");
Expand All @@ -156,7 +156,6 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {

let mut config = cmake::Config::new(genmc_path);
config
.always_configure(always_configure) // We force running the configure step when the GenMC commit changed.
.out_dir(genmc_build_dir)
.profile(GENMC_CMAKE_PROFILE)
.define("BUILD_LLI", "OFF")
Expand Down Expand Up @@ -208,8 +207,7 @@ fn main() {
}

// Select which path to use for the GenMC repo:
let (genmc_path, always_configure) = if let Some(genmc_src_path) = option_env!("GENMC_SRC_PATH")
{
let genmc_path = if let Some(genmc_src_path) = option_env!("GENMC_SRC_PATH") {
let genmc_src_path =
PathBuf::from_str(&genmc_src_path).expect("GENMC_SRC_PATH should contain a valid path");
assert!(
Expand All @@ -219,18 +217,14 @@ fn main() {
);
// Rebuild files in the given path change.
println!("cargo::rerun-if-changed={}", genmc_src_path.display());
// We disable `always_configure` when working with a local repository,
// since it increases compile times when working on `genmc-sys`.
(genmc_src_path, false)
genmc_src_path
} else {
// Download GenMC if required and ensure that the correct commit is checked out.
// If anything changed in the downloaded repository (e.g., the commit),
// we set `always_configure` to ensure there are no weird configs from previous builds.
downloading::download_genmc()
};

// Build all required components:
compile_cpp_dependencies(&genmc_path, always_configure);
compile_cpp_dependencies(&genmc_path);

// Only rebuild if anything changes:
// Note that we don't add the downloaded GenMC repo, since that should never be modified
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/rust-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
55e86c996809902e8bbad512cfb4d2c18be446d9
4c4205163abcbd08948b3efab796c543ba1ea687
5 changes: 3 additions & 2 deletions src/tools/miri/src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,9 @@ impl rustc_driver::Callbacks for MiriDepCompilerCalls {
// We need to add #[used] symbols to exported_symbols for `lookup_link_section`.
// FIXME handle this somehow in rustc itself to avoid this hack.
local_providers.queries.exported_non_generic_symbols = |tcx, LocalCrate| {
let reachable_set = tcx
.with_stable_hashing_context(|mut hcx| tcx.reachable_set(()).to_sorted(&mut hcx, true));
let reachable_set = tcx.with_stable_hashing_context(|mut hcx| {
tcx.reachable_set(()).to_sorted(&mut hcx, true)
});
tcx.arena.alloc_from_iter(
// This is based on:
// https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63
Expand Down
6 changes: 3 additions & 3 deletions src/tools/miri/src/borrow_tracker/stacked_borrows/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use crate::borrow_tracker::BorTag;
pub struct Item(u64);

// An Item contains 3 bitfields:
// * Bits 0-61 store a BorTag.
// * Bits 61-63 store a Permission.
// * Bit 64 stores a flag which indicates if we might have a protector.
// * Bits 0-60 store a BorTag (61 bits).
// * Bits 61-62 store a Permission (2 bits).
// * Bit 63 stores a flag which indicates if we might have a protector (1 bit).
// This is purely an optimization: if the bit is set, the tag *might* be
// in `protected_tags`, but if the bit is not set then the tag is definitely
// not in `protected_tags`.
Expand Down
141 changes: 110 additions & 31 deletions src/tools/miri/src/concurrency/blocking_io.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::collections::BTreeMap;
use std::io;
use std::time::Duration;

use mio::event::Source;
use mio::{Events, Interest, Poll, Token};
use rustc_data_structures::fx::FxHashMap;

use crate::shims::{FdId, FileDescriptionRef};
use crate::*;

/// Capacity of the event queue which can be polled at a time.
Expand All @@ -18,6 +20,14 @@ pub trait WithSource {
fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()>;
}

/// An interest receiver defines the action that should be taken when
/// the associated [`Interest`] is fulfilled.
#[derive(Debug, Hash, PartialEq, Clone, Copy, Eq, PartialOrd, Ord)]
pub enum InterestReceiver {
/// The specified thread should be unblocked.
UnblockThread(ThreadId),
}

/// Manager for managing blocking host I/O in a non-blocking manner.
/// We use [`Poll`] to poll for new I/O events from the OS for sources
/// registered using this manager.
Expand All @@ -34,9 +44,10 @@ pub struct BlockingIoManager {
/// This is not part of the state and only stored to avoid allocating a
/// new buffer for every poll.
events: Events,
/// Map between threads which are currently blocked and the
/// underlying I/O source.
sources: FxHashMap<ThreadId, Box<dyn WithSource>>,
/// Map from source ids to the actual sources and their registered receivers
/// together with their associated interests.
sources:
BTreeMap<FdId, (FileDescriptionRef<dyn WithSource>, FxHashMap<InterestReceiver, Interest>)>,
}

impl BlockingIoManager {
Expand All @@ -46,7 +57,7 @@ impl BlockingIoManager {
let manager = Self {
poll: communicate.then_some(Poll::new()?),
events: Events::with_capacity(IO_EVENT_CAPACITY),
sources: FxHashMap::default(),
sources: BTreeMap::default(),
};
Ok(manager)
}
Expand All @@ -59,8 +70,12 @@ impl BlockingIoManager {
/// specified duration.
/// - If the timeout is [`None`] the poll blocks indefinitely until an event occurs.
///
/// Returns all threads that are ready because they received an I/O event.
pub fn poll(&mut self, timeout: Option<Duration>) -> Result<Vec<ThreadId>, io::Error> {
/// Returns the interest receivers for all file descriptions which received an I/O event together
/// with the file description they were registered for.
pub fn poll(
&mut self,
timeout: Option<Duration>,
) -> Result<Vec<(InterestReceiver, FileDescriptionRef<dyn WithSource>)>, io::Error> {
let poll =
self.poll.as_mut().expect("Blocking I/O should not be called with isolation enabled");

Expand All @@ -70,56 +85,120 @@ impl BlockingIoManager {
let ready = self
.events
.iter()
.map(|event| {
.flat_map(|event| {
let token = event.token();
ThreadId::new_unchecked(token.0.try_into().unwrap())
// We know all tokens are valid `FdId`.
let fd_id = FdId::new_unchecked(token.0);
let (source, interests) =
self.sources.get(&fd_id).expect("Source should be registered");
assert_eq!(source.id(), fd_id);
// Because we allow spurious wake-ups, we mark all interests as ready even
// though some may not have been fulfilled.
interests.keys().map(move |receiver| (*receiver, source.clone()))
})
.collect::<Vec<_>>();

// Deregister all ready sources as we only want to receive one event per thread.
ready.iter().for_each(|thread_id| self.deregister(*thread_id));
// Deregister all ready sources as we only want to receive one event per receiver.
ready.iter().for_each(|(receiver, source)| self.deregister(source.id(), *receiver));

Ok(ready)
}

/// Register a blocking I/O source for a thread together with it's poll interests.
///
/// The source will be deregistered automatically once an event for it is received.
/// Register an interest for a blocking I/O source.
///
/// As the OS can always produce spurious wake-ups, it's the callers responsibility to
/// verify the requested I/O interests are really ready and to register again if they're not.
pub fn register(&mut self, source: Box<dyn WithSource>, thread: ThreadId, interests: Interest) {
///
/// It's assumed that no interest is already registered for this source with the same reason!
pub fn register(
&mut self,
source_fd: FileDescriptionRef<dyn WithSource>,
receiver: InterestReceiver,
interest: Interest,
) {
let poll =
self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled");

let token = Token(thread.to_u32().to_usize());
let id = source_fd.id();
let token = Token(id.to_usize());

let Some((_, current_interests)) = self.sources.get_mut(&id) else {
// The source is not yet registered.

// Treat errors from registering as fatal. On UNIX hosts this can only
// fail due to system resource errors (e.g. ENOMEM or ENOSPC).
source_fd
.with_source(&mut |source| poll.registry().register(source, token, interest))
.unwrap();

self.sources.insert(id, (source_fd, FxHashMap::from_iter([(receiver, interest)])));
return;
};

// The source is already registered. We need to check whether we need to
// reregister because the provided interest contains new interests for the source.

// Treat errors from registering as fatal. On UNIX hosts this can only
let old_interest =
interest_union(current_interests).expect("Source should contain at least one interest");

current_interests
.try_insert(receiver, interest)
.unwrap_or_else(|_| panic!("Receiver should be unique"));

let new_interest = old_interest.add(interest);

// Reregister the source since the overall interests might have changed.

// Treat errors from reregistering as fatal. On UNIX hosts this can only
// fail due to system resource errors (e.g. ENOMEM or ENOSPC).
source
.with_source(&mut |source| source.register(poll.registry(), token, interests))
source_fd
.with_source(&mut |source| poll.registry().reregister(source, token, new_interest))
.unwrap();
self.sources
.try_insert(thread, source)
.unwrap_or_else(|_| panic!("A thread cannot be registered twice at the same time"));
}

/// Deregister the event source for a thread. Returns the kind of I/O the thread was
/// blocked on.
fn deregister(&mut self, thread: ThreadId) {
/// Deregister an interest from a blocking I/O source.
///
/// The receiver is assumed to be registered for the provided source!
pub fn deregister(&mut self, source_id: FdId, receiver: InterestReceiver) {
let poll =
self.poll.as_ref().expect("Blocking I/O should not be called with isolation enabled");

let Some(source) = self.sources.remove(&thread) else {
panic!("Attempt to deregister a token which isn't registered")
let token = Token(source_id.to_usize());
let (fd, current_interests) =
self.sources.get_mut(&source_id).expect("Source should be registered");

current_interests
.remove(&receiver)
.unwrap_or_else(|| panic!("Receiver should be registered for source"));

let Some(new_interest) = interest_union(current_interests) else {
// There are no longer any interests in this source.
// We can thus deregister the source from the poll.

// Treat errors from deregistering as fatal. On UNIX hosts this can only
// fail due to system resource errors (e.g. ENOMEM or ENOSPC).
fd.with_source(&mut |source| poll.registry().deregister(source)).unwrap();
self.sources.remove(&source_id);
return;
};

// Treat errors from deregistering as fatal. On UNIX hosts this can only
// Reregister the source since the overall interests might have changed.

// Treat errors from reregistering as fatal. On UNIX hosts this can only
// fail due to system resource errors (e.g. ENOMEM or ENOSPC).
source.with_source(&mut |source| source.deregister(poll.registry())).unwrap();
fd.with_source(&mut |source| poll.registry().reregister(source, token, new_interest))
.unwrap();
}
}

/// Get the union of all interests for a source. Returns `None` if the map is empty.
fn interest_union(interests: &FxHashMap<InterestReceiver, Interest>) -> Option<Interest> {
interests
.values()
.copied()
.fold(None, |acc, interest| acc.map(|acc: Interest| acc.add(interest)).or(Some(interest)))
}

impl<'tcx> EvalContextExt<'tcx> for MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
/// Block the current thread until some interests on an I/O source
Expand All @@ -132,15 +211,15 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
#[inline]
fn block_thread_for_io(
&mut self,
source: impl WithSource + 'static,
source_fd: FileDescriptionRef<dyn WithSource>,
interests: Interest,
timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
callback: DynUnblockCallback<'tcx>,
) {
let this = self.eval_context_mut();
this.machine.blocking_io.register(
Box::new(source),
this.machine.threads.active_thread(),
source_fd,
InterestReceiver::UnblockThread(this.machine.threads.active_thread()),
interests,
);
this.block_thread(BlockReason::IO, timeout, callback);
Expand Down
8 changes: 7 additions & 1 deletion src/tools/miri/src/concurrency/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rustc_span::{DUMMY_SP, Span};
use rustc_target::spec::Os;

use crate::concurrency::GlobalDataRaceHandler;
use crate::concurrency::blocking_io::InterestReceiver;
use crate::shims::tls;
use crate::*;

Expand Down Expand Up @@ -822,7 +823,12 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
Err(e) => panic!("unexpected error while polling: {e}"),
};

ready.into_iter().try_for_each(|thread_id| this.unblock_thread(thread_id, BlockReason::IO))
ready.into_iter().try_for_each(|(receiver, _source)| {
match receiver {
InterestReceiver::UnblockThread(thread_id) =>
this.unblock_thread(thread_id, BlockReason::IO),
}
})
}

/// Find all threads with expired timeouts, unblock them and execute their timeout callbacks.
Expand Down
11 changes: 11 additions & 0 deletions src/tools/miri/src/shims/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ use crate::*;
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct FdId(usize);

impl FdId {
pub fn to_usize(self) -> usize {
self.0
}

/// Create a new fd id from a `usize` without checking if this fd exists.
pub fn new_unchecked(id: usize) -> Self {
Self(id)
}
}

#[derive(Debug, Clone)]
struct FdIdWith<T: ?Sized> {
id: FdId,
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub mod time;
pub mod tls;
pub mod unwind;

pub use self::files::FdTable;
pub use self::files::{FdId, FdTable, FileDescriptionRef};
#[cfg(all(feature = "native-lib", unix))]
pub use self::native_lib::trace::{init_sv, register_retcode_sv};
pub use self::unix::{DirTable, EpollInterestTable};
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::shims::unix::*;
use crate::*;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum FlockOp {
pub enum FlockOp {
SharedLock { nonblocking: bool },
ExclusiveLock { nonblocking: bool },
Unlock,
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/unix/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ impl VisitProvenance for FileDescriptionRef<Socket> {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
}

impl WithSource for FileDescriptionRef<Socket> {
impl WithSource for Socket {
fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
let mut state = self.state.borrow_mut();
match &mut *state {
Expand Down
Loading
Loading