Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
61b26f9
Improve diagnostics for buffer reuse with borrowed references
JohnTitor Oct 22, 2025
1c0cae3
Set up snapshot tests for bootstrap's path-to-step handling
Zalathar Nov 1, 2025
bb05869
Add several snapshot tests for path-to-step handling
Zalathar Nov 1, 2025
4e9dfb5
cmse: add test for `async` and `const` functions
folkertdev Nov 5, 2025
568c6ed
propagate function attributes in ast visitor
folkertdev Nov 9, 2025
1cff528
epoll: unblock threads when new interest is registered
RalfJung Nov 9, 2025
ac12e46
simplify epoll data structures: dont make the interests themselves in…
RalfJung Nov 9, 2025
ebd173f
add `feature(c_variadic_naked_functions)`
folkertdev Nov 9, 2025
9129b90
use `feature(c_variadic_naked_functions)` in tests
folkertdev Nov 9, 2025
69be846
Merge pull request #4675 from RalfJung/epoll
RalfJung Nov 9, 2025
047ee0a
epoll: when an event disappears entirely, remove it from the ready list
RalfJung Nov 10, 2025
a8baf33
epoll: do proper edge detection inside the epoll system
RalfJung Nov 10, 2025
6c6aa0f
Prepare for merging from rust-lang/rust
Nov 10, 2025
c9a2581
Merge ref '8401398e1f14' from rust-lang/rust
Nov 10, 2025
e8aa88f
fmt
Nov 10, 2025
3c56919
clippy
RalfJung Nov 10, 2025
656fe2f
Merge pull request #4677 from rust-lang/rustup-2025-11-10
RalfJung Nov 10, 2025
4412a0a
eventfd: force spurious wakeups, matching Linux
RalfJung Nov 10, 2025
7b4802d
fix EPOLL_CTL_MOD
RalfJung Nov 10, 2025
fc4748c
make EpollInterestTable sorted to avoid linear scan
RalfJung Nov 10, 2025
fe6bc02
avoid using the address of an FD; now that we sort by that it may be …
RalfJung Nov 10, 2025
b340f08
add the tokio poll_fns test that triggered this investigation
RalfJung Nov 10, 2025
15299b7
Merge pull request #4676 from RalfJung/epoll-edge
RalfJung Nov 10, 2025
69da9af
Update git index before running diff-index
Kobzol Nov 4, 2025
fcce61c
Remove specialized warning for removed target
bjorn3 Nov 11, 2025
0b45ab3
Suggest add bounding value for RangeTo
chenyukang Oct 16, 2025
04f798b
Fix invalid jump to def macro link generation
GuillaumeGomez Oct 24, 2025
dacabcd
Update jump to def macro link generation test
GuillaumeGomez Oct 24, 2025
f4e1ffc
Add missing documentation
GuillaumeGomez Nov 6, 2025
050412a
add shim for avx512 ternarylogic functions
folkertdev Nov 10, 2025
4d3d3b3
Improve code
GuillaumeGomez Nov 11, 2025
13b8cf5
Merge pull request #4678 from folkertdev/ternary-logic
RalfJung Nov 11, 2025
dd2159e
Simplify jemalloc setup
madsmtm Nov 11, 2025
65f0b7a
Fix building rustdoc and clippy with jemalloc feature
madsmtm Oct 25, 2025
54ee492
Update rustbook dependencies
jamie-osec Nov 11, 2025
c8d1ac9
Remove more #[must_use] from portable-simd
dtolnay Nov 11, 2025
888937e
Rollup merge of #146627 - madsmtm:jemalloc-simplify, r=jdonszelmann
Zalathar Nov 11, 2025
a6c9a7b
Rollup merge of #147753 - chenyukang:yukang-147749, r=fmease
Zalathar Nov 11, 2025
f22d205
Rollup merge of #147974 - JohnTitor:diag-detect-buf-reuse-pattern, r=…
Zalathar Nov 11, 2025
be19629
Rollup merge of #148080 - GuillaumeGomez:fix-jump-def-links, r=lolbin…
Zalathar Nov 11, 2025
5488658
Rollup merge of #148424 - Zalathar:tests, r=Kobzol
Zalathar Nov 11, 2025
127b459
Rollup merge of #148500 - Kobzol:git-update-inex, r=jieyouxu
Zalathar Nov 11, 2025
052feb2
Rollup merge of #148536 - folkertdev:cmse-async-const-fn, r=davidtwco
Zalathar Nov 11, 2025
1fe1f02
Rollup merge of #148770 - folkertdev:naked-c-variadic, r=workingjubilee
Zalathar Nov 11, 2025
d4efa36
Rollup merge of #148819 - bjorn3:fix_fixme, r=jieyouxu
Zalathar Nov 11, 2025
e8f8143
Rollup merge of #148830 - RalfJung:miri, r=RalfJung
Zalathar Nov 11, 2025
9db311e
Rollup merge of #148833 - clubby789:cargo-update-rustbook-11-11-25, r…
Zalathar Nov 11, 2025
da6f7ce
Rollup merge of #148841 - dtolnay:simdmustuse, r=calebzulawski
Zalathar Nov 11, 2025
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
18 changes: 3 additions & 15 deletions src/tools/miri/src/shims/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ impl<T: ?Sized> FileDescriptionRef<T> {
pub fn id(&self) -> FdId {
self.0.id
}

/// Returns the raw address of this file description. Useful for equality comparisons.
/// Use `id` instead if this can affect user-visible behavior!
pub fn addr(&self) -> usize {
Rc::as_ptr(&self.0).addr()
}
}

