Skip to main content

miri/concurrency/
sync.rs

1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::VecDeque;
4use std::collections::hash_map::Entry;
5use std::default::Default;
6use std::ops::Not;
7use std::rc::Rc;
8use std::{fmt, iter};
9
10use rustc_abi::Size;
11use rustc_data_structures::fx::FxHashMap;
12
13use super::vector_clock::VClock;
14use crate::*;
15
16/// Indicates which kind of access is being performed.
17#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
18pub enum AccessKind {
19    Read,
20    Write,
21    Dealloc,
22}
23
24impl fmt::Display for AccessKind {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            AccessKind::Read => write!(f, "read"),
28            AccessKind::Write => write!(f, "write"),
29            AccessKind::Dealloc => write!(f, "deallocation"),
30        }
31    }
32}
33
34/// A trait for the synchronization metadata that can be attached to a memory location.
35pub trait SyncObj: Any {
36    /// Determines whether reads/writes to this object's location are currently permitted.
37    fn on_access<'tcx>(&self, _access_kind: AccessKind) -> InterpResult<'tcx> {
38        interp_ok(())
39    }
40
41    /// Determines whether this object's metadata shall be deleted when a write to its
42    /// location occurs.
43    fn delete_on_write(&self) -> bool {
44        false
45    }
46}
47
48impl dyn SyncObj {
49    #[inline(always)]
50    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
51        let x: &dyn Any = self;
52        x.downcast_ref()
53    }
54}
55
56impl fmt::Debug for dyn SyncObj {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.debug_struct("SyncObj").finish_non_exhaustive()
59    }
60}
61
62/// The mutex state.
63#[derive(Default, Debug)]
64struct Mutex {
65    /// The thread that currently owns the lock.
66    owner: Option<ThreadId>,
67    /// How many times the mutex was locked by the owner.
68    lock_count: usize,
69    /// The queue of threads waiting for this mutex.
70    queue: VecDeque<ThreadId>,
71    /// Mutex clock. This tracks the moment of the last unlock.
72    clock: VClock,
73}
74
75#[derive(Default, Clone, Debug)]
76pub struct MutexRef(Rc<RefCell<Mutex>>);
77
78impl MutexRef {
79    pub fn new() -> Self {
80        Self(Default::default())
81    }
82
83    /// Get the id of the thread that currently owns this lock, or `None` if it is not locked.
84    pub fn owner(&self) -> Option<ThreadId> {
85        self.0.borrow().owner
86    }
87
88    pub fn queue_is_empty(&self) -> bool {
89        self.0.borrow().queue.is_empty()
90    }
91}
92
93impl VisitProvenance for MutexRef {
94    // Mutex contains no provenance.
95    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
96}
97
98/// The read-write lock state.
99#[derive(Default, Debug)]
100struct RwLock {
101    /// The writer thread that currently owns the lock.
102    writer: Option<ThreadId>,
103    /// The readers that currently own the lock and how many times they acquired
104    /// the lock.
105    readers: FxHashMap<ThreadId, usize>,
106    /// The queue of writer threads waiting for this lock.
107    writer_queue: VecDeque<ThreadId>,
108    /// The queue of reader threads waiting for this lock.
109    reader_queue: VecDeque<ThreadId>,
110    /// Data race clock for writers. Tracks the happens-before
111    /// ordering between each write access to a rwlock and is updated
112    /// after a sequence of concurrent readers to track the happens-
113    /// before ordering between the set of previous readers and
114    /// the current writer.
115    /// Contains the clock of the last thread to release a writer
116    /// lock or the joined clock of the set of last threads to release
117    /// shared reader locks.
118    clock_unlocked: VClock,
119    /// Data race clock for readers. This is temporary storage
120    /// for the combined happens-before ordering for between all
121    /// concurrent readers and the next writer, and the value
122    /// is stored to the main data_race variable once all
123    /// readers are finished.
124    /// Has to be stored separately since reader lock acquires
125    /// must load the clock of the last write and must not
126    /// add happens-before orderings between shared reader
127    /// locks.
128    /// This is only relevant when there is an active reader.
129    clock_current_readers: VClock,
130}
131
132impl RwLock {
133    #[inline]
134    /// Check if locked.
135    fn is_locked(&self) -> bool {
136        trace!(
137            "rwlock_is_locked: writer is {:?} and there are {} reader threads (some of which could hold multiple read locks)",
138            self.writer,
139            self.readers.len(),
140        );
141        self.writer.is_some() || self.readers.is_empty().not()
142    }
143
144    /// Check if write locked.
145    #[inline]
146    fn is_write_locked(&self) -> bool {
147        trace!("rwlock_is_write_locked: writer is {:?}", self.writer);
148        self.writer.is_some()
149    }
150}
151
152#[derive(Default, Clone, Debug)]
153pub struct RwLockRef(Rc<RefCell<RwLock>>);
154
155impl RwLockRef {
156    pub fn new() -> Self {
157        Self(Default::default())
158    }
159
160    pub fn is_locked(&self) -> bool {
161        self.0.borrow().is_locked()
162    }
163
164    pub fn is_write_locked(&self) -> bool {
165        self.0.borrow().is_write_locked()
166    }
167
168    pub fn queue_is_empty(&self) -> bool {
169        let inner = self.0.borrow();
170        inner.reader_queue.is_empty() && inner.writer_queue.is_empty()
171    }
172}
173
174impl VisitProvenance for RwLockRef {
175    // RwLock contains no provenance.
176    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
177}
178
179/// The conditional variable state.
180#[derive(Default, Debug)]
181struct Condvar {
182    waiters: VecDeque<ThreadId>,
183    /// Tracks the happens-before relationship
184    /// between a cond-var signal and a cond-var
185    /// wait during a non-spurious signal event.
186    /// Contains the clock of the last thread to
187    /// perform a condvar-signal.
188    clock: VClock,
189}
190
191#[derive(Default, Clone, Debug)]
192pub struct CondvarRef(Rc<RefCell<Condvar>>);
193
194impl CondvarRef {
195    pub fn new() -> Self {
196        Self(Default::default())
197    }
198
199    pub fn queue_is_empty(&self) -> bool {
200        self.0.borrow().waiters.is_empty()
201    }
202}
203
204impl VisitProvenance for CondvarRef {
205    // Condvar contains no provenance.
206    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
207}
208
209/// The semaphore state.
210#[derive(Default, Debug)]
211struct Semaphore {
212    waiters: VecDeque<ThreadId>,
213    value: u64,
214    /// The joint data race clock of the most recent semaphore increment.
215    clock: VClock,
216}
217
218#[derive(Default, Clone, Debug)]
219pub struct SemaphoreRef(Rc<RefCell<Semaphore>>);
220
221impl SemaphoreRef {
222    pub fn new(value: u64) -> Self {
223        Self(Rc::new(RefCell::new(Semaphore { value, ..Default::default() })))
224    }
225
226    pub fn value(&self) -> u64 {
227        self.0.borrow().value
228    }
229
230    pub fn queue_is_empty(&self) -> bool {
231        self.0.borrow().waiters.is_empty()
232    }
233}
234
235impl VisitProvenance for SemaphoreRef {
236    // Semaphore contains no provenance.
237    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
238}
239
240/// The futex state.
241#[derive(Default, Debug)]
242struct Futex {
243    waiters: Vec<FutexWaiter>,
244    /// Tracks the happens-before relationship
245    /// between a futex-wake and a futex-wait
246    /// during a non-spurious wake event.
247    /// Contains the clock of the last thread to
248    /// perform a futex-wake.
249    clock: VClock,
250}
251
252#[derive(Default, Clone, Debug)]
253pub struct FutexRef(Rc<RefCell<Futex>>);
254
255impl FutexRef {
256    pub fn new() -> Self {
257        Self(Default::default())
258    }
259
260    pub fn waiters(&self) -> usize {
261        self.0.borrow().waiters.len()
262    }
263}
264
265impl VisitProvenance for FutexRef {
266    // Futex contains no provenance.
267    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
268}
269
270/// A thread waiting on a futex.
271#[derive(Debug)]
272struct FutexWaiter {
273    /// The thread that is waiting on this futex.
274    thread: ThreadId,
275    /// The bitset used by FUTEX_*_BITSET, or u32::MAX for other operations.
276    bitset: u32,
277}
278
279// Private extension trait for local helper methods
280impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
281pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
282    fn condvar_reacquire_mutex(
283        &mut self,
284        mutex_ref: MutexRef,
285        retval: Scalar,
286        dest: MPlaceTy<'tcx>,
287    ) -> InterpResult<'tcx> {
288        let this = self.eval_context_mut();
289        if let Some(owner) = mutex_ref.owner() {
290            assert_ne!(owner, this.active_thread());
291            this.mutex_enqueue_and_block(mutex_ref, Some((retval, dest)));
292        } else {
293            // We can have it right now!
294            this.mutex_lock(&mutex_ref)?;
295            // Don't forget to write the return value.
296            this.write_scalar(retval, &dest)?;
297        }
298        interp_ok(())
299    }
300}
301
302impl<'tcx> AllocExtra<'tcx> {
303    fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
304        self.sync_objs.get(&offset).and_then(|s| s.downcast_ref::<T>())
305    }
306}
307
308// Public interface to synchronization objects. Please note that in most
309// cases, the function calls are infallible and it is the client's (shim
310// implementation's) responsibility to detect and deal with erroneous
311// situations.
312impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
313pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
314    /// Get the synchronization object associated with the given pointer,
315    /// or initialize a new one.
316    ///
317    /// Return `None` if this pointer does not point to at least 1 byte of mutable memory.
318    fn get_sync_or_init<'a, T: SyncObj>(
319        &'a mut self,
320        ptr: Pointer,
321        new: impl FnOnce(&'a mut MiriMachine<'tcx>) -> T,
322    ) -> Option<&'a T>
323    where
324        'tcx: 'a,
325    {
326        let this = self.eval_context_mut();
327        if !this.ptr_try_get_alloc_id(ptr, 0).ok().is_some_and(|(alloc_id, offset, ..)| {
328            let info = this.get_alloc_info(alloc_id);
329            info.kind == AllocKind::LiveData && info.mutbl.is_mut() && offset < info.size
330        }) {
331            return None;
332        }
333        // This cannot fail now.
334        let (alloc, offset, _) = this.ptr_get_alloc_id(ptr, 0).unwrap();
335        let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc).unwrap();
336        // Due to borrow checker reasons, we have to do the lookup twice.
337        if alloc_extra.get_sync::<T>(offset).is_none() {
338            let new = new(machine);
339            alloc_extra.sync_objs.insert(offset, Box::new(new));
340        }
341        Some(alloc_extra.get_sync::<T>(offset).unwrap())
342    }
343
344    /// Helper for "immovable" synchronization objects: the expected protocol for these objects is
345    /// that they use a static initializer of `uninit_val`, and we set them to `init_val` upon
346    /// initialization. At that point we also register a synchronization object, which is expected
347    /// to have `delete_on_write() == true`. So in the future, if we still see the object, we know
348    /// the location must still contain `init_val`. If the object is copied somewhere, that will
349    /// show up as a non-`init_val` value without a synchronization object, which we can then use to
350    /// error.
351    ///
352    /// `new_meta_obj` gets invoked when there is not yet an initialization object.
353    /// It has to ensure that the in-memory representation indeed matches `uninit_val`.
354    ///
355    /// The point of storing an `init_val` is so that if this memory gets copied somewhere else,
356    /// it does not look like the static initializer (i.e., `uninit_val`) any more. For some
357    /// objects we could just entirely forbid reading their bytes to ensure they don't get copied,
358    /// but that does not work for objects without a destructor (Windows `InitOnce`, macOS
359    /// `os_unfair_lock`).
360    fn get_immovable_sync_with_static_init<'a, T: SyncObj>(
361        &'a mut self,
362        obj: &MPlaceTy<'tcx>,
363        init_offset: Size,
364        uninit_val: u8,
365        init_val: u8,
366        new_meta_obj: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, T>,
367    ) -> InterpResult<'tcx, &'a T>
368    where
369        'tcx: 'a,
370    {
371        assert!(init_val != uninit_val);
372        let this = self.eval_context_mut();
373        this.check_ptr_access(
374            obj.ptr(),
375            obj.layout.size,
376            CheckInAllocMsg::Dereferenceable("pointer"),
377        )?;
378        assert!(init_offset < obj.layout.size); // ensure our 1-byte flag fits
379        let init_field = obj.offset(init_offset, this.machine.layouts.u8, this)?;
380
381        let (alloc, offset, _) = this.ptr_get_alloc_id(init_field.ptr(), 0)?;
382        let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?;
383        // Due to borrow checker reasons, we have to do the lookup twice.
384        if alloc_extra.get_sync::<T>(offset).is_some() {
385            let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
386            return interp_ok(alloc_extra.get_sync::<T>(offset).unwrap());
387        }
388
389        // There's no sync object there yet. Create one, and try a CAS for uninit_val to init_val.
390        let meta_obj = new_meta_obj(this)?;
391        let (old_init, success) = this.atomic_compare_exchange(
392            &init_field,
393            &ImmTy::from_scalar(Scalar::from_u8(uninit_val), this.machine.layouts.u8),
394            Scalar::from_u8(init_val),
395            AtomicRwOrd::Relaxed,
396            AtomicReadOrd::Relaxed,
397            /* can_fail_spuriously */ false,
398        )?;
399        if !success {
400            // This can happen for the macOS lock if it is already marked as initialized.
401            assert_eq!(
402                old_init.to_u8()?,
403                init_val,
404                "`new_meta_obj` should have ensured that this CAS succeeds"
405            );
406        }
407
408        let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
409        assert!(meta_obj.delete_on_write());
410        alloc_extra.sync_objs.insert(offset, Box::new(meta_obj));
411        interp_ok(alloc_extra.get_sync::<T>(offset).unwrap())
412    }
413
414    /// Explicitly initializes an object that would usually be implicitly initialized with
415    /// `get_immovable_sync_with_static_init`.
416    fn init_immovable_sync<'a, T: SyncObj>(
417        &'a mut self,
418        obj: &MPlaceTy<'tcx>,
419        init_offset: Size,
420        init_val: u8,
421        new_meta_obj: T,
422    ) -> InterpResult<'tcx, Option<&'a T>>
423    where
424        'tcx: 'a,
425    {
426        let this = self.eval_context_mut();
427        this.check_ptr_access(
428            obj.ptr(),
429            obj.layout.size,
430            CheckInAllocMsg::Dereferenceable("pointer"),
431        )?;
432        assert!(init_offset < obj.layout.size); // ensure our 1-byte flag fits
433        let init_field = obj.offset(init_offset, this.machine.layouts.u8, this)?;
434
435        // Zero the entire object, and then store `init_val` directly.
436        this.write_bytes_ptr(obj.ptr(), iter::repeat_n(0, obj.layout.size.bytes_usize()))?;
437        this.write_scalar(Scalar::from_u8(init_val), &init_field)?;
438
439        // Create meta-level initialization object.
440        let (alloc, offset, _) = this.ptr_get_alloc_id(init_field.ptr(), 0)?;
441        let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
442        assert!(new_meta_obj.delete_on_write());
443        alloc_extra.sync_objs.insert(offset, Box::new(new_meta_obj));
444        interp_ok(Some(alloc_extra.get_sync::<T>(offset).unwrap()))
445    }
446
447    /// Lock by setting the mutex owner and increasing the lock count.
448    fn mutex_lock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx> {
449        let this = self.eval_context_mut();
450        let thread = this.active_thread();
451        let mut mutex = mutex_ref.0.borrow_mut();
452        if let Some(current_owner) = mutex.owner {
453            assert_eq!(thread, current_owner, "mutex already locked by another thread");
454            assert!(
455                mutex.lock_count > 0,
456                "invariant violation: lock_count == 0 iff the thread is unlocked"
457            );
458        } else {
459            mutex.owner = Some(thread);
460        }
461        mutex.lock_count = mutex.lock_count.strict_add(1);
462        this.acquire_clock(&mutex.clock)?;
463        interp_ok(())
464    }
465
466    /// Try unlocking by decreasing the lock count and returning the old lock
467    /// count. If the lock count reaches 0, release the lock and potentially
468    /// give to a new owner. If the lock was not locked by the current thread,
469    /// return `None`.
470    fn mutex_unlock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx, Option<usize>> {
471        let this = self.eval_context_mut();
472        let mut mutex = mutex_ref.0.borrow_mut();
473        interp_ok(if let Some(current_owner) = mutex.owner {
474            // Mutex is locked.
475            if current_owner != this.machine.threads.active_thread() {
476                // Only the owner can unlock the mutex.
477                return interp_ok(None);
478            }
479            let old_lock_count = mutex.lock_count;
480            mutex.lock_count = old_lock_count.strict_sub(1);
481            if mutex.lock_count == 0 {
482                mutex.owner = None;
483                // The mutex is completely unlocked. Try transferring ownership
484                // to another thread.
485
486                this.release_clock(|clock| mutex.clock.clone_from(clock))?;
487                let thread_id = mutex.queue.pop_front();
488                // We need to drop our mutex borrow before unblock_thread
489                // because it will be borrowed again in the unblock callback.
490                drop(mutex);
491                if let Some(thread_id) = thread_id {
492                    this.unblock_thread(thread_id, BlockReason::Mutex)?;
493                }
494            }
495            Some(old_lock_count)
496        } else {
497            // Mutex is not locked.
498            None
499        })
500    }
501
502    /// Put the thread into the queue waiting for the mutex.
503    ///
504    /// Once the Mutex becomes available and if it exists, `retval_dest.0` will
505    /// be written to `retval_dest.1`.
506    #[inline]
507    fn mutex_enqueue_and_block(
508        &mut self,
509        mutex_ref: MutexRef,
510        retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
511    ) {
512        let this = self.eval_context_mut();
513        let thread = this.active_thread();
514        let mut mutex = mutex_ref.0.borrow_mut();
515        mutex.queue.push_back(thread);
516        assert!(mutex.owner.is_some(), "queuing on unlocked mutex");
517        drop(mutex);
518        this.block_thread(
519            BlockReason::Mutex,
520            None,
521            callback!(
522                @capture<'tcx> {
523                    mutex_ref: MutexRef,
524                    retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
525                }
526                |this, unblock: UnblockKind| {
527                    assert_eq!(unblock, UnblockKind::Ready);
528
529                    assert!(mutex_ref.owner().is_none());
530                    this.mutex_lock(&mutex_ref)?;
531
532                    if let Some((retval, dest)) = retval_dest {
533                        this.write_scalar(retval, &dest)?;
534                    }
535
536                    interp_ok(())
537                }
538            ),
539        );
540    }
541
542    /// Read-lock the lock by adding the `reader` the list of threads that own
543    /// this lock.
544    fn rwlock_reader_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> {
545        let this = self.eval_context_mut();
546        let thread = this.active_thread();
547        trace!("rwlock_reader_lock: now also held (one more time) by {:?}", thread);
548        let mut rwlock = rwlock_ref.0.borrow_mut();
549        assert!(!rwlock.is_write_locked(), "the lock is write locked");
550        let count = rwlock.readers.entry(thread).or_insert(0);
551        *count = count.strict_add(1);
552        this.acquire_clock(&rwlock.clock_unlocked)?;
553        interp_ok(())
554    }
555
556    /// Try read-unlock the lock for the current threads and potentially give the lock to a new owner.
557    /// Returns `true` if succeeded, `false` if this `reader` did not hold the lock.
558    fn rwlock_reader_unlock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx, bool> {
559        let this = self.eval_context_mut();
560        let thread = this.active_thread();
561        let mut rwlock = rwlock_ref.0.borrow_mut();
562        match rwlock.readers.entry(thread) {
563            Entry::Occupied(mut entry) => {
564                let count = entry.get_mut();
565                assert!(*count > 0, "rwlock locked with count == 0");
566                *count -= 1;
567                if *count == 0 {
568                    trace!("rwlock_reader_unlock: no longer held by {:?}", thread);
569                    entry.remove();
570                } else {
571                    trace!("rwlock_reader_unlock: held one less time by {:?}", thread);
572                }
573            }
574            Entry::Vacant(_) => return interp_ok(false), // we did not even own this lock
575        }
576        // Add this to the shared-release clock of all concurrent readers.
577        this.release_clock(|clock| rwlock.clock_current_readers.join(clock))?;
578
579        // The thread was a reader. If the lock is not held any more, give it to a writer.
580        if rwlock.is_locked().not() {
581            // All the readers are finished, so set the writer data-race handle to the value
582            // of the union of all reader data race handles, since the set of readers
583            // happen-before the writers
584            let rwlock_ref = &mut *rwlock;
585            rwlock_ref.clock_unlocked.clone_from(&rwlock_ref.clock_current_readers);
586            // See if there is a thread to unblock.
587            if let Some(writer) = rwlock_ref.writer_queue.pop_front() {
588                drop(rwlock); // make RefCell available for unblock callback
589                this.unblock_thread(writer, BlockReason::RwLock)?;
590            }
591        }
592        interp_ok(true)
593    }
594
595    /// Put the reader in the queue waiting for the lock and block it.
596    /// Once the lock becomes available, `retval` will be written to `dest`.
597    #[inline]
598    fn rwlock_enqueue_and_block_reader(
599        &mut self,
600        rwlock_ref: RwLockRef,
601        retval: Scalar,
602        dest: MPlaceTy<'tcx>,
603    ) {
604        let this = self.eval_context_mut();
605        let thread = this.active_thread();
606        let mut rwlock = rwlock_ref.0.borrow_mut();
607        rwlock.reader_queue.push_back(thread);
608        assert!(rwlock.is_write_locked(), "read-queueing on not write locked rwlock");
609        drop(rwlock);
610        this.block_thread(
611            BlockReason::RwLock,
612            None,
613            callback!(
614                @capture<'tcx> {
615                    rwlock_ref: RwLockRef,
616                    retval: Scalar,
617                    dest: MPlaceTy<'tcx>,
618                }
619                |this, unblock: UnblockKind| {
620                    assert_eq!(unblock, UnblockKind::Ready);
621                    this.rwlock_reader_lock(&rwlock_ref)?;
622                    this.write_scalar(retval, &dest)?;
623                    interp_ok(())
624                }
625            ),
626        );
627    }
628
629    /// Lock by setting the writer that owns the lock.
630    #[inline]
631    fn rwlock_writer_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> {
632        let this = self.eval_context_mut();
633        let thread = this.active_thread();
634        trace!("rwlock_writer_lock: now held by {:?}", thread);
635
636        let mut rwlock = rwlock_ref.0.borrow_mut();
637        assert!(!rwlock.is_locked(), "the rwlock is already locked");
638        rwlock.writer = Some(thread);
639        this.acquire_clock(&rwlock.clock_unlocked)?;
640        interp_ok(())
641    }
642
643    /// Try to unlock an rwlock held by the current thread.
644    /// Return `false` if it is held by another thread.
645    #[inline]
646    fn rwlock_writer_unlock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx, bool> {
647        let this = self.eval_context_mut();
648        let thread = this.active_thread();
649        let mut rwlock = rwlock_ref.0.borrow_mut();
650        interp_ok(if let Some(current_writer) = rwlock.writer {
651            if current_writer != thread {
652                // Only the owner can unlock the rwlock.
653                return interp_ok(false);
654            }
655            rwlock.writer = None;
656            trace!("rwlock_writer_unlock: unlocked by {:?}", thread);
657            // Record release clock for next lock holder.
658            this.release_clock(|clock| rwlock.clock_unlocked.clone_from(clock))?;
659
660            // The thread was a writer.
661            //
662            // We are prioritizing writers here against the readers. As a
663            // result, not only readers can starve writers, but also writers can
664            // starve readers.
665            if let Some(writer) = rwlock.writer_queue.pop_front() {
666                drop(rwlock); // make RefCell available for unblock callback
667                this.unblock_thread(writer, BlockReason::RwLock)?;
668            } else {
669                // Take the entire read queue and wake them all up.
670                let readers = std::mem::take(&mut rwlock.reader_queue);
671                drop(rwlock); // make RefCell available for unblock callback
672                for reader in readers {
673                    this.unblock_thread(reader, BlockReason::RwLock)?;
674                }
675            }
676            true
677        } else {
678            false
679        })
680    }
681
682    /// Put the writer in the queue waiting for the lock.
683    /// Once the lock becomes available, `retval` will be written to `dest`.
684    #[inline]
685    fn rwlock_enqueue_and_block_writer(
686        &mut self,
687        rwlock_ref: RwLockRef,
688        retval: Scalar,
689        dest: MPlaceTy<'tcx>,
690    ) {
691        let this = self.eval_context_mut();
692        let thread = this.active_thread();
693        let mut rwlock = rwlock_ref.0.borrow_mut();
694        rwlock.writer_queue.push_back(thread);
695        assert!(rwlock.is_locked(), "write-queueing on unlocked rwlock");
696        drop(rwlock);
697        this.block_thread(
698            BlockReason::RwLock,
699            None,
700            callback!(
701                @capture<'tcx> {
702                    rwlock_ref: RwLockRef,
703                    retval: Scalar,
704                    dest: MPlaceTy<'tcx>,
705                }
706                |this, unblock: UnblockKind| {
707                    assert_eq!(unblock, UnblockKind::Ready);
708                    this.rwlock_writer_lock(&rwlock_ref)?;
709                    this.write_scalar(retval, &dest)?;
710                    interp_ok(())
711                }
712            ),
713        );
714    }
715
716    /// Release the mutex and let the current thread wait on the given condition variable.
717    /// Once it is signaled, the mutex will be acquired and `retval_succ` will be written to `dest`.
718    /// If the timeout happens first, `retval_timeout` will be written to `dest`.
719    fn condvar_wait(
720        &mut self,
721        condvar_ref: CondvarRef,
722        mutex_ref: MutexRef,
723        deadline: Option<Deadline>,
724        retval_succ: Scalar,
725        retval_timeout: Scalar,
726        dest: MPlaceTy<'tcx>,
727    ) -> InterpResult<'tcx> {
728        let this = self.eval_context_mut();
729        if let Some(old_locked_count) = this.mutex_unlock(&mutex_ref)? {
730            if old_locked_count != 1 {
731                throw_unsup_format!(
732                    "awaiting a condvar on a mutex acquired multiple times is not supported"
733                );
734            }
735        } else {
736            throw_ub_format!(
737                "awaiting a condvar on a mutex that is unlocked or owned by a different thread"
738            );
739        }
740        let thread = this.active_thread();
741
742        condvar_ref.0.borrow_mut().waiters.push_back(thread);
743        this.block_thread(
744            BlockReason::Condvar,
745            deadline,
746            callback!(
747                @capture<'tcx> {
748                    condvar_ref: CondvarRef,
749                    mutex_ref: MutexRef,
750                    retval_succ: Scalar,
751                    retval_timeout: Scalar,
752                    dest: MPlaceTy<'tcx>,
753                }
754                |this, unblock: UnblockKind| {
755                    match unblock {
756                        UnblockKind::Ready => {
757                            // The condvar was signaled. Make sure we get the clock for that.
758                            this.acquire_clock(
759                                    &condvar_ref.0.borrow().clock,
760                                )?;
761                            // Try to acquire the mutex.
762                            // The timeout only applies to the first wait (until the signal), not for mutex acquisition.
763                            this.condvar_reacquire_mutex(mutex_ref, retval_succ, dest)
764                        }
765                        UnblockKind::TimedOut => {
766                            // We have to remove the waiter from the queue again.
767                            let thread = this.active_thread();
768                            let waiters = &mut condvar_ref.0.borrow_mut().waiters;
769                            waiters.retain(|waiter| *waiter != thread);
770                            // Now get back the lock.
771                            this.condvar_reacquire_mutex(mutex_ref, retval_timeout, dest)
772                        }
773                    }
774                }
775            ),
776        );
777        interp_ok(())
778    }
779
780    /// Wake up some thread (if there is any) sleeping on the conditional
781    /// variable. Returns `true` iff any thread was woken up.
782    fn condvar_signal(&mut self, condvar_ref: &CondvarRef) -> InterpResult<'tcx, bool> {
783        let this = self.eval_context_mut();
784        let mut condvar = condvar_ref.0.borrow_mut();
785
786        // Each condvar signal happens-before the end of the condvar wake
787        this.release_clock(|clock| condvar.clock.clone_from(clock))?;
788        let Some(waiter) = condvar.waiters.pop_front() else {
789            return interp_ok(false);
790        };
791        drop(condvar);
792        this.unblock_thread(waiter, BlockReason::Condvar)?;
793        interp_ok(true)
794    }
795
796    /// Wait for the futex to be signaled, or a timeout. Once the thread is
797    /// unblocked, `callback` is called with the unblock reason.
798    fn futex_wait(
799        &mut self,
800        futex_ref: FutexRef,
801        bitset: u32,
802        deadline: Option<Deadline>,
803        callback: DynUnblockCallback<'tcx>,
804    ) {
805        let this = self.eval_context_mut();
806        let thread = this.active_thread();
807        let mut futex = futex_ref.0.borrow_mut();
808        let waiters = &mut futex.waiters;
809        assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
810        waiters.push(FutexWaiter { thread, bitset });
811        drop(futex);
812
813        this.block_thread(
814            BlockReason::Futex,
815            deadline,
816            callback!(
817                @capture<'tcx> {
818                    futex_ref: FutexRef,
819                    callback: DynUnblockCallback<'tcx>,
820                }
821                |this, unblock: UnblockKind| {
822                    match unblock {
823                        UnblockKind::Ready => {
824                            let futex = futex_ref.0.borrow();
825                            // Acquire the clock of the futex.
826                            this.acquire_clock(&futex.clock)?;
827                        },
828                        UnblockKind::TimedOut => {
829                            // Remove the waiter from the futex.
830                            let thread = this.active_thread();
831                            let mut futex = futex_ref.0.borrow_mut();
832                            futex.waiters.retain(|waiter| waiter.thread != thread);
833                        },
834                    }
835
836                    callback.call(this, unblock)
837                }
838            ),
839        );
840    }
841
842    /// Wake up `count` of the threads in the queue that match any of the bits
843    /// in the bitset. Returns how many threads were woken.
844    fn futex_wake(
845        &mut self,
846        futex_ref: &FutexRef,
847        bitset: u32,
848        count: usize,
849    ) -> InterpResult<'tcx, usize> {
850        let this = self.eval_context_mut();
851        let mut futex = futex_ref.0.borrow_mut();
852
853        // Each futex-wake happens-before the end of the futex wait
854        this.release_clock(|clock| futex.clock.clone_from(clock))?;
855
856        // Remove `count` of the threads in the queue that match any of the bits in the bitset.
857        // We collect all of them before unblocking because the unblock callback may access the
858        // futex state to retrieve the remaining number of waiters on macOS.
859        let wake_threads: Vec<_> =
860            futex.waiters.extract_if(.., |w| w.bitset & bitset != 0).take(count).collect();
861        drop(futex);
862
863        let woken = wake_threads.len();
864        for waiter in wake_threads {
865            this.unblock_thread(waiter.thread, BlockReason::Futex)?;
866        }
867
868        interp_ok(woken)
869    }
870
871    /// Tries to decrement the semaphore. Blocks the current thread if that is not possible.
872    /// `callback` is invoked after the decrement was successful.
873    ///
874    /// Once the Mutex becomes available and if it exists, `retval_dest.0` will
875    /// be written to `retval_dest.1`.
876    fn semaphore_decrement(
877        &mut self,
878        semaphore_ref: SemaphoreRef,
879        deadline: Option<Deadline>,
880        callback: DynUnblockCallback<'tcx>,
881    ) -> InterpResult<'tcx> {
882        let this = self.eval_context_mut();
883
884        let mut semaphore = semaphore_ref.0.borrow_mut();
885        if let Some(new_value) = semaphore.value.checked_sub(1) {
886            semaphore.value = new_value;
887            this.acquire_clock(&semaphore.clock)?;
888            drop(semaphore);
889            return callback.call(this, UnblockKind::Ready);
890        }
891
892        // We have to block.
893        semaphore.waiters.push_back(this.active_thread());
894        drop(semaphore);
895
896        this.block_thread(
897            BlockReason::Semaphore,
898            deadline.clone(),
899            callback!(
900                @capture<'tcx> {
901                    semaphore_ref: SemaphoreRef,
902                    deadline: Option<Deadline>,
903                    callback: DynUnblockCallback<'tcx>,
904                }
905                |this, unblock: UnblockKind| {
906                    match unblock {
907                        UnblockKind::TimedOut => {
908                            // Remove the waiter.
909                            let thread = this.active_thread();
910                            let mut semaphore = semaphore_ref.0.borrow_mut();
911                            semaphore.waiters.retain(|&waiter| waiter != thread);
912                            // Finish with callback.
913                            return callback.call(this, UnblockKind::TimedOut);
914                        }
915                        UnblockKind::Ready => {
916                            // Try again! This time it will definitely work.
917                            // (But it's too much of a hassle to assert that.)
918                            this.semaphore_decrement(semaphore_ref, deadline, callback)
919                        }
920                    }
921                }
922            ),
923        );
924        interp_ok(())
925    }
926
927    /// Increment the semaphore.
928    /// Returns whether anyone was woken up.
929    fn semaphore_increment(&mut self, semaphore_ref: &SemaphoreRef) -> InterpResult<'tcx, bool> {
930        let this = self.eval_context_mut();
931
932        let mut semaphore = semaphore_ref.0.borrow_mut();
933        // If the value is currently 0, we will unlock one thread later.
934        let wake_thread =
935            (semaphore.value == 0).then_some(()).and_then(|()| semaphore.waiters.pop_front());
936        // Increment value.
937        let Some(new_value) = semaphore.value.checked_add(1) else {
938            throw_unsup_format!("the counter of a semaphore overflowed")
939        };
940        semaphore.value = new_value;
941        // Snapshot vector clock.
942        this.release_clock(|clock| semaphore.clock.clone_from(clock))?;
943        drop(semaphore);
944
945        if let Some(thread) = wake_thread {
946            this.unblock_thread(thread, BlockReason::Semaphore)?;
947        }
948
949        interp_ok(wake_thread.is_some())
950    }
951}