about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/concurrency/init_once.rs98
-rw-r--r--src/tools/miri/src/concurrency/sync.rs136
-rw-r--r--src/tools/miri/src/concurrency/thread.rs4
-rw-r--r--src/tools/miri/src/lib.rs6
-rw-r--r--src/tools/miri/src/machine.rs5
-rw-r--r--src/tools/miri/src/shims/unix/macos/sync.rs5
-rw-r--r--src/tools/miri/src/shims/unix/sync.rs41
-rw-r--r--src/tools/miri/src/shims/windows/sync.rs37
8 files changed, 148 insertions, 184 deletions
diff --git a/src/tools/miri/src/concurrency/init_once.rs b/src/tools/miri/src/concurrency/init_once.rs
index c26384f65f6..165215f9270 100644
--- a/src/tools/miri/src/concurrency/init_once.rs
+++ b/src/tools/miri/src/concurrency/init_once.rs
@@ -1,13 +1,11 @@
+use std::cell::RefCell;
 use std::collections::VecDeque;
-
-use rustc_index::Idx;
+use std::rc::Rc;
 
 use super::thread::DynUnblockCallback;
 use super::vector_clock::VClock;
 use crate::*;
 
-super::sync::declare_id!(InitOnceId);
-
 #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
 /// The current status of a one time initialization.
 pub enum InitOnceStatus {
@@ -25,44 +23,70 @@ pub(super) struct InitOnce {
     clock: VClock,
 }
 
-impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
-pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+impl InitOnce {
     #[inline]
-    fn init_once_status(&mut self, id: InitOnceId) -> InitOnceStatus {
-        let this = self.eval_context_ref();
-        this.machine.sync.init_onces[id].status
-    }
-
-    /// Put the thread into the queue waiting for the initialization.
-    #[inline]
-    fn init_once_enqueue_and_block(&mut self, id: InitOnceId, callback: DynUnblockCallback<'tcx>) {
-        let this = self.eval_context_mut();
-        let thread = this.active_thread();
-        let init_once = &mut this.machine.sync.init_onces[id];
-        assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
-        init_once.waiters.push_back(thread);
-        this.block_thread(BlockReason::InitOnce(id), None, callback);
+    pub fn status(&self) -> InitOnceStatus {
+        self.status
     }
 
     /// Begin initializing this InitOnce. Must only be called after checking that it is currently
     /// uninitialized.
     #[inline]
-    fn init_once_begin(&mut self, id: InitOnceId) {
-        let this = self.eval_context_mut();
-        let init_once = &mut this.machine.sync.init_onces[id];
+    pub fn begin(&mut self) {
         assert_eq!(
-            init_once.status,
+            self.status(),
             InitOnceStatus::Uninitialized,
             "beginning already begun or complete init once"
         );
-        init_once.status = InitOnceStatus::Begun;
+        self.status = InitOnceStatus::Begun;
     }
+}
 
+#[derive(Default, Clone, Debug)]
+pub struct InitOnceRef(Rc<RefCell<InitOnce>>);
+
+impl InitOnceRef {
+    pub fn new() -> Self {
+        Self(Default::default())
+    }
+
+    pub fn status(&self) -> InitOnceStatus {
+        self.0.borrow().status()
+    }
+
+    pub fn begin(&self) {
+        self.0.borrow_mut().begin();
+    }
+}
+
+impl VisitProvenance for InitOnceRef {
+    // InitOnce contains no provenance.
+    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
+}
+
+impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
+pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    /// Put the thread into the queue waiting for the initialization.
     #[inline]
-    fn init_once_complete(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
+    fn init_once_enqueue_and_block(
+        &mut self,
+        init_once_ref: InitOnceRef,
+        callback: DynUnblockCallback<'tcx>,
+    ) {
         let this = self.eval_context_mut();
-        let init_once = &mut this.machine.sync.init_onces[id];
+        let thread = this.active_thread();
+        let mut init_once = init_once_ref.0.borrow_mut();
+        assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
+
+        init_once.waiters.push_back(thread);
+        this.block_thread(BlockReason::InitOnce, None, callback);
+    }
 
+    #[inline]
+    fn init_once_complete(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let mut init_once = init_once_ref.0.borrow_mut();
         assert_eq!(
             init_once.status,
             InitOnceStatus::Begun,
@@ -79,17 +103,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         // Wake up everyone.
         // need to take the queue to avoid having `this` be borrowed multiple times
-        for waiter in std::mem::take(&mut init_once.waiters) {
-            this.unblock_thread(waiter, BlockReason::InitOnce(id))?;
+        let waiters = std::mem::take(&mut init_once.waiters);
+        drop(init_once);
+        for waiter in waiters {
+            this.unblock_thread(waiter, BlockReason::InitOnce)?;
         }
 
         interp_ok(())
     }
 
     #[inline]
-    fn init_once_fail(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
+    fn init_once_fail(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let init_once = &mut this.machine.sync.init_onces[id];
+        let mut init_once = init_once_ref.0.borrow_mut();
         assert_eq!(
             init_once.status,
             InitOnceStatus::Begun,
@@ -106,7 +132,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         // Wake up one waiting thread, so they can go ahead and try to init this.
         if let Some(waiter) = init_once.waiters.pop_front() {
-            this.unblock_thread(waiter, BlockReason::InitOnce(id))?;
+            drop(init_once);
+            this.unblock_thread(waiter, BlockReason::InitOnce)?;
         }
 
         interp_ok(())
@@ -115,15 +142,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// Synchronize with the previous completion of an InitOnce.
     /// Must only be called after checking that it is complete.
     #[inline]
-    fn init_once_observe_completed(&mut self, id: InitOnceId) {
+    fn init_once_observe_completed(&mut self, init_once_ref: &InitOnceRef) {
         let this = self.eval_context_mut();
+        let init_once = init_once_ref.0.borrow();
 
         assert_eq!(
-            this.init_once_status(id),
+            init_once.status,
             InitOnceStatus::Complete,
             "observing the completion of incomplete init once"
         );
 
-        this.acquire_clock(&this.machine.sync.init_onces[id].clock);
+        this.acquire_clock(&init_once.clock);
     }
 }
diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs
index 74379d6438d..179094db0fe 100644
--- a/src/tools/miri/src/concurrency/sync.rs
+++ b/src/tools/miri/src/concurrency/sync.rs
@@ -8,45 +8,10 @@ use std::time::Duration;
 
 use rustc_abi::Size;
 use rustc_data_structures::fx::FxHashMap;
-use rustc_index::{Idx, IndexVec};
 
-use super::init_once::InitOnce;
 use super::vector_clock::VClock;
 use crate::*;
 
-/// We cannot use the `newtype_index!` macro because we have to use 0 as a
-/// sentinel value meaning that the identifier is not assigned. This is because
-/// the pthreads static initializers initialize memory with zeros (see the
-/// `src/shims/sync.rs` file).
-macro_rules! declare_id {
-    ($name: ident) => {
-        /// 0 is used to indicate that the id was not yet assigned and,
-        /// therefore, is not a valid identifier.
-        #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
-        pub struct $name(std::num::NonZero<u32>);
-
-        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,
-                // therefore, need to shift by one when converting from an index
-                // into a vector.
-                let shifted_idx = u32::try_from(idx).unwrap().strict_add(1);
-                $name(std::num::NonZero::new(shifted_idx).unwrap())
-            }
-            fn index(self) -> usize {
-                // See the comment in `Self::new`.
-                // (This cannot underflow because `self.0` is `NonZero<u32>`.)
-                usize::try_from(self.0.get() - 1).unwrap()
-            }
-        }
-    };
-}
-pub(super) use declare_id;
-
 /// The mutex state.
 #[derive(Default, Debug)]
 struct Mutex {
@@ -64,8 +29,8 @@ struct Mutex {
 pub struct MutexRef(Rc<RefCell<Mutex>>);
 
 impl MutexRef {
-    fn new() -> Self {
-        MutexRef(Rc::new(RefCell::new(Mutex::default())))
+    pub fn new() -> Self {
+        Self(Default::default())
     }
 
     /// Get the id of the thread that currently owns this lock, or `None` if it is not locked.
@@ -75,9 +40,8 @@ impl MutexRef {
 }
 
 impl VisitProvenance for MutexRef {
-    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
-        // Mutex contains no provenance.
-    }
+    // Mutex contains no provenance.
+    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
 }
 
 /// The read-write lock state.
@@ -138,8 +102,8 @@ impl RwLock {
 pub struct RwLockRef(Rc<RefCell<RwLock>>);
 
 impl RwLockRef {
-    fn new() -> Self {
-        RwLockRef(Rc::new(RefCell::new(RwLock::default())))
+    pub fn new() -> Self {
+        Self(Default::default())
     }
 
     pub fn is_locked(&self) -> bool {
@@ -152,13 +116,10 @@ impl RwLockRef {
 }
 
 impl VisitProvenance for RwLockRef {
-    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
-        // RwLockRef contains no provenance.
-    }
+    // RwLock contains no provenance.
+    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
 }
 
-declare_id!(CondvarId);
-
 /// The conditional variable state.
 #[derive(Default, Debug)]
 struct Condvar {
@@ -171,6 +132,24 @@ struct Condvar {
     clock: VClock,
 }
 
+#[derive(Default, Clone, Debug)]
+pub struct CondvarRef(Rc<RefCell<Condvar>>);
+
+impl CondvarRef {
+    pub fn new() -> Self {
+        Self(Default::default())
+    }
+
+    pub fn is_awaited(&self) -> bool {
+        !self.0.borrow().waiters.is_empty()
+    }
+}
+
+impl VisitProvenance for CondvarRef {
+    // Condvar contains no provenance.
+    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
+}
+
 /// The futex state.
 #[derive(Default, Debug)]
 struct Futex {
@@ -183,19 +162,22 @@ struct Futex {
     clock: VClock,
 }
 
-#[derive(Default, Clone)]
+#[derive(Default, Clone, Debug)]
 pub struct FutexRef(Rc<RefCell<Futex>>);
 
 impl FutexRef {
+    pub fn new() -> Self {
+        Self(Default::default())
+    }
+
     pub fn waiters(&self) -> usize {
         self.0.borrow().waiters.len()
     }
 }
 
 impl VisitProvenance for FutexRef {
-    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
-        // No provenance in `Futex`.
-    }
+    // Futex contains no provenance.
+    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
 }
 
 /// A thread waiting on a futex.
@@ -207,13 +189,6 @@ struct FutexWaiter {
     bitset: u32,
 }
 
-/// The state of all synchronization objects.
-#[derive(Default, Debug)]
-pub struct SynchronizationObjects {
-    condvars: IndexVec<CondvarId, Condvar>,
-    pub(super) init_onces: IndexVec<InitOnceId, InitOnce>,
-}
-
 // Private extension trait for local helper methods
 impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
@@ -237,23 +212,6 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
     }
 }
 
-impl SynchronizationObjects {
-    pub fn mutex_create(&mut self) -> MutexRef {
-        MutexRef::new()
-    }
-    pub fn rwlock_create(&mut self) -> RwLockRef {
-        RwLockRef::new()
-    }
-
-    pub fn condvar_create(&mut self) -> CondvarId {
-        self.condvars.push(Default::default())
-    }
-
-    pub fn init_once_create(&mut self) -> InitOnceId {
-        self.init_onces.push(Default::default())
-    }
-}
-
 impl<'tcx> AllocExtra<'tcx> {
     fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
         self.sync.get(&offset).and_then(|s| s.downcast_ref::<T>())
@@ -663,19 +621,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         );
     }
 
-    /// Is the conditional variable awaited?
-    #[inline]
-    fn condvar_is_awaited(&mut self, id: CondvarId) -> bool {
-        let this = self.eval_context_mut();
-        !this.machine.sync.condvars[id].waiters.is_empty()
-    }
-
     /// Release the mutex and let the current thread wait on the given condition variable.
     /// Once it is signaled, the mutex will be acquired and `retval_succ` will be written to `dest`.
     /// If the timeout happens first, `retval_timeout` will be written to `dest`.
     fn condvar_wait(
         &mut self,
-        condvar: CondvarId,
+        condvar_ref: CondvarRef,
         mutex_ref: MutexRef,
         timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
         retval_succ: Scalar,
@@ -695,14 +646,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             );
         }
         let thread = this.active_thread();
-        let waiters = &mut this.machine.sync.condvars[condvar].waiters;
-        waiters.push_back(thread);
+
+        condvar_ref.0.borrow_mut().waiters.push_back(thread);
         this.block_thread(
-            BlockReason::Condvar(condvar),
+            BlockReason::Condvar,
             timeout,
             callback!(
                 @capture<'tcx> {
-                    condvar: CondvarId,
+                    condvar_ref: CondvarRef,
                     mutex_ref: MutexRef,
                     retval_succ: Scalar,
                     retval_timeout: Scalar,
@@ -714,7 +665,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                             // The condvar was signaled. Make sure we get the clock for that.
                             if let Some(data_race) = this.machine.data_race.as_vclocks_ref() {
                                 data_race.acquire_clock(
-                                    &this.machine.sync.condvars[condvar].clock,
+                                    &condvar_ref.0.borrow().clock,
                                     &this.machine.threads,
                                 );
                             }
@@ -725,7 +676,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                         UnblockKind::TimedOut => {
                             // We have to remove the waiter from the queue again.
                             let thread = this.active_thread();
-                            let waiters = &mut this.machine.sync.condvars[condvar].waiters;
+                            let waiters = &mut condvar_ref.0.borrow_mut().waiters;
                             waiters.retain(|waiter| *waiter != thread);
                             // Now get back the lock.
                             this.condvar_reacquire_mutex(mutex_ref, retval_timeout, dest)
@@ -739,9 +690,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
     /// Wake up some thread (if there is any) sleeping on the conditional
     /// variable. Returns `true` iff any thread was woken up.
-    fn condvar_signal(&mut self, id: CondvarId) -> InterpResult<'tcx, bool> {
+    fn condvar_signal(&mut self, condvar_ref: &CondvarRef) -> InterpResult<'tcx, bool> {
         let this = self.eval_context_mut();
-        let condvar = &mut this.machine.sync.condvars[id];
+        let mut condvar = condvar_ref.0.borrow_mut();
 
         // Each condvar signal happens-before the end of the condvar wake
         if let Some(data_race) = this.machine.data_race.as_vclocks_ref() {
@@ -750,7 +701,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let Some(waiter) = condvar.waiters.pop_front() else {
             return interp_ok(false);
         };
-        this.unblock_thread(waiter, BlockReason::Condvar(id))?;
+        drop(condvar);
+        this.unblock_thread(waiter, BlockReason::Condvar)?;
         interp_ok(true)
     }
 
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index c8a408fd8ac..56c19794854 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -97,13 +97,13 @@ pub enum BlockReason {
     /// Blocked on a mutex.
     Mutex,
     /// Blocked on a condition variable.
-    Condvar(CondvarId),
+    Condvar,
     /// Blocked on a reader-writer lock.
     RwLock,
     /// Blocked on a Futex variable.
     Futex,
     /// Blocked on an InitOnce.
-    InitOnce(InitOnceId),
+    InitOnce,
     /// Blocked on epoll.
     Epoll,
     /// Blocked on eventfd.
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 00f26a37f71..9c574688ce7 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -128,10 +128,8 @@ pub use crate::concurrency::cpu_affinity::MAX_CPUS;
 pub use crate::concurrency::data_race::{
     AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _,
 };
-pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceId};
-pub use crate::concurrency::sync::{
-    CondvarId, EvalContextExt as _, MutexRef, RwLockRef, SynchronizationObjects,
-};
+pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceRef};
+pub use crate::concurrency::sync::{CondvarRef, EvalContextExt as _, MutexRef, RwLockRef};
 pub use crate::concurrency::thread::{
     BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId,
     ThreadManager, TimeoutAnchor, TimeoutClock, UnblockKind,
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 17c13a9e33c..014b83922cc 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -499,9 +499,6 @@ pub struct MiriMachine<'tcx> {
     /// in `sched_getaffinity`
     pub(crate) thread_cpu_affinity: FxHashMap<ThreadId, CpuAffinityMask>,
 
-    /// The state of the primitive synchronization objects.
-    pub(crate) sync: SynchronizationObjects,
-
     /// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri.
     pub(crate) layouts: PrimitiveLayouts<'tcx>,
 
@@ -713,7 +710,6 @@ impl<'tcx> MiriMachine<'tcx> {
             layouts,
             threads,
             thread_cpu_affinity,
-            sync: SynchronizationObjects::default(),
             static_roots: Vec::new(),
             profiler,
             string_cache: Default::default(),
@@ -903,7 +899,6 @@ impl VisitProvenance for MiriMachine<'_> {
         let MiriMachine {
             threads,
             thread_cpu_affinity: _,
-            sync: _,
             tls,
             env_vars,
             main_fn_ret_place,
diff --git a/src/tools/miri/src/shims/unix/macos/sync.rs b/src/tools/miri/src/shims/unix/macos/sync.rs
index 19f55e6c917..05616dd5a42 100644
--- a/src/tools/miri/src/shims/unix/macos/sync.rs
+++ b/src/tools/miri/src/shims/unix/macos/sync.rs
@@ -68,10 +68,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // LAZY_INIT_COOKIE). This can't be hit via `std::sync::Mutex`.
                 interp_ok(MacOsUnfairLock::Poisoned)
             },
-            |ecx| {
-                let mutex_ref = ecx.machine.sync.mutex_create();
-                interp_ok(MacOsUnfairLock::Active { mutex_ref })
-            },
+            |_| interp_ok(MacOsUnfairLock::Active { mutex_ref: MutexRef::new() }),
         )
     }
 }
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
index 50eb4d92289..e20e3b79c3b 100644
--- a/src/tools/miri/src/shims/unix/sync.rs
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -171,8 +171,7 @@ fn mutex_create<'tcx>(
     kind: MutexKind,
 ) -> InterpResult<'tcx, PthreadMutex> {
     let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?;
-    let id = ecx.machine.sync.mutex_create();
-    let data = PthreadMutex { mutex_ref: id, kind };
+    let data = PthreadMutex { mutex_ref: MutexRef::new(), kind };
     ecx.lazy_sync_init(&mutex, mutex_init_offset(ecx)?, data.clone())?;
     interp_ok(data)
 }
@@ -193,8 +192,7 @@ where
         || throw_ub_format!("`pthread_mutex_t` can't be moved after first use"),
         |ecx| {
             let kind = mutex_kind_from_static_initializer(ecx, &mutex)?;
-            let id = ecx.machine.sync.mutex_create();
-            interp_ok(PthreadMutex { mutex_ref: id, kind })
+            interp_ok(PthreadMutex { mutex_ref: MutexRef::new(), kind })
         },
     )
 }
@@ -278,8 +276,7 @@ where
             )? {
                 throw_unsup_format!("unsupported static initializer used for `pthread_rwlock_t`");
             }
-            let rwlock_ref = ecx.machine.sync.rwlock_create();
-            interp_ok(PthreadRwLock { rwlock_ref })
+            interp_ok(PthreadRwLock { rwlock_ref: RwLockRef::new() })
         },
     )
 }
@@ -372,9 +369,9 @@ enum ClockId {
     Monotonic,
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
 struct PthreadCondvar {
-    id: CondvarId,
+    condvar_ref: CondvarRef,
     clock: ClockId,
 }
 
@@ -384,9 +381,8 @@ fn cond_create<'tcx>(
     clock: ClockId,
 ) -> InterpResult<'tcx, PthreadCondvar> {
     let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
-    let id = ecx.machine.sync.condvar_create();
-    let data = PthreadCondvar { id, clock };
-    ecx.lazy_sync_init(&cond, cond_init_offset(ecx)?, data)?;
+    let data = PthreadCondvar { condvar_ref: CondvarRef::new(), clock };
+    ecx.lazy_sync_init(&cond, cond_init_offset(ecx)?, data.clone())?;
     interp_ok(data)
 }
 
@@ -411,8 +407,7 @@ where
                 throw_unsup_format!("unsupported static initializer used for `pthread_cond_t`");
             }
             // This used the static initializer. The clock there is always CLOCK_REALTIME.
-            let id = ecx.machine.sync.condvar_create();
-            interp_ok(PthreadCondvar { id, clock: ClockId::Realtime })
+            interp_ok(PthreadCondvar { condvar_ref: CondvarRef::new(), clock: ClockId::Realtime })
         },
     )
 }
