about summary refs log tree commit diff
path: root/src/tools/miri
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-05-26 19:14:56 +0200
committerRalf Jung <post@ralfj.de>2024-05-26 19:15:05 +0200
commit2e89443b93c8ebfcd361e5201cc94884db14fee3 (patch)
tree0b30fee2cdc4c37f66e064cdf54c8b99f876ab99 /src/tools/miri
parente6bb468b53f4756f1b9356d475fcaf280a16abef (diff)
downloadrust-2e89443b93c8ebfcd361e5201cc94884db14fee3.tar.gz
rust-2e89443b93c8ebfcd361e5201cc94884db14fee3.zip
add a macro to declare thread unblock callbacks
Diffstat (limited to 'src/tools/miri')
-rw-r--r--src/tools/miri/src/concurrency/init_once.rs2
-rw-r--r--src/tools/miri/src/concurrency/sync.rs301
-rw-r--r--src/tools/miri/src/concurrency/thread.rs117
-rw-r--r--src/tools/miri/src/provenance_gc.rs12
-rw-r--r--src/tools/miri/src/shims/time.rs36
-rw-r--r--src/tools/miri/src/shims/windows/sync.rs37
6 files changed, 256 insertions, 249 deletions
diff --git a/src/tools/miri/src/concurrency/init_once.rs b/src/tools/miri/src/concurrency/init_once.rs
index 1ee84273b58..3d6cf7ecca6 100644
--- a/src/tools/miri/src/concurrency/init_once.rs
+++ b/src/tools/miri/src/concurrency/init_once.rs
@@ -75,7 +75,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     fn init_once_enqueue_and_block(
         &mut self,
         id: InitOnceId,
-        callback: impl UnblockCallback<'mir, 'tcx> + 'tcx,
+        callback: impl UnblockCallback<'tcx> + 'tcx,
     ) {
         let this = self.eval_context_mut();
         let thread = this.active_thread();
diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs
index df10e15fe10..8925409ecc2 100644
--- a/src/tools/miri/src/concurrency/sync.rs
+++ b/src/tools/miri/src/concurrency/sync.rs
@@ -35,6 +35,10 @@ macro_rules! declare_id {
             }
         }
 
+        impl $crate::VisitProvenance for $name {
+            fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
+        }
+
         impl Idx for $name {
             fn new(idx: usize) -> Self {
                 // We use 0 as a sentinel value (see the comment above) and,
@@ -258,6 +262,25 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>:
             Ok(new_index)
         }
     }
+
+    fn condvar_reacquire_mutex(
+        &mut self,
+        mutex: MutexId,
+        retval: Scalar<Provenance>,
+        dest: MPlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        if this.mutex_is_locked(mutex) {
+            assert_ne!(this.mutex_get_owner(mutex), this.active_thread());
+            this.mutex_enqueue_and_block(mutex, retval, dest);
+        } else {
+            // We can have it right now!
+            this.mutex_lock(mutex);
+            // Don't forget to write the return value.
+            this.write_scalar(retval, &dest)?;
+        }
+        Ok(())
+    }
 }
 
 // Public interface to synchronization primitives. Please note that in most
@@ -384,29 +407,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         assert!(this.mutex_is_locked(id), "queing on unlocked mutex");
         let thread = this.active_thread();
         this.machine.sync.mutexes[id].queue.push_back(thread);
-        this.block_thread(BlockReason::Mutex(id), None, Callback { id, retval, dest });
-
-        struct Callback<'tcx> {
-            id: MutexId,
-            retval: Scalar<Provenance>,
-            dest: MPlaceTy<'tcx, Provenance>,
-        }
-        impl<'tcx> VisitProvenance for Callback<'tcx> {
-            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-                let Callback { id: _, retval, dest } = self;
-                retval.visit_provenance(visit);
-                dest.visit_provenance(visit);
-            }
-        }
-        impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
-            fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-                assert!(!this.mutex_is_locked(self.id));
-                this.mutex_lock(self.id);
-
-                this.write_scalar(self.retval, &self.dest)?;
-                Ok(())
-            }
-        }
+        this.block_thread(
+            BlockReason::Mutex(id),
+            None,
+            callback!(
+                @capture<'tcx> {
+                    id: MutexId,
+                    retval: Scalar<Provenance>,
+                    dest: MPlaceTy<'tcx, Provenance>,
+                }
+                @unblock = |this| {
+                    assert!(!this.mutex_is_locked(id));
+                    this.mutex_lock(id);
+                    this.write_scalar(retval, &dest)?;
+                    Ok(())
+                }
+            ),
+        );
     }
 
     #[inline]
