Skip to main content

miri/concurrency/
sync.rs

1use std::cell::RefCell;
2use std::collections::VecDeque;
3use std::collections::hash_map::Entry;
4use std::default::Default;
5use std::ops::Not;
6use std::rc::Rc;
7
8use rustc_data_structures::fx::FxHashMap;
9
10use super::vector_clock::VClock;
11use crate::*;
12
13/// The mutex state.
14#[derive(Default, Debug)]
15struct Mutex {
16    /// The thread that currently owns the lock.
17    owner: Option<ThreadId>,
18    /// How many times the mutex was locked by the owner.
19    lock_count: usize,
20    /// The queue of threads waiting for this mutex.
21    queue: VecDeque<ThreadId>,
22    /// Mutex clock. This tracks the moment of the last unlock.
23    clock: VClock,
24}
25
26#[derive(Default, Clone, Debug)]
27pub struct MutexRef(Rc<RefCell<Mutex>>);
28
29impl MutexRef {
30    pub fn new() -> Self {
31        Self(Default::default())
32    }
33
34    /// Get the id of the thread that currently owns this lock, or `None` if it is not locked.
35    pub fn owner(&self) -> Option<ThreadId> {
36        self.0.borrow().owner
37    }
38
39    pub fn queue_is_empty(&self) -> bool {
40        self.0.borrow().queue.is_empty()
41    }
42}
43
44impl VisitProvenance for MutexRef {
45    // Mutex contains no provenance.
46    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
47}
48
49/// The read-write lock state.
50#[derive(Default, Debug)]
51struct RwLock {
52    /// The writer thread that currently owns the lock.
53    writer: Option<ThreadId>,
54    /// The readers that currently own the lock and how many times they acquired
55    /// the lock.
56    readers: FxHashMap<ThreadId, usize>,
57    /// The queue of writer threads waiting for this lock.
58    writer_queue: VecDeque<ThreadId>,
59    /// The queue of reader threads waiting for this lock.
60    reader_queue: VecDeque<ThreadId>,
61    /// Data race clock for writers. Tracks the happens-before
62    /// ordering between each write access to a rwlock and is updated
63    /// after a sequence of concurrent readers to track the happens-
64    /// before ordering between the set of previous readers and
65    /// the current writer.
66    /// Contains the clock of the last thread to release a writer
67    /// lock or the joined clock of the set of last threads to release
68    /// shared reader locks.
69    clock_unlocked: VClock,
70    /// Data race clock for readers. This is temporary storage
71    /// for the combined happens-before ordering for between all
72    /// concurrent readers and the next writer, and the value
73    /// is stored to the main data_race variable once all
74    /// readers are finished.
75    /// Has to be stored separately since reader lock acquires
76    /// must load the clock of the last write and must not
77    /// add happens-before orderings between shared reader
78    /// locks.
79    /// This is only relevant when there is an active reader.
80    clock_current_readers: VClock,
81}
82
83impl RwLock {
84    #[inline]
85    /// Check if locked.
86    fn is_locked(&self) -> bool {
87        trace!(
88            "rwlock_is_locked: writer is {:?} and there are {} reader threads (some of which could hold multiple read locks)",
89            self.writer,
90            self.readers.len(),
91        );
92        self.writer.is_some() || self.readers.is_empty().not()
93    }
94
95    /// Check if write locked.
96    #[inline]
97    fn is_write_locked(&self) -> bool {
98        trace!("rwlock_is_write_locked: writer is {:?}", self.writer);
99        self.writer.is_some()
100    }
101}
102
103#[derive(Default, Clone, Debug)]
104pub struct RwLockRef(Rc<RefCell<RwLock>>);
105
106impl RwLockRef {
107    pub fn new() -> Self {
108        Self(Default::default())
109    }
110
111    pub fn is_locked(&self) -> bool {
112        self.0.borrow().is_locked()
113    }
114
115    pub fn is_write_locked(&self) -> bool {
116        self.0.borrow().is_write_locked()
117    }
118
119    pub fn queue_is_empty(&self) -> bool {
120        let inner = self.0.borrow();
121        inner.reader_queue.is_empty() && inner.writer_queue.is_empty()
122    }
123}
124
125impl VisitProvenance for RwLockRef {
126    // RwLock contains no provenance.
127    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
128}
129
130/// The conditional variable state.
131#[derive(Default, Debug)]
132struct Condvar {
133    waiters: VecDeque<ThreadId>,
134    /// Tracks the happens-before relationship
135    /// between a cond-var signal and a cond-var
136    /// wait during a non-spurious signal event.
137    /// Contains the clock of the last thread to
138    /// perform a condvar-signal.
139    clock: VClock,
140}
141
142#[derive(Default, Clone, Debug)]
143pub struct CondvarRef(Rc<RefCell<Condvar>>);
144
145impl CondvarRef {
146    pub fn new() -> Self {
147        Self(Default::default())
148    }
149
150    pub fn queue_is_empty(&self) -> bool {
151        self.0.borrow().waiters.is_empty()
152    }
153}
154
155impl VisitProvenance for CondvarRef {
156    // Condvar contains no provenance.
157    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
158}
159
160/// The semaphore state.
161#[derive(Default, Debug)]
162struct Semaphore {
163    waiters: VecDeque<ThreadId>,
164    value: u64,
165    /// The joint data race clock of the most recent semaphore increment.
166    clock: VClock,
167}
168
169#[derive(Default, Clone, Debug)]
170pub struct SemaphoreRef(Rc<RefCell<Semaphore>>);
171
172impl SemaphoreRef {
173    pub fn new(value: u64) -> Self {
174        Self(Rc::new(RefCell::new(Semaphore { value, ..Default::default() })))
175    }
176
177    pub fn value(&self) -> u64 {
178        self.0.borrow().value
179    }
180
181    pub fn queue_is_empty(&self) -> bool {
182        self.0.borrow().waiters.is_empty()
183    }
184}
185
186impl VisitProvenance for SemaphoreRef {
187    // Semaphore contains no provenance.
188    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
189}
190
191/// The futex state.
192#[derive(Default, Debug)]
193struct Futex {
194    waiters: Vec<FutexWaiter>,
195    /// Tracks the happens-before relationship
196    /// between a futex-wake and a futex-wait
197    /// during a non-spurious wake event.
198    /// Contains the clock of the last thread to
199    /// perform a futex-wake.
200    clock: VClock,
201}
202
203#[derive(Default, Clone, Debug)]
204pub struct FutexRef(Rc<RefCell<Futex>>);
205
206impl FutexRef {
207    pub fn new() -> Self {
208        Self(Default::default())
209    }
210
211    pub fn waiters(&self) -> usize {
212        self.0.borrow().waiters.len()
213    }
214}
215
216impl VisitProvenance for FutexRef {
217    // Futex contains no provenance.
218    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
219}
220
221/// A thread waiting on a futex.
222#[derive(Debug)]
223struct FutexWaiter {
224    /// The thread that is waiting on this futex.
225    thread: ThreadId,
226    /// The bitset used by FUTEX_*_BITSET, or u32::MAX for other operations.
227    bitset: u32,
228}
229
230// Private extension trait for local helper methods
231impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
232pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
233    fn condvar_reacquire_mutex(
234        &mut self,
235        mutex_ref: MutexRef,
236        retval: Scalar,
237        dest: MPlaceTy<'tcx>,
238    ) -> InterpResult<'tcx> {
239        let ecx = self.eval_context_mut();
240        if let Some(owner) = mutex_ref.owner() {
241            assert_ne!(owner, ecx.active_thread());
242            ecx.mutex_enqueue_and_block(mutex_ref, Some((retval, dest)));
243        } else {
244            // We can have it right now!
245            ecx.mutex_lock(&mutex_ref)?;
246            // Don't forget to write the return value.
247            ecx.write_scalar(retval, &dest)?;
248        }
249        interp_ok(())
250    }
251}
252
253// Public interface to synchronization objects. Please note that in most
254// cases, the function calls are infallible and it is the client's (shim
255// implementation's) responsibility to detect and deal with erroneous
256// situations.
257impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
258pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
259    /// Lock by setting the mutex owner and increasing the lock count.
260    fn mutex_lock(&self, mutex_ref: &MutexRef) -> InterpResult<'tcx> {
261        let ecx = self.eval_context_ref();
262        let thread = ecx.active_thread();
263        let mut mutex = mutex_ref.0.borrow_mut();
264        if let Some(current_owner) = mutex.owner {
265            assert_eq!(thread, current_owner, "mutex already locked by another thread");
266            assert!(
267                mutex.lock_count > 0,
268                "invariant violation: lock_count == 0 iff the thread is unlocked"
269            );
270        } else {
271            mutex.owner = Some(thread);
272        }
273        mutex.lock_count = mutex.lock_count.strict_add(1);
274        ecx.acquire_clock(&mutex.clock)?;
275        interp_ok(())
276    }
277
278    /// Try unlocking by decreasing the lock count and returning the old lock
279    /// count. If the lock count reaches 0, release the lock and potentially
280    /// give to a new owner. If the lock was not locked by the current thread,
281    /// return `None`.
282    fn mutex_unlock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx, Option<usize>> {
283        let ecx = self.eval_context_mut();
284        let mut mutex = mutex_ref.0.borrow_mut();
285        interp_ok(if let Some(current_owner) = mutex.owner {
286            // Mutex is locked.
287            if current_owner != ecx.machine.threads.active_thread() {
288                // Only the owner can unlock the mutex.
289                return interp_ok(None);
290            }
291            let old_lock_count = mutex.lock_count;
292            mutex.lock_count = old_lock_count.strict_sub(1);
293            if mutex.lock_count == 0 {
294                mutex.owner = None;
295                // The mutex is completely unlocked. Try transferring ownership
296                // to another thread.
297
298                ecx.release_clock(|clock| mutex.clock.clone_from(clock))?;
299                let thread_id = mutex.queue.pop_front();
300                // We need to drop our mutex borrow before unblock_thread
301                // because it will be borrowed again in the unblock callback.
302                drop(mutex);
303                if let Some(thread_id) = thread_id {
304                    ecx.unblock_thread(thread_id, BlockReason::Mutex)?;
305                }
306            }
307            Some(old_lock_count)
308        } else {
309            // Mutex is not locked.
310            None
311        })
312    }
313
314    /// Put the thread into the queue waiting for the mutex.
315    ///
316    /// Once the Mutex becomes available and if it exists, `retval_dest.0` will
317    /// be written to `retval_dest.1`.
318    #[inline]
319    fn mutex_enqueue_and_block(
320        &mut self,
321        mutex_ref: MutexRef,
322        retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
323    ) {
324        let ecx = self.eval_context_mut();
325        let thread = ecx.active_thread();
326        let mut mutex = mutex_ref.0.borrow_mut();
327        mutex.queue.push_back(thread);
328        assert!(mutex.owner.is_some(), "queuing on unlocked mutex");
329        drop(mutex);
330        ecx.block_thread(
331            BlockReason::Mutex,
332            None,
333            callback!(
334                @capture<'tcx> {
335                    mutex_ref: MutexRef,
336                    retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
337                }
338                |ecx, unblock: UnblockKind| {
339                    assert_eq!(unblock, UnblockKind::Ready);
340
341                    assert!(mutex_ref.owner().is_none());
342                    ecx.mutex_lock(&mutex_ref)?;
343
344                    if let Some((retval, dest)) = retval_dest {
345                        ecx.write_scalar(retval, &dest)?;
346                    }
347
348                    interp_ok(())
349                }
350            ),
351        );
352    }
353
354    /// Read-lock the lock by adding the `reader` the list of threads that own
355    /// this lock.
356    fn rwlock_reader_lock(&self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> {
357        let ecx = self.eval_context_ref();
358        let thread = ecx.active_thread();
359        trace!("rwlock_reader_lock: now also held (one more time) by {:?}", thread);
360        let mut rwlock = rwlock_ref.0.borrow_mut();
361        assert!(!rwlock.is_write_locked(), "the lock is write locked");
362        let count = rwlock.readers.entry(thread).or_insert(0);
363        *count = count.strict_add(1);
364        ecx.acquire_clock(&rwlock.clock_unlocked)?;
365        interp_ok(())
366    }
367
368    /// Try read-unlock the lock for the current threads and potentially give the lock to a new owner.
369    /// Returns `true` if succeeded, `false` if this `reader` did not hold the lock.
370    fn rwlock_reader_unlock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx, bool> {
371        let ecx = self.eval_context_mut();
372        let thread = ecx.active_thread();
373        let mut rwlock = rwlock_ref.0.borrow_mut();
374        match rwlock.readers.entry(thread) {
375            Entry::Occupied(mut entry) => {
376                let count = entry.get_mut();
377                assert!(*count > 0, "rwlock locked with count == 0");
378                *count -= 1;
379                if *count == 0 {
380                    trace!("rwlock_reader_unlock: no longer held by {:?}", thread);
381                    entry.remove();
382                } else {
383                    trace!("rwlock_reader_unlock: held one less time by {:?}", thread);
384                }
385            }
386            Entry::Vacant(_) => return interp_ok(false), // we did not even own this lock
387        }
388        // Add this to the shared-release clock of all concurrent readers.
389        ecx.release_clock(|clock| rwlock.clock_current_readers.join(clock))?;
390
391        // The thread was a reader. If the lock is not held any more, give it to a writer.
392        if rwlock.is_locked().not() {
393            // All the readers are finished, so set the writer data-race handle to the value
394            // of the union of all reader data race handles, since the set of readers
395            // happen-before the writers
396            let rwlock_ref = &mut *rwlock;
397            rwlock_ref.clock_unlocked.clone_from(&rwlock_ref.clock_current_readers);
398            // See if there is a thread to unblock.
399            if let Some(writer) = rwlock_ref.writer_queue.pop_front() {
400                drop(rwlock); // make RefCell available for unblock callback
401                ecx.unblock_thread(writer, BlockReason::RwLock)?;
402            }
403        }
404        interp_ok(true)
405    }
406
407    /// Put the reader in the queue waiting for the lock and block it.
408    /// Once the lock becomes available, `retval` will be written to `dest`.
409    #[inline]
410    fn rwlock_enqueue_and_block_reader(
411        &mut self,
412        rwlock_ref: RwLockRef,
413        retval: Scalar,
414        dest: MPlaceTy<'tcx>,
415    ) {
416        let ecx = self.eval_context_mut();
417        let thread = ecx.active_thread();
418        let mut rwlock = rwlock_ref.0.borrow_mut();
419        rwlock.reader_queue.push_back(thread);
420        assert!(rwlock.is_write_locked(), "read-queueing on not write locked rwlock");
421        drop(rwlock);
422        ecx.block_thread(
423            BlockReason::RwLock,
424            None,
425            callback!(
426                @capture<'tcx> {
427                    rwlock_ref: RwLockRef,
428                    retval: Scalar,
429                    dest: MPlaceTy<'tcx>,
430                }
431                |ecx, unblock: UnblockKind| {
432                    assert_eq!(unblock, UnblockKind::Ready);
433                    ecx.rwlock_reader_lock(&rwlock_ref)?;
434                    ecx.write_scalar(retval, &dest)?;
435                    interp_ok(())
436                }
437            ),
438        );
439    }
440
441    /// Lock by setting the writer that owns the lock.
442    #[inline]
443    fn rwlock_writer_lock(&self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> {
444        let ecx = self.eval_context_ref();
445        let thread = ecx.active_thread();
446        trace!("rwlock_writer_lock: now held by {:?}", thread);
447
448        let mut rwlock = rwlock_ref.0.borrow_mut();
449        assert!(!rwlock.is_locked(), "the rwlock is already locked");
450        rwlock.writer = Some(thread);
451        ecx.acquire_clock(&rwlock.clock_unlocked)?;
452        interp_ok(())
453    }
454
455    /// Try to unlock an rwlock held by the current thread.
456    /// Return `false` if it is held by another thread.
457    #[inline]
458    fn rwlock_writer_unlock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx, bool> {
459        let ecx = self.eval_context_mut();
460        let thread = ecx.active_thread();
461        let mut rwlock = rwlock_ref.0.borrow_mut();
462        interp_ok(if let Some(current_writer) = rwlock.writer {
463            if current_writer != thread {
464                // Only the owner can unlock the rwlock.
465                return interp_ok(false);
466            }
467            rwlock.writer = None;
468            trace!("rwlock_writer_unlock: unlocked by {:?}", thread);
469            // Record release clock for next lock holder.
470            ecx.release_clock(|clock| rwlock.clock_unlocked.clone_from(clock))?;
471
472            // The thread was a writer.
473            //
474            // We are prioritizing writers here against the readers. As a
475            // result, not only readers can starve writers, but also writers can
476            // starve readers.
477            if let Some(writer) = rwlock.writer_queue.pop_front() {
478                drop(rwlock); // make RefCell available for unblock callback
479                ecx.unblock_thread(writer, BlockReason::RwLock)?;
480            } else {
481                // Take the entire read queue and wake them all up.
482                let readers = std::mem::take(&mut rwlock.reader_queue);
483                drop(rwlock); // make RefCell available for unblock callback
484                for reader in readers {
485                    ecx.unblock_thread(reader, BlockReason::RwLock)?;
486                }
487            }
488            true
489        } else {
490            false
491        })
492    }
493
494    /// Put the writer in the queue waiting for the lock.
495    /// Once the lock becomes available, `retval` will be written to `dest`.
496    #[inline]
497    fn rwlock_enqueue_and_block_writer(
498        &mut self,
499        rwlock_ref: RwLockRef,
500        retval: Scalar,
501        dest: MPlaceTy<'tcx>,
502    ) {
503        let ecx = self.eval_context_mut();
504        let thread = ecx.active_thread();
505        let mut rwlock = rwlock_ref.0.borrow_mut();
506        rwlock.writer_queue.push_back(thread);
507        assert!(rwlock.is_locked(), "write-queueing on unlocked rwlock");
508        drop(rwlock);
509        ecx.block_thread(
510            BlockReason::RwLock,
511            None,
512            callback!(
513                @capture<'tcx> {
514                    rwlock_ref: RwLockRef,
515                    retval: Scalar,
516                    dest: MPlaceTy<'tcx>,
517                }
518                |ecx, unblock: UnblockKind| {
519                    assert_eq!(unblock, UnblockKind::Ready);
520                    ecx.rwlock_writer_lock(&rwlock_ref)?;
521                    ecx.write_scalar(retval, &dest)?;
522                    interp_ok(())
523                }
524            ),
525        );
526    }
527
528    /// Release the mutex and let the current thread wait on the given condition variable.
529    /// Once it is signaled, the mutex will be acquired and `retval_succ` will be written to `dest`.
530    /// If the timeout happens first, `retval_timeout` will be written to `dest`.
531    fn condvar_wait(
532        &mut self,
533        condvar_ref: CondvarRef,
534        mutex_ref: MutexRef,
535        deadline: Option<Deadline>,
536        retval_succ: Scalar,
537        retval_timeout: Scalar,
538        dest: MPlaceTy<'tcx>,
539    ) -> InterpResult<'tcx> {
540        let ecx = self.eval_context_mut();
541        if let Some(old_locked_count) = ecx.mutex_unlock(&mutex_ref)? {
542            if old_locked_count != 1 {
543                throw_unsup_format!(
544                    "awaiting a condvar on a mutex acquired multiple times is not supported"
545                );
546            }
547        } else {
548            throw_ub_format!(
549                "awaiting a condvar on a mutex that is unlocked or owned by a different thread"
550            );
551        }
552        let thread = ecx.active_thread();
553
554        condvar_ref.0.borrow_mut().waiters.push_back(thread);
555        ecx.block_thread(
556            BlockReason::Condvar,
557            deadline,
558            callback!(
559                @capture<'tcx> {
560                    condvar_ref: CondvarRef,
561                    mutex_ref: MutexRef,
562                    retval_succ: Scalar,
563                    retval_timeout: Scalar,
564                    dest: MPlaceTy<'tcx>,
565                }
566                |ecx, unblock: UnblockKind| {
567                    match unblock {
568                        UnblockKind::Ready => {
569                            // The condvar was signaled. Make sure we get the clock for that.
570                            ecx.acquire_clock(
571                                    &condvar_ref.0.borrow().clock,
572                                )?;
573                            // Try to acquire the mutex.
574                            // The timeout only applies to the first wait (until the signal), not for mutex acquisition.
575                            ecx.condvar_reacquire_mutex(mutex_ref, retval_succ, dest)
576                        }
577                        UnblockKind::TimedOut => {
578                            // We have to remove the waiter from the queue again.
579                            let thread = ecx.active_thread();
580                            let waiters = &mut condvar_ref.0.borrow_mut().waiters;
581                            waiters.retain(|waiter| *waiter != thread);
582                            // Now get back the lock.
583                            ecx.condvar_reacquire_mutex(mutex_ref, retval_timeout, dest)
584                        }
585                    }
586                }
587            ),
588        );
589        interp_ok(())
590    }
591
592    /// Wake up some thread (if there is any) sleeping on the conditional
593    /// variable. Returns `true` iff any thread was woken up.
594    fn condvar_signal(&mut self, condvar_ref: &CondvarRef) -> InterpResult<'tcx, bool> {
595        let ecx = self.eval_context_mut();
596        let mut condvar = condvar_ref.0.borrow_mut();
597
598        // Each condvar signal happens-before the end of the condvar wake
599        ecx.release_clock(|clock| condvar.clock.clone_from(clock))?;
600        let Some(waiter) = condvar.waiters.pop_front() else {
601            return interp_ok(false);
602        };
603        drop(condvar);
604        ecx.unblock_thread(waiter, BlockReason::Condvar)?;
605        interp_ok(true)
606    }
607
608    /// Wait for the futex to be signaled, or a timeout. Once the thread is
609    /// unblocked, `callback` is called with the unblock reason.
610    fn futex_wait(
611        &mut self,
612        futex_ref: FutexRef,
613        bitset: u32,
614        deadline: Option<Deadline>,
615        callback: DynUnblockCallback<'tcx>,
616    ) {
617        let ecx = self.eval_context_mut();
618        let thread = ecx.active_thread();
619        let mut futex = futex_ref.0.borrow_mut();
620        let waiters = &mut futex.waiters;
621        assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
622        waiters.push(FutexWaiter { thread, bitset });
623        drop(futex);
624
625        ecx.block_thread(
626            BlockReason::Futex,
627            deadline,
628            callback!(
629                @capture<'tcx> {
630                    futex_ref: FutexRef,
631                    callback: DynUnblockCallback<'tcx>,
632                }
633                |ecx, unblock: UnblockKind| {
634                    match unblock {
635                        UnblockKind::Ready => {
636                            let futex = futex_ref.0.borrow();
637                            // Acquire the clock of the futex.
638                            ecx.acquire_clock(&futex.clock)?;
639                        },
640                        UnblockKind::TimedOut => {
641                            // Remove the waiter from the futex.
642                            let thread = ecx.active_thread();
643                            let mut futex = futex_ref.0.borrow_mut();
644                            futex.waiters.retain(|waiter| waiter.thread != thread);
645                        },
646                    }
647
648                    callback.call(ecx, unblock)
649                }
650            ),
651        );
652    }
653
654    /// Wake up `count` of the threads in the queue that match any of the bits
655    /// in the bitset. Returns how many threads were woken.
656    fn futex_wake(
657        &mut self,
658        futex_ref: &FutexRef,
659        bitset: u32,
660        count: usize,
661    ) -> InterpResult<'tcx, usize> {
662        let ecx = self.eval_context_mut();
663        let mut futex = futex_ref.0.borrow_mut();
664
665        // Each futex-wake happens-before the end of the futex wait
666        ecx.release_clock(|clock| futex.clock.clone_from(clock))?;
667
668        // Remove `count` of the threads in the queue that match any of the bits in the bitset.
669        // We collect all of them before unblocking because the unblock callback may access the
670        // futex state to retrieve the remaining number of waiters on macOS.
671        let wake_threads: Vec<_> =
672            futex.waiters.extract_if(.., |w| w.bitset & bitset != 0).take(count).collect();
673        drop(futex);
674
675        let woken = wake_threads.len();
676        for waiter in wake_threads {
677            ecx.unblock_thread(waiter.thread, BlockReason::Futex)?;
678        }
679
680        interp_ok(woken)
681    }
682
683    /// Tries to decrement the semaphore. Blocks the current thread if that is not possible.
684    /// `callback` is invoked after the decrement was successful.
685    ///
686    /// Once the Mutex becomes available and if it exists, `retval_dest.0` will
687    /// be written to `retval_dest.1`.
688    fn semaphore_decrement(
689        &mut self,
690        semaphore_ref: SemaphoreRef,
691        deadline: Option<Deadline>,
692        callback: DynUnblockCallback<'tcx>,
693    ) -> InterpResult<'tcx> {
694        let ecx = self.eval_context_mut();
695
696        let mut semaphore = semaphore_ref.0.borrow_mut();
697        if let Some(new_value) = semaphore.value.checked_sub(1) {
698            semaphore.value = new_value;
699            ecx.acquire_clock(&semaphore.clock)?;
700            drop(semaphore);
701            return callback.call(ecx, UnblockKind::Ready);
702        }
703
704        // We have to block.
705        semaphore.waiters.push_back(ecx.active_thread());
706        drop(semaphore);
707
708        ecx.block_thread(
709            BlockReason::Semaphore,
710            deadline.clone(),
711            callback!(
712                @capture<'tcx> {
713                    semaphore_ref: SemaphoreRef,
714                    deadline: Option<Deadline>,
715                    callback: DynUnblockCallback<'tcx>,
716                }
717                |ecx, unblock: UnblockKind| {
718                    match unblock {
719                        UnblockKind::TimedOut => {
720                            // Remove the waiter.
721                            let thread = ecx.active_thread();
722                            let mut semaphore = semaphore_ref.0.borrow_mut();
723                            semaphore.waiters.retain(|&waiter| waiter != thread);
724                            // Finish with callback.
725                            return callback.call(ecx, UnblockKind::TimedOut);
726                        }
727                        UnblockKind::Ready => {
728                            // Try again! This time it will definitely work.
729                            // (But it's too much of a hassle to assert that.)
730                            ecx.semaphore_decrement(semaphore_ref, deadline, callback)
731                        }
732                    }
733                }
734            ),
735        );
736        interp_ok(())
737    }
738
739    /// Increment the semaphore.
740    /// Returns whether anyone was woken up.
741    fn semaphore_increment(&mut self, semaphore_ref: &SemaphoreRef) -> InterpResult<'tcx, bool> {
742        let ecx = self.eval_context_mut();
743
744        let mut semaphore = semaphore_ref.0.borrow_mut();
745        // If the value is currently 0, we will unlock one thread later.
746        let wake_thread =
747            (semaphore.value == 0).then_some(()).and_then(|()| semaphore.waiters.pop_front());
748        // Increment value.
749        let Some(new_value) = semaphore.value.checked_add(1) else {
750            throw_unsup_format!("the counter of a semaphore overflowed")
751        };
752        semaphore.value = new_value;
753        // Snapshot vector clock.
754        ecx.release_clock(|clock| semaphore.clock.clone_from(clock))?;
755        drop(semaphore);
756
757        if let Some(thread) = wake_thread {
758            ecx.unblock_thread(thread, BlockReason::Semaphore)?;
759        }
760
761        interp_ok(wake_thread.is_some())
762    }
763}