/// Holds a weak reference to the actual file description.
Expand All @@ -76,11 +70,6 @@ impl<T: ?Sized> WeakFileDescriptionRef<T> {
pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
self.0.upgrade().map(FileDescriptionRef)
}

/// Returns the raw address of this file description. Useful for equality comparisons.
pub fn addr(&self) -> usize {
self.0.as_ptr().addr()
}
}

impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
Expand Down Expand Up @@ -116,13 +105,12 @@ impl<T: FileDescription + 'static> FileDescriptionExt for T {
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
let addr = self.addr();
match Rc::into_inner(self.0) {
Some(fd) => {
// There might have been epolls interested in this FD. Remove that.
ecx.machine.epoll_interests.remove_epolls(fd.id);

fd.inner.destroy(addr, communicate_allowed, ecx)
fd.inner.destroy(fd.id, communicate_allowed, ecx)
}
None => {
// Not the last reference.
Expand Down Expand Up @@ -200,7 +188,7 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
/// `self_addr` is the address that this file description used to be stored at.
fn destroy<'tcx>(
self,
_self_addr: usize,
_self_id: FdId,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>>
Expand Down Expand Up @@ -379,7 +367,7 @@ impl FileDescription for FileHandle {

fn destroy<'tcx>(
self,
_self_addr: usize,
_self_id: FdId,
communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
Expand Down
120 changes: 72 additions & 48 deletions src/tools/miri/src/shims/unix/linux_like/epoll.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, btree_map};
use std::io;
use std::time::Duration;

Expand Down Expand Up @@ -44,7 +44,7 @@ fn range_for_id(id: FdId) -> std::ops::RangeInclusive<EpollEventKey> {
}

/// EpollEventInstance contains information that will be returned by epoll_wait.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct EpollEventInstance {
/// Bitmask of event types that happened to the file description.
events: u32,
Expand All @@ -54,12 +54,6 @@ pub struct EpollEventInstance {
clock: VClock,
}

impl EpollEventInstance {
pub fn new(events: u32, data: u64) -> EpollEventInstance {
EpollEventInstance { events, data, clock: Default::default() }
}
}

/// EpollEventInterest registers the file description information to an epoll
/// instance during a successful `epoll_ctl` call. It also stores additional
/// information needed to check and update readiness state for `epoll_wait`.
Expand All @@ -69,10 +63,12 @@ impl EpollEventInstance {
/// see the man page:
///
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
#[derive(Debug, Copy, Clone)]
#[derive(Debug)]
pub struct EpollEventInterest {
/// The events bitmask retrieved from `epoll_event`.
events: u32,
/// The way the events looked last time we checked (for edge trigger / ET detection).
prev_events: u32,
/// The data retrieved from `epoll_event`.
/// libc's data field in epoll_event can store integer or pointer,
/// but only u64 is supported for now.
Expand Down Expand Up @@ -146,15 +142,15 @@ impl FileDescription for Epoll {

fn destroy<'tcx>(
mut self,
self_addr: usize,
self_id: FdId,
_communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
// If we were interested in some FDs, we can remove that now.
let mut ids = self.interest_list.get_mut().keys().map(|(id, _num)| *id).collect::<Vec<_>>();
ids.dedup(); // they come out of the map sorted
for id in ids {
ecx.machine.epoll_interests.remove(id, self_addr);
ecx.machine.epoll_interests.remove(id, self_id);
}
interp_ok(Ok(()))
}
Expand All @@ -168,36 +164,38 @@ impl UnixFileDescription for Epoll {}

/// The table of all EpollEventInterest.
/// This tracks, for each file description, which epoll instances have an interest in events
/// for this file description.
pub struct EpollInterestTable(BTreeMap<FdId, Vec<WeakFileDescriptionRef<Epoll>>>);
/// for this file description. The `FdId` is the ID of the epoll instance, so that we can recognize
/// it later when it is slated for removal. The vector is sorted by that ID.
pub struct EpollInterestTable(BTreeMap<FdId, Vec<(FdId, WeakFileDescriptionRef<Epoll>)>>);

impl EpollInterestTable {
pub(crate) fn new() -> Self {
EpollInterestTable(BTreeMap::new())
}

fn insert(&mut self, id: FdId, epoll: WeakFileDescriptionRef<Epoll>) {
fn insert(&mut self, id: FdId, epoll: &FileDescriptionRef<Epoll>) {
let epolls = self.0.entry(id).or_default();
epolls.push(epoll);
let idx = epolls
.binary_search_by_key(&epoll.id(), |&(id, _)| id)
.expect_err("trying to add an epoll that's already in the list");
epolls.insert(idx, (epoll.id(), FileDescriptionRef::downgrade(epoll)));
}

fn remove(&mut self, id: FdId, epoll_addr: usize) {
fn remove(&mut self, id: FdId, epoll_id: FdId) {
let epolls = self.0.entry(id).or_default();
// FIXME: linear scan. Keep the list sorted so we can do binary search?
let idx = epolls
.iter()
.position(|old_ref| old_ref.addr() == epoll_addr)
.binary_search_by_key(&epoll_id, |&(id, _)| id)
.expect("trying to remove an epoll that's not in the list");
epolls.remove(idx);
}

fn get_epolls(&self, id: FdId) -> Option<&Vec<WeakFileDescriptionRef<Epoll>>> {
self.0.get(&id)
fn get_epolls(&self, id: FdId) -> Option<impl Iterator<Item = &WeakFileDescriptionRef<Epoll>>> {
self.0.get(&id).map(|epolls| epolls.iter().map(|(_id, epoll)| epoll))
}

pub fn remove_epolls(&mut self, id: FdId) {
if let Some(epolls) = self.0.remove(&id) {
for epoll in epolls.iter().filter_map(|e| e.upgrade()) {
for epoll in epolls.iter().filter_map(|(_id, epoll)| epoll.upgrade()) {
// This is a still-live epoll with interest in this FD. Remove all
// relevent interests.
epoll
Expand Down Expand Up @@ -343,33 +341,40 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
);
}

// Add new interest to list.
// Add new interest to list. Experiments show that we need to reset all state
// on `EPOLL_CTL_MOD`, including the edge tracking.
let epoll_key = (id, fd);
let new_interest = EpollEventInterest { events, data };
if op == epoll_ctl_add {
let new_interest = EpollEventInterest { events, data, prev_events: 0 };
let new_interest = if op == epoll_ctl_add {
if interest_list.range(range_for_id(id)).next().is_none() {
// This is the first time this FD got added to this epoll.
// Remember that in the global list so we get notified about FD events.
this.machine.epoll_interests.insert(id, FileDescriptionRef::downgrade(&epfd));
this.machine.epoll_interests.insert(id, &epfd);
}
if interest_list.insert(epoll_key, new_interest).is_some() {
// We already had interest in this.
return this.set_last_error_and_return_i32(LibcError("EEXIST"));
match interest_list.entry(epoll_key) {
btree_map::Entry::Occupied(_) => {
// We already had interest in this.
return this.set_last_error_and_return_i32(LibcError("EEXIST"));
}
btree_map::Entry::Vacant(e) => e.insert(new_interest),
}
} else {
// Modify the existing interest.
let Some(interest) = interest_list.get_mut(&epoll_key) else {
return this.set_last_error_and_return_i32(LibcError("ENOENT"));
};
*interest = new_interest;
interest
};

// Deliver events for the new interest.
let force_edge = true; // makes no difference since we reset `prev_events`
send_ready_events_to_interests(
this,
&epfd,
fd_ref.as_unix(this).get_epoll_ready_events()?.get_event_bitmask(this),
std::iter::once((&epoll_key, &new_interest)),
force_edge,
std::iter::once((&epoll_key, new_interest)),
)?;

interp_ok(Scalar::from_i32(0))
Expand All @@ -384,10 +389,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// If this was the last interest in this FD, remove us from the global list
// of who is interested in this FD.
if interest_list.range(range_for_id(id)).next().is_none() {
this.machine.epoll_interests.remove(id, epfd.addr());
this.machine.epoll_interests.remove(id, epfd.id());
}

// Remove related epoll_interest from ready list.
// Remove related event instance from ready list.
epfd.ready_list.borrow_mut().remove(&epoll_key);

interp_ok(Scalar::from_i32(0))
Expand Down Expand Up @@ -519,13 +524,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}

/// For a specific file description, get its ready events and send it to everyone who registered
/// interest in this FD. This function should be called whenever an event causes more bytes or
/// an EOF to become newly readable from an FD, and whenever more bytes can be written to an FD
/// or no more future writes are possible.
/// interest in this FD. This function should be called whenever the result of
/// `get_epoll_ready_events` would change.
///
/// This *will* report an event if anyone is subscribed to it, without any further filtering, so
/// do not call this function when an FD didn't have anything happen to it!
fn epoll_send_fd_ready_events(&mut self, fd_ref: DynFileDescriptionRef) -> InterpResult<'tcx> {
/// If `force_edge` is set, edge-triggered interests will be triggered even if the set of
/// ready events did not change. This can lead to spurious wakeups. Use with caution!
fn epoll_send_fd_ready_events(
&mut self,
fd_ref: DynFileDescriptionRef,
force_edge: bool,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let id = fd_ref.id();
// Figure out who is interested in this. We need to clone this list since we can't prove
Expand All @@ -534,7 +542,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return interp_ok(());
};
let epolls = epolls
.iter()
.map(|weak| {
weak.upgrade()
.expect("someone forgot to remove the garbage from `machine.epoll_interests`")
Expand All @@ -546,7 +553,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this,
&epoll,
event_bitmask,
epoll.interest_list.borrow().range(range_for_id(id)),
force_edge,
epoll.interest_list.borrow_mut().range_mut(range_for_id(id)),
)?;
}

Expand All @@ -560,25 +568,41 @@ fn send_ready_events_to_interests<'tcx, 'a>(
ecx: &mut MiriInterpCx<'tcx>,
epoll: &Epoll,
event_bitmask: u32,
interests: impl Iterator<Item = (&'a EpollEventKey, &'a EpollEventInterest)>,
force_edge: bool,
interests: impl Iterator<Item = (&'a EpollEventKey, &'a mut EpollEventInterest)>,
) -> InterpResult<'tcx> {
let mut wakeup = false;
for (&event_key, interest) in interests {
let mut ready_list = epoll.ready_list.borrow_mut();
// This checks if any of the events specified in epoll_event_interest.events
// match those in ready_events.
let flags = interest.events & event_bitmask;
let prev = std::mem::replace(&mut interest.prev_events, flags);
if flags == 0 {
// Make sure we *remove* any previous item from the ready list, since this
// is not ready any more.
ready_list.remove(&event_key);
continue;
}
// Geenrate a new event instance, with the flags that this one is interested in.
let mut new_instance = EpollEventInstance::new(flags, interest.data);
// Generate new instance, or update existing one. It is crucial that whe we are done,
// if an interest exists in the ready list, then it matches the latest events and data!
let instance = match ready_list.entry(event_key) {
btree_map::Entry::Occupied(e) => e.into_mut(),
btree_map::Entry::Vacant(e) => {
if !force_edge && flags == prev & flags {
// Every bit in `flags` was already set in `prev`, and there's currently
// no entry in the ready list for this. So there is nothing new and no
// prior entry to update; just skip it.
continue;
}
e.insert(EpollEventInstance::default())
}
};
instance.events = flags;
instance.data = interest.data;
ecx.release_clock(|clock| {
new_instance.clock.clone_from(clock);
instance.clock.join(clock);
})?;
// Add event to ready list for this epoll instance.
// Tests confirm that we have to *overwrite* the old instance for the same key.
let mut ready_list = epoll.ready_list.borrow_mut();
ready_list.insert(event_key, new_instance);
wakeup = true;
}
if wakeup {
Expand Down
12 changes: 8 additions & 4 deletions src/tools/miri/src/shims/unix/linux_like/eventfd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::io;
use std::io::ErrorKind;

use crate::concurrency::VClock;
use crate::shims::files::{FileDescription, FileDescriptionRef, WeakFileDescriptionRef};
use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef};
use crate::shims::unix::UnixFileDescription;
use crate::shims::unix::linux_like::epoll::{EpollReadyEvents, EvalContextExt as _};
use crate::*;
Expand Down Expand Up @@ -39,7 +39,7 @@ impl FileDescription for EventFd {

fn destroy<'tcx>(
self,
_self_addr: usize,
_self_id: FdId,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
Expand Down Expand Up @@ -217,7 +217,10 @@ fn eventfd_write<'tcx>(

// The state changed; we check and update the status of all supported event
// types for current file description.
ecx.epoll_send_fd_ready_events(eventfd)?;
// Linux seems to cause spurious wakeups here, and Tokio seems to rely on that
// (see <https://github.com/rust-lang/miri/pull/4676#discussion_r2510528994>
// and also <https://www.illumos.org/issues/16700>).
ecx.epoll_send_fd_ready_events(eventfd, /* force_edge */ true)?;

// Return how many bytes we consumed from the user-provided buffer.
return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
Expand Down Expand Up @@ -312,7 +315,8 @@ fn eventfd_read<'tcx>(

// The state changed; we check and update the status of all supported event
// types for current file description.
ecx.epoll_send_fd_ready_events(eventfd)?;
// Linux seems to always emit do notifications here, even if we were already writable.
ecx.epoll_send_fd_ready_events(eventfd, /* force_edge */ true)?;

// Tell userspace how many bytes we put into the buffer.
return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
Expand Down
Loading