@@ -500,27 +517,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let thread = this.active_thread();
         assert!(this.rwlock_is_write_locked(id), "read-queueing on not write locked rwlock");
         this.machine.sync.rwlocks[id].reader_queue.push_back(thread);
-        this.block_thread(BlockReason::RwLock(id), None, Callback { id, retval, dest });
-
-        struct Callback<'tcx> {
-            id: RwLockId,
-            retval: Scalar<Provenance>,
-            dest: MPlaceTy<'tcx, Provenance>,
-        }
-        impl<'tcx> VisitProvenance for Callback<'tcx> {
-            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-                let Callback { id: _, retval, dest } = self;
-                retval.visit_provenance(visit);
-                dest.visit_provenance(visit);
-            }
-        }
-        impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
-            fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-                this.rwlock_reader_lock(self.id);
-                this.write_scalar(self.retval, &self.dest)?;
-                Ok(())
-            }
-        }
+        this.block_thread(
+            BlockReason::RwLock(id),
+            None,
+            callback!(
+                @capture<'tcx> {
+                    id: RwLockId,
+                    retval: Scalar<Provenance>,
+                    dest: MPlaceTy<'tcx, Provenance>,
+                }
+                @unblock = |this| {
+                    this.rwlock_reader_lock(id);
+                    this.write_scalar(retval, &dest)?;
+                    Ok(())
+                }
+            ),
+        );
     }
 
     /// Lock by setting the writer that owns the lock.
@@ -588,27 +600,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         assert!(this.rwlock_is_locked(id), "write-queueing on unlocked rwlock");
         let thread = this.active_thread();
         this.machine.sync.rwlocks[id].writer_queue.push_back(thread);
-        this.block_thread(BlockReason::RwLock(id), None, Callback { id, retval, dest });
-
-        struct Callback<'tcx> {
-            id: RwLockId,
-            retval: Scalar<Provenance>,
-            dest: MPlaceTy<'tcx, Provenance>,
-        }
-        impl<'tcx> VisitProvenance for Callback<'tcx> {
-            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-                let Callback { id: _, retval, dest } = self;
-                retval.visit_provenance(visit);
-                dest.visit_provenance(visit);
-            }
-        }
-        impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
-            fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-                this.rwlock_writer_lock(self.id);
-                this.write_scalar(self.retval, &self.dest)?;
-                Ok(())
-            }
-        }
+        this.block_thread(
+            BlockReason::RwLock(id),
+            None,
+            callback!(
+                @capture<'tcx> {
+                    id: RwLockId,
+                    retval: Scalar<Provenance>,
+                    dest: MPlaceTy<'tcx, Provenance>,
+                }
+                @unblock = |this| {
+                    this.rwlock_writer_lock(id);
+                    this.write_scalar(retval, &dest)?;
+                    Ok(())
+                }
+            ),
+        );
     }
 
     /// Is the conditional variable awaited?