@@ -817,15 +812,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
     fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
         let this = self.eval_context_mut();
-        let id = cond_get_data(this, cond_op)?.id;
-        this.condvar_signal(id)?;
+        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
+        this.condvar_signal(&condvar)?;
         interp_ok(())
     }
 
     fn pthread_cond_broadcast(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
         let this = self.eval_context_mut();
-        let id = cond_get_data(this, cond_op)?.id;
-        while this.condvar_signal(id)? {}
+        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
+        while this.condvar_signal(&condvar)? {}
         interp_ok(())
     }
 
@@ -837,11 +832,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
 
-        let data = *cond_get_data(this, cond_op)?;
+        let data = cond_get_data(this, cond_op)?.clone();
         let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
 
         this.condvar_wait(
-            data.id,
+            data.condvar_ref,
             mutex_ref,
             None, // no timeout
             Scalar::from_i32(0),
@@ -861,7 +856,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
 
-        let data = *cond_get_data(this, cond_op)?;
+        let data = cond_get_data(this, cond_op)?.clone();
         let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
 
         // Extract the timeout.
@@ -884,7 +879,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
 
         this.condvar_wait(
-            data.id,
+            data.condvar_ref,
             mutex_ref,
             Some((timeout_clock, TimeoutAnchor::Absolute, duration)),
             Scalar::from_i32(0),
@@ -900,8 +895,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         // Reading the field also has the side-effect that we detect double-`destroy`
         // since we make the field uninit below.
-        let id = cond_get_data(this, cond_op)?.id;
-        if this.condvar_is_awaited(id) {
+        let condvar = &cond_get_data(this, cond_op)?.condvar_ref;
+        if condvar.is_awaited() {
             throw_ub_format!("destroying an awaited conditional variable");
         }
 
diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs
index 8d5ea7db9e4..9165e76b63d 100644
--- a/src/tools/miri/src/shims/windows/sync.rs
+++ b/src/tools/miri/src/shims/windows/sync.rs
@@ -2,13 +2,13 @@ use std::time::Duration;
 
 use rustc_abi::Size;
 
-use crate::concurrency::init_once::InitOnceStatus;
+use crate::concurrency::init_once::{EvalContextExt as _, InitOnceStatus};
 use crate::concurrency::sync::FutexRef;
 use crate::*;
 
-#[derive(Copy, Clone)]
+#[derive(Clone)]
 struct WindowsInitOnce {
-    id: InitOnceId,
+    init_once: InitOnceRef,
 }
 
 struct WindowsFutex {
@@ -37,10 +37,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             &init_once,
             init_offset,
             || throw_ub_format!("`INIT_ONCE` can't be moved after first use"),
-            |this| {
+            |_| {
                 // TODO: check that this is still all-zero.
-                let id = this.machine.sync.init_once_create();
-                interp_ok(WindowsInitOnce { id })
+                interp_ok(WindowsInitOnce { init_once: InitOnceRef::new() })
             },
         )
     }
@@ -48,20 +47,20 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// Returns `true` if we were succssful, `false` if we would block.
     fn init_once_try_begin(
         &mut self,
-        id: InitOnceId,
+        init_once_ref: &InitOnceRef,
         pending_place: &MPlaceTy<'tcx>,
         dest: &MPlaceTy<'tcx>,
     ) -> InterpResult<'tcx, bool> {
         let this = self.eval_context_mut();
-        interp_ok(match this.init_once_status(id) {
+        interp_ok(match init_once_ref.status() {
             InitOnceStatus::Uninitialized => {
-                this.init_once_begin(id);
+                init_once_ref.begin();
                 this.write_scalar(this.eval_windows("c", "TRUE"), pending_place)?;
                 this.write_scalar(this.eval_windows("c", "TRUE"), dest)?;
                 true
             }
             InitOnceStatus::Complete => {
-                this.init_once_observe_completed(id);
+                this.init_once_observe_completed(init_once_ref);
                 this.write_scalar(this.eval_windows("c", "FALSE"), pending_place)?;
                 this.write_scalar(this.eval_windows("c", "TRUE"), dest)?;
                 true
@@ -84,7 +83,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
 
-        let id = this.init_once_get_data(init_once_op)?.id;
+        let init_once = this.init_once_get_data(init_once_op)?.init_once.clone();
         let flags = this.read_scalar(flags_op)?.to_u32()?;
         // PBOOL is int*
         let pending_place = this.deref_pointer_as(pending_op, this.machine.layouts.i32)?;
@@ -98,7 +97,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             throw_unsup_format!("non-null `lpContext` in `InitOnceBeginInitialize`");
         }
 
-        if this.init_once_try_begin(id, &pending_place, dest)? {
+        if this.init_once_try_begin(&init_once, &pending_place, dest)? {
             // Done!
             return interp_ok(());
         }
@@ -106,16 +105,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         // We have to block, and then try again when we are woken up.
         let dest = dest.clone();
         this.init_once_enqueue_and_block(
-            id,
+            init_once.clone(),
             callback!(
                 @capture<'tcx> {
-                    id: InitOnceId,
+                    init_once: InitOnceRef,
                     pending_place: MPlaceTy<'tcx>,
                     dest: MPlaceTy<'tcx>,
                 }
                 |this, unblock: UnblockKind| {
                     assert_eq!(unblock, UnblockKind::Ready);
-                    let ret = this.init_once_try_begin(id, &pending_place, &dest)?;
+                    let ret = this.init_once_try_begin(&init_once, &pending_place, &dest)?;
                     assert!(ret, "we were woken up but init_once_try_begin still failed");
                     interp_ok(())
                 }
@@ -132,7 +131,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
-        let id = this.init_once_get_data(init_once_op)?.id;
+        let init_once = this.init_once_get_data(init_once_op)?.init_once.clone();
         let flags = this.read_scalar(flags_op)?.to_u32()?;
         let context = this.read_pointer(context_op)?;
 
@@ -148,7 +147,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             throw_unsup_format!("non-null `lpContext` in `InitOnceBeginInitialize`");
         }
 
-        if this.init_once_status(id) != InitOnceStatus::Begun {
+        if init_once.status() != InitOnceStatus::Begun {
             // The docs do not say anything about this case, but it seems better to not allow it.
             throw_ub_format!(
                 "calling InitOnceComplete on a one time initialization that has not begun or is already completed"
@@ -156,9 +155,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         }
 
         if success {
-            this.init_once_complete(id)?;
+            this.init_once_complete(&init_once)?;
         } else {
-            this.init_once_fail(id)?;
+            this.init_once_fail(&init_once)?;
         }
 
         interp_ok(this.eval_windows("c", "TRUE"))