@@ -648,71 +655,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         this.block_thread(
             BlockReason::Condvar(condvar),
             timeout,
-            Callback { condvar, mutex, retval_succ, retval_timeout, dest },
-        );
-        return Ok(());
-
-        struct Callback<'tcx> {
-            condvar: CondvarId,
-            mutex: MutexId,
-            retval_succ: Scalar<Provenance>,
-            retval_timeout: Scalar<Provenance>,
-            dest: MPlaceTy<'tcx, Provenance>,
-        }
-        impl<'tcx> VisitProvenance for Callback<'tcx> {
-            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-                let Callback { condvar: _, mutex: _, retval_succ, retval_timeout, dest } = self;
-                retval_succ.visit_provenance(visit);
-                retval_timeout.visit_provenance(visit);
-                dest.visit_provenance(visit);
-            }
-        }
-        impl<'tcx, 'mir> Callback<'tcx> {
-            #[allow(clippy::boxed_local)]
-            fn reacquire_mutex(
-                self: Box<Self>,
-                this: &mut MiriInterpCx<'mir, 'tcx>,
-                retval: Scalar<Provenance>,
-            ) -> InterpResult<'tcx> {
-                if this.mutex_is_locked(self.mutex) {
-                    assert_ne!(this.mutex_get_owner(self.mutex), this.active_thread());
-                    this.mutex_enqueue_and_block(self.mutex, retval, self.dest);
-                } else {
-                    // We can have it right now!
-                    this.mutex_lock(self.mutex);
-                    // Don't forget to write the return value.
-                    this.write_scalar(retval, &self.dest)?;
+            callback!(
+                @capture<'tcx> {
+                    condvar: CondvarId,
+                    mutex: MutexId,
+                    retval_succ: Scalar<Provenance>,
+                    retval_timeout: Scalar<Provenance>,
+                    dest: MPlaceTy<'tcx, Provenance>,
                 }
-                Ok(())
-            }
-        }
-        impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
-            fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-                // The condvar was signaled. Make sure we get the clock for that.
-                if let Some(data_race) = &this.machine.data_race {
-                    data_race.acquire_clock(
-                        &this.machine.sync.condvars[self.condvar].clock,
-                        &this.machine.threads,
-                    );
+                @unblock = |this| {
+                    // The condvar was signaled. Make sure we get the clock for that.
+                    if let Some(data_race) = &this.machine.data_race {
+                        data_race.acquire_clock(
+                            &this.machine.sync.condvars[condvar].clock,
+                            &this.machine.threads,
+                        );
+                    }
+                    // Try to acquire the mutex.
+                    // The timeout only applies to the first wait (until the signal), not for mutex acquisition.
+                    this.condvar_reacquire_mutex(mutex, retval_succ, dest)
                 }
-                // Try to acquire the mutex.
-                // The timeout only applies to the first wait (until the signal), not for mutex acquisition.
-                let retval = self.retval_succ;
-                self.reacquire_mutex(this, retval)
-            }
-            fn timeout(
-                self: Box<Self>,
-                this: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-            ) -> InterpResult<'tcx> {
-                // We have to remove the waiter from the queue again.
-                let thread = this.active_thread();
-                let waiters = &mut this.machine.sync.condvars[self.condvar].waiters;
-                waiters.retain(|waiter| *waiter != thread);
-                // Now get back the lock.
-                let retval = self.retval_timeout;
-                self.reacquire_mutex(this, retval)
-            }
-        }
+                @timeout = |this| {
+                    // We have to remove the waiter from the queue again.
+                    let thread = this.active_thread();
+                    let waiters = &mut this.machine.sync.condvars[condvar].waiters;
+                    waiters.retain(|waiter| *waiter != thread);
+                    // Now get back the lock.
+                    this.condvar_reacquire_mutex(mutex, retval_timeout, dest)
+                }
+            ),
+        );
+        return Ok(());
     }
 
     /// Wake up some thread (if there is any) sleeping on the conditional
@@ -755,50 +728,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         this.block_thread(
             BlockReason::Futex { addr },
             timeout,
-            Callback { addr, retval_succ, retval_timeout, dest, errno_timeout },
-        );
-
-        struct Callback<'tcx> {
-            addr: u64,
-            retval_succ: Scalar<Provenance>,
-            retval_timeout: Scalar<Provenance>,
-            dest: MPlaceTy<'tcx, Provenance>,
-            errno_timeout: Scalar<Provenance>,
-        }
-        impl<'tcx> VisitProvenance for Callback<'tcx> {
-            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-                let Callback { addr: _, retval_succ, retval_timeout, dest, errno_timeout } = self;
-                retval_succ.visit_provenance(visit);
-                retval_timeout.visit_provenance(visit);
-                dest.visit_provenance(visit);
-                errno_timeout.visit_provenance(visit);
-            }
-        }
-        impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
-            fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-                let futex = this.machine.sync.futexes.get(&self.addr).unwrap();
-                // Acquire the clock of the futex.
-                if let Some(data_race) = &this.machine.data_race {
-                    data_race.acquire_clock(&futex.clock, &this.machine.threads);
+            callback!(
+                @capture<'tcx> {
+                    addr: u64,
+                    retval_succ: Scalar<Provenance>,
+                    retval_timeout: Scalar<Provenance>,
+                    dest: MPlaceTy<'tcx, Provenance>,
+                    errno_timeout: Scalar<Provenance>,
                 }
-                // Write the return value.
-                this.write_scalar(self.retval_succ, &self.dest)?;
-                Ok(())
-            }
-            fn timeout(
-                self: Box<Self>,
-                this: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-            ) -> InterpResult<'tcx> {
-                // Remove the waiter from the futex.
-                let thread = this.active_thread();
-                let futex = this.machine.sync.futexes.get_mut(&self.addr).unwrap();
-                futex.waiters.retain(|waiter| waiter.thread != thread);
-                // Set errno and write return value.
-                this.set_last_error(self.errno_timeout)?;
-                this.write_scalar(self.retval_timeout, &self.dest)?;
-                Ok(())
-            }
-        }
+                @unblock = |this| {
+                    let futex = this.machine.sync.futexes.get(&addr).unwrap();
+                    // Acquire the clock of the futex.
+                    if let Some(data_race) = &this.machine.data_race {
+                        data_race.acquire_clock(&futex.clock, &this.machine.threads);
+                    }
+                    // Write the return value.
+                    this.write_scalar(retval_succ, &dest)?;
+                    Ok(())
+                }
+                @timeout = |this| {
+                    // Remove the waiter from the futex.
+                    let thread = this.active_thread();
+                    let futex = this.machine.sync.futexes.get_mut(&addr).unwrap();
+                    futex.waiters.retain(|waiter| waiter.thread != thread);
+                    // Set errno and write return value.
+                    this.set_last_error(errno_timeout)?;
+                    this.write_scalar(retval_timeout, &dest)?;
+                    Ok(())
+                }
+            ),
+        );
     }
 
     /// Returns whether anything was woken.
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index d663ff5a3be..c948383d597 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -41,22 +41,75 @@ pub enum TlsAllocAction {
 }
 
 /// Trait for callbacks that are executed when a thread gets unblocked.
-pub trait UnblockCallback<'mir, 'tcx>: VisitProvenance {
-    fn unblock(
+pub trait UnblockCallback<'tcx>: VisitProvenance {
+    /// Will be invoked when the thread was unblocked the "regular" way,
+    /// i.e. whatever event it was blocking on has happened.
+    fn unblock<'mir>(
         self: Box<Self>,
         ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
     ) -> InterpResult<'tcx>;
 
-    fn timeout(
+    /// Will be invoked when the timeout ellapsed without the event the
+    /// thread was blocking on having occurred.
+    fn timeout<'mir>(
         self: Box<Self>,
         _ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-    ) -> InterpResult<'tcx> {
-        unreachable!(
-            "timeout on a thread that was blocked without a timeout (or someone forgot to overwrite this method)"
+    ) -> InterpResult<'tcx>;
+}
+type DynUnblockCallback<'tcx> = Box<dyn UnblockCallback<'tcx> + 'tcx>;
+
+#[macro_export]
+macro_rules! callback {
+    (
+        @capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? }
+        @unblock = |$this:ident| $unblock:block
+    ) => {
+        callback!(
+            @capture<$tcx, $($lft),*> { $($name: $type),+ }
+            @unblock = |$this| $unblock
+            @timeout = |_this| {
+                unreachable!(
+                    "timeout on a thread that was blocked without a timeout (or someone forgot to overwrite this method)"
+                )
+            }
         )
-    }
+    };
+    (
+        @capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? }
+        @unblock = |$this:ident| $unblock:block
+        @timeout = |$this_timeout:ident| $timeout:block
+    ) => {{
+        struct Callback<$tcx, $($lft),*> {
+            $($name: $type,)*
+            _phantom: std::marker::PhantomData<&$tcx ()>,
+        }
+
+        impl<$tcx, $($lft),*> VisitProvenance for Callback<$tcx, $($lft),*> {
+            #[allow(unused_variables)]
+            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
+                $(
+                    self.$name.visit_provenance(visit);
+                )*
+            }
+        }
+
+        impl<$tcx, $($lft),*> UnblockCallback<$tcx> for Callback<$tcx, $($lft),*> {
+            fn unblock<'mir>(self: Box<Self>, $this: &mut MiriInterpCx<'mir, $tcx>) -> InterpResult<$tcx> {
+                #[allow(unused_variables)]
+                let Callback { $($name,)* _phantom } = *self;
+                $unblock
+            }
+
+            fn timeout<'mir>(self: Box<Self>, $this_timeout: &mut MiriInterpCx<'mir, $tcx>) -> InterpResult<$tcx> {
+                #[allow(unused_variables)]
+                let Callback { $($name,)* _phantom } = *self;
+                $timeout
+            }
+        }
+
+        Callback { $($name,)* _phantom: std::marker::PhantomData }
+    }}
 }
-type DynUnblockCallback<'mir, 'tcx> = Box<dyn UnblockCallback<'mir, 'tcx> + 'tcx>;
 
 /// A thread identifier.
 #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
@@ -127,21 +180,17 @@ pub enum BlockReason {
 }
 
 /// The state of a thread.
-enum ThreadState<'mir, 'tcx> {
+enum ThreadState<'tcx> {
     /// The thread is enabled and can be executed.
     Enabled,
     /// The thread is blocked on something.
-    Blocked {
-        reason: BlockReason,
-        timeout: Option<Timeout>,
-        callback: DynUnblockCallback<'mir, 'tcx>,
-    },
+    Blocked { reason: BlockReason, timeout: Option<Timeout>, callback: DynUnblockCallback<'tcx> },
     /// The thread has terminated its execution. We do not delete terminated
     /// threads (FIXME: why?).
     Terminated,
 }
 
-impl<'mir, 'tcx> std::fmt::Debug for ThreadState<'mir, 'tcx> {
+impl<'tcx> std::fmt::Debug for ThreadState<'tcx> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::Enabled => write!(f, "Enabled"),
@@ -152,7 +201,7 @@ impl<'mir, 'tcx> std::fmt::Debug for ThreadState<'mir, 'tcx> {
     }
 }
 
-impl<'mir, 'tcx> ThreadState<'mir, 'tcx> {
+impl<'tcx> ThreadState<'tcx> {
     fn is_enabled(&self) -> bool {
         matches!(self, ThreadState::Enabled)
     }
@@ -180,7 +229,7 @@ enum ThreadJoinStatus {
 
 /// A thread.
 pub struct Thread<'mir, 'tcx> {
-    state: ThreadState<'mir, 'tcx>,
+    state: ThreadState<'tcx>,
 
     /// Name of the thread.
     thread_name: Option<Vec<u8>>,
@@ -582,26 +631,18 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
             self.block_thread(
                 BlockReason::Join(joined_thread_id),
                 None,
-                Callback { joined_thread_id },
-            );
-
-            struct Callback {
-                joined_thread_id: ThreadId,
-            }
-            impl VisitProvenance for Callback {
-                fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
-            }
-            impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback {
-                fn unblock(
-                    self: Box<Self>,
-                    this: &mut MiriInterpCx<'mir, 'tcx>,
-                ) -> InterpResult<'tcx> {
-                    if let Some(data_race) = &mut this.machine.data_race {
-                        data_race.thread_joined(&this.machine.threads, self.joined_thread_id);
+                callback!(
+                    @capture<'tcx> {
+                        joined_thread_id: ThreadId,
                     }
-                    Ok(())
-                }
-            }
+                    @unblock = |this| {
+                        if let Some(data_race) = &mut this.machine.data_race {
+                            data_race.thread_joined(&this.machine.threads, joined_thread_id);
+                        }
+                        Ok(())
+                    }
+                ),
+            );
         } else {
             // The thread has already terminated - establish happens-before
             if let Some(data_race) = data_race {
@@ -656,7 +697,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
         &mut self,
         reason: BlockReason,
         timeout: Option<Timeout>,
-        callback: impl UnblockCallback<'mir, 'tcx> + 'tcx,
+        callback: impl UnblockCallback<'tcx> + 'tcx,
     ) {
         let state = &mut self.threads[self.active_thread].state;
         assert!(state.is_enabled());
@@ -963,7 +1004,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         &mut self,
         reason: BlockReason,
         timeout: Option<Timeout>,
-        callback: impl UnblockCallback<'mir, 'tcx> + 'tcx,
+        callback: impl UnblockCallback<'tcx> + 'tcx,
     ) {
         let this = self.eval_context_mut();
         if !this.machine.communicate() && matches!(timeout, Some(Timeout::RealTime(..))) {
diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs
index ecd614bf467..5cc14e25ebd 100644
--- a/src/tools/miri/src/provenance_gc.rs
+++ b/src/tools/miri/src/provenance_gc.rs
@@ -10,6 +10,18 @@ pub trait VisitProvenance {
     fn visit_provenance(&self, visit: &mut VisitWith<'_>);
 }
 
+// Trivial impls for types that do not contain any provenance
+macro_rules! no_provenance {
+    ($($ty:ident)+) => {
+        $(
+            impl VisitProvenance for $ty {
+                fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
+            }
+        )+
+    }
+}
+no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize ThreadId);
+
 impl<T: VisitProvenance> VisitProvenance for Option<T> {
     fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
         if let Some(x) = self {
diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs
index a99006f3970..5c43563fac4 100644
--- a/src/tools/miri/src/shims/time.rs
+++ b/src/tools/miri/src/shims/time.rs
@@ -337,7 +337,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             .unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap());
         let timeout_time = Timeout::Monotonic(timeout_time);
 
-        this.block_thread(BlockReason::Sleep, Some(timeout_time), SleepCallback);
+        this.block_thread(
+            BlockReason::Sleep,
+            Some(timeout_time),
+            callback!(
+                @capture<'tcx> {}
+                @unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
+                @timeout = |_this| { Ok(()) }
+            ),
+        );
         Ok(0)
     }
 
@@ -353,23 +361,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let timeout_time = this.machine.clock.now().checked_add(duration).unwrap();
         let timeout_time = Timeout::Monotonic(timeout_time);
 
-        this.block_thread(BlockReason::Sleep, Some(timeout_time), SleepCallback);
+        this.block_thread(
+            BlockReason::Sleep,
+            Some(timeout_time),
+            callback!(
+                @capture<'tcx> {}
+                @unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
+                @timeout = |_this| { Ok(()) }
+            ),
+        );
         Ok(())
     }
 }
-
-struct SleepCallback;
-impl VisitProvenance for SleepCallback {
-    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
-}
-impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for SleepCallback {
-    fn timeout(self: Box<Self>, _this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-        Ok(())
-    }
-    fn unblock(
-        self: Box<Self>,
-        _this: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-    ) -> InterpResult<'tcx> {
-        panic!("a sleeping thread should only ever be woken up via the timeout")
-    }
-}
diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs
index 1e71fc92400..e4307c33bb4 100644
--- a/src/tools/miri/src/shims/windows/sync.rs
+++ b/src/tools/miri/src/shims/windows/sync.rs
@@ -76,28 +76,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         }
 
         // We have to block, and then try again when we are woken up.
-        this.init_once_enqueue_and_block(id, Callback { id, pending_place, dest: dest.clone() });
+        let dest = dest.clone();
+        this.init_once_enqueue_and_block(
+            id,
+            callback!(
+                @capture<'tcx> {
+                    id: InitOnceId,
+                    pending_place: MPlaceTy<'tcx, Provenance>,
+                    dest: MPlaceTy<'tcx, Provenance>,
+                }
+                @unblock = |this| {
+                    let ret = this.init_once_try_begin(id, &pending_place, &dest)?;
+                    assert!(ret, "we were woken up but init_once_try_begin still failed");
+                    Ok(())
+                }
+            ),
+        );
         return Ok(());
-
-        struct Callback<'tcx> {
-            id: InitOnceId,
-            pending_place: MPlaceTy<'tcx, Provenance>,
-            dest: MPlaceTy<'tcx, Provenance>,
-        }
-        impl<'tcx> VisitProvenance for Callback<'tcx> {
-            fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-                let Callback { id: _, dest, pending_place } = self;
-                pending_place.visit_provenance(visit);
-                dest.visit_provenance(visit);
-            }
-        }
-        impl<'mir, 'tcx> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
-            fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
-                let ret = this.init_once_try_begin(self.id, &self.pending_place, &self.dest)?;
-                assert!(ret, "we were woken up but init_once_try_begin still failed");
-                Ok(())
-            }
-        }
     }
 
     fn InitOnceComplete(