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::time::Duration;
9use std::{fmt, iter};
10
11use rustc_abi::Size;
12use rustc_data_structures::fx::FxHashMap;
13
14use super::vector_clock::VClock;
15use crate::*;
16
17#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
19pub enum AccessKind {
20 Read,
21 Write,
22 Dealloc,
23}
24
25impl fmt::Display for AccessKind {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 AccessKind::Read => write!(f, "read"),
29 AccessKind::Write => write!(f, "write"),
30 AccessKind::Dealloc => write!(f, "deallocation"),
31 }
32 }
33}
34
35pub trait SyncObj: Any {
37 fn on_access<'tcx>(&self, _access_kind: AccessKind) -> InterpResult<'tcx> {
39 interp_ok(())
40 }
41
42 fn delete_on_write(&self) -> bool {
45 false
46 }
47}
48
49impl dyn SyncObj {
50 #[inline(always)]
51 pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
52 let x: &dyn Any = self;
53 x.downcast_ref()
54 }
55}
56
57impl fmt::Debug for dyn SyncObj {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.debug_struct("SyncObj").finish_non_exhaustive()
60 }
61}
62
63#[derive(Default, Debug)]
65struct Mutex {
66 owner: Option<ThreadId>,
68 lock_count: usize,
70 queue: VecDeque<ThreadId>,
72 clock: VClock,
74}
75
76#[derive(Default, Clone, Debug)]
77pub struct MutexRef(Rc<RefCell<Mutex>>);
78
79impl MutexRef {
80 pub fn new() -> Self {
81 Self(Default::default())
82 }
83
84 pub fn owner(&self) -> Option<ThreadId> {
86 self.0.borrow().owner
87 }
88
89 pub fn queue_is_empty(&self) -> bool {
90 self.0.borrow().queue.is_empty()
91 }
92}
93
94impl VisitProvenance for MutexRef {
95 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
97}
98
99#[derive(Default, Debug)]
101struct RwLock {
102 writer: Option<ThreadId>,
104 readers: FxHashMap<ThreadId, usize>,
107 writer_queue: VecDeque<ThreadId>,
109 reader_queue: VecDeque<ThreadId>,
111 clock_unlocked: VClock,
120 clock_current_readers: VClock,
131}
132
133impl RwLock {
134 #[inline]
135 fn is_locked(&self) -> bool {
137 trace!(
138 "rwlock_is_locked: writer is {:?} and there are {} reader threads (some of which could hold multiple read locks)",
139 self.writer,
140 self.readers.len(),
141 );
142 self.writer.is_some() || self.readers.is_empty().not()
143 }
144
145 #[inline]
147 fn is_write_locked(&self) -> bool {
148 trace!("rwlock_is_write_locked: writer is {:?}", self.writer);
149 self.writer.is_some()
150 }
151}
152
153#[derive(Default, Clone, Debug)]
154pub struct RwLockRef(Rc<RefCell<RwLock>>);
155
156impl RwLockRef {
157 pub fn new() -> Self {
158 Self(Default::default())
159 }
160
161 pub fn is_locked(&self) -> bool {
162 self.0.borrow().is_locked()
163 }
164
165 pub fn is_write_locked(&self) -> bool {
166 self.0.borrow().is_write_locked()
167 }
168
169 pub fn queue_is_empty(&self) -> bool {
170 let inner = self.0.borrow();
171 inner.reader_queue.is_empty() && inner.writer_queue.is_empty()
172 }
173}
174
175impl VisitProvenance for RwLockRef {
176 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
178}
179
180#[derive(Default, Debug)]
182struct Condvar {
183 waiters: VecDeque<ThreadId>,
184 clock: VClock,
190}
191
192#[derive(Default, Clone, Debug)]
193pub struct CondvarRef(Rc<RefCell<Condvar>>);
194
195impl CondvarRef {
196 pub fn new() -> Self {
197 Self(Default::default())
198 }
199
200 pub fn queue_is_empty(&self) -> bool {
201 self.0.borrow().waiters.is_empty()
202 }
203}
204
205impl VisitProvenance for CondvarRef {
206 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
208}
209
210#[derive(Default, Debug)]
212struct Futex {
213 waiters: Vec<FutexWaiter>,
214 clock: VClock,
220}
221
222#[derive(Default, Clone, Debug)]
223pub struct FutexRef(Rc<RefCell<Futex>>);
224
225impl FutexRef {
226 pub fn new() -> Self {
227 Self(Default::default())
228 }
229
230 pub fn waiters(&self) -> usize {
231 self.0.borrow().waiters.len()
232 }
233}
234
235impl VisitProvenance for FutexRef {
236 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
238}
239
240#[derive(Debug)]
242struct FutexWaiter {
243 thread: ThreadId,
245 bitset: u32,
247}
248
249impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
251pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
252 fn condvar_reacquire_mutex(
253 &mut self,
254 mutex_ref: MutexRef,
255 retval: Scalar,
256 dest: MPlaceTy<'tcx>,
257 ) -> InterpResult<'tcx> {
258 let this = self.eval_context_mut();
259 if let Some(owner) = mutex_ref.owner() {
260 assert_ne!(owner, this.active_thread());
261 this.mutex_enqueue_and_block(mutex_ref, Some((retval, dest)));
262 } else {
263 this.mutex_lock(&mutex_ref)?;
265 this.write_scalar(retval, &dest)?;
267 }
268 interp_ok(())
269 }
270}
271
272impl<'tcx> AllocExtra<'tcx> {
273 fn get_sync<T: 'static>(&self, offset: Size) -> Option<&T> {
274 self.sync_objs.get(&offset).and_then(|s| s.downcast_ref::<T>())
275 }
276}
277
278impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
283pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
284 fn get_sync_or_init<'a, T: SyncObj>(
289 &'a mut self,
290 ptr: Pointer,
291 new: impl FnOnce(&'a mut MiriMachine<'tcx>) -> T,
292 ) -> Option<&'a T>
293 where
294 'tcx: 'a,
295 {
296 let this = self.eval_context_mut();
297 if !this.ptr_try_get_alloc_id(ptr, 0).ok().is_some_and(|(alloc_id, offset, ..)| {
298 let info = this.get_alloc_info(alloc_id);
299 info.kind == AllocKind::LiveData && info.mutbl.is_mut() && offset < info.size
300 }) {
301 return None;
302 }
303 let (alloc, offset, _) = this.ptr_get_alloc_id(ptr, 0).unwrap();
305 let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc).unwrap();
306 if alloc_extra.get_sync::<T>(offset).is_none() {
308 let new = new(machine);
309 alloc_extra.sync_objs.insert(offset, Box::new(new));
310 }
311 Some(alloc_extra.get_sync::<T>(offset).unwrap())
312 }
313
314 fn get_immovable_sync_with_static_init<'a, T: SyncObj>(
331 &'a mut self,
332 obj: &MPlaceTy<'tcx>,
333 init_offset: Size,
334 uninit_val: u8,
335 init_val: u8,
336 new_meta_obj: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, T>,
337 ) -> InterpResult<'tcx, &'a T>
338 where
339 'tcx: 'a,
340 {
341 assert!(init_val != uninit_val);
342 let this = self.eval_context_mut();
343 this.check_ptr_access(obj.ptr(), obj.layout.size, CheckInAllocMsg::Dereferenceable)?;
344 assert!(init_offset < obj.layout.size); let init_field = obj.offset(init_offset, this.machine.layouts.u8, this)?;
346
347 let (alloc, offset, _) = this.ptr_get_alloc_id(init_field.ptr(), 0)?;
348 let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc)?;
349 if alloc_extra.get_sync::<T>(offset).is_some() {
351 let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
352 return interp_ok(alloc_extra.get_sync::<T>(offset).unwrap());
353 }
354
355 let meta_obj = new_meta_obj(this)?;
357 let (old_init, success) = this
358 .atomic_compare_exchange_scalar(
359 &init_field,
360 &ImmTy::from_scalar(Scalar::from_u8(uninit_val), this.machine.layouts.u8),
361 Scalar::from_u8(init_val),
362 AtomicRwOrd::Relaxed,
363 AtomicReadOrd::Relaxed,
364 false,
365 )?
366 .to_scalar_pair();
367 if !success.to_bool()? {
368 assert_eq!(
370 old_init.to_u8()?,
371 init_val,
372 "`new_meta_obj` should have ensured that this CAS succeeds"
373 );
374 }
375
376 let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
377 assert!(meta_obj.delete_on_write());
378 alloc_extra.sync_objs.insert(offset, Box::new(meta_obj));
379 interp_ok(alloc_extra.get_sync::<T>(offset).unwrap())
380 }
381
382 fn init_immovable_sync<'a, T: SyncObj>(
385 &'a mut self,
386 obj: &MPlaceTy<'tcx>,
387 init_offset: Size,
388 init_val: u8,
389 new_meta_obj: T,
390 ) -> InterpResult<'tcx, Option<&'a T>>
391 where
392 'tcx: 'a,
393 {
394 let this = self.eval_context_mut();
395 this.check_ptr_access(obj.ptr(), obj.layout.size, CheckInAllocMsg::Dereferenceable)?;
396 assert!(init_offset < obj.layout.size); let init_field = obj.offset(init_offset, this.machine.layouts.u8, this)?;
398
399 this.write_bytes_ptr(obj.ptr(), iter::repeat_n(0, obj.layout.size.bytes_usize()))?;
401 this.write_scalar(Scalar::from_u8(init_val), &init_field)?;
402
403 let (alloc, offset, _) = this.ptr_get_alloc_id(init_field.ptr(), 0)?;
405 let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
406 assert!(new_meta_obj.delete_on_write());
407 alloc_extra.sync_objs.insert(offset, Box::new(new_meta_obj));
408 interp_ok(Some(alloc_extra.get_sync::<T>(offset).unwrap()))
409 }
410
411 fn mutex_lock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx> {
413 let this = self.eval_context_mut();
414 let thread = this.active_thread();
415 let mut mutex = mutex_ref.0.borrow_mut();
416 if let Some(current_owner) = mutex.owner {
417 assert_eq!(thread, current_owner, "mutex already locked by another thread");
418 assert!(
419 mutex.lock_count > 0,
420 "invariant violation: lock_count == 0 iff the thread is unlocked"
421 );
422 } else {
423 mutex.owner = Some(thread);
424 }
425 mutex.lock_count = mutex.lock_count.strict_add(1);
426 this.acquire_clock(&mutex.clock)?;
427 interp_ok(())
428 }
429
430 fn mutex_unlock(&mut self, mutex_ref: &MutexRef) -> InterpResult<'tcx, Option<usize>> {
435 let this = self.eval_context_mut();
436 let mut mutex = mutex_ref.0.borrow_mut();
437 interp_ok(if let Some(current_owner) = mutex.owner {
438 if current_owner != this.machine.threads.active_thread() {
440 return interp_ok(None);
442 }
443 let old_lock_count = mutex.lock_count;
444 mutex.lock_count = old_lock_count.strict_sub(1);
445 if mutex.lock_count == 0 {
446 mutex.owner = None;
447 this.release_clock(|clock| mutex.clock.clone_from(clock))?;
451 let thread_id = mutex.queue.pop_front();
452 drop(mutex);
455 if let Some(thread_id) = thread_id {
456 this.unblock_thread(thread_id, BlockReason::Mutex)?;
457 }
458 }
459 Some(old_lock_count)
460 } else {
461 None
463 })
464 }
465
466 #[inline]
471 fn mutex_enqueue_and_block(
472 &mut self,
473 mutex_ref: MutexRef,
474 retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
475 ) {
476 let this = self.eval_context_mut();
477 let thread = this.active_thread();
478 let mut mutex = mutex_ref.0.borrow_mut();
479 mutex.queue.push_back(thread);
480 assert!(mutex.owner.is_some(), "queuing on unlocked mutex");
481 drop(mutex);
482 this.block_thread(
483 BlockReason::Mutex,
484 None,
485 callback!(
486 @capture<'tcx> {
487 mutex_ref: MutexRef,
488 retval_dest: Option<(Scalar, MPlaceTy<'tcx>)>,
489 }
490 |this, unblock: UnblockKind| {
491 assert_eq!(unblock, UnblockKind::Ready);
492
493 assert!(mutex_ref.owner().is_none());
494 this.mutex_lock(&mutex_ref)?;
495
496 if let Some((retval, dest)) = retval_dest {
497 this.write_scalar(retval, &dest)?;
498 }
499
500 interp_ok(())
501 }
502 ),
503 );
504 }
505
506 fn rwlock_reader_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> {
509 let this = self.eval_context_mut();
510 let thread = this.active_thread();
511 trace!("rwlock_reader_lock: now also held (one more time) by {:?}", thread);
512 let mut rwlock = rwlock_ref.0.borrow_mut();
513 assert!(!rwlock.is_write_locked(), "the lock is write locked");
514 let count = rwlock.readers.entry(thread).or_insert(0);
515 *count = count.strict_add(1);
516 this.acquire_clock(&rwlock.clock_unlocked)?;
517 interp_ok(())
518 }
519
520 fn rwlock_reader_unlock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx, bool> {
523 let this = self.eval_context_mut();
524 let thread = this.active_thread();
525 let mut rwlock = rwlock_ref.0.borrow_mut();
526 match rwlock.readers.entry(thread) {
527 Entry::Occupied(mut entry) => {
528 let count = entry.get_mut();
529 assert!(*count > 0, "rwlock locked with count == 0");
530 *count -= 1;
531 if *count == 0 {
532 trace!("rwlock_reader_unlock: no longer held by {:?}", thread);
533 entry.remove();
534 } else {
535 trace!("rwlock_reader_unlock: held one less time by {:?}", thread);
536 }
537 }
538 Entry::Vacant(_) => return interp_ok(false), }
540 this.release_clock(|clock| rwlock.clock_current_readers.join(clock))?;
542
543 if rwlock.is_locked().not() {
545 let rwlock_ref = &mut *rwlock;
549 rwlock_ref.clock_unlocked.clone_from(&rwlock_ref.clock_current_readers);
550 if let Some(writer) = rwlock_ref.writer_queue.pop_front() {
552 drop(rwlock); this.unblock_thread(writer, BlockReason::RwLock)?;
554 }
555 }
556 interp_ok(true)
557 }
558
559 #[inline]
562 fn rwlock_enqueue_and_block_reader(
563 &mut self,
564 rwlock_ref: RwLockRef,
565 retval: Scalar,
566 dest: MPlaceTy<'tcx>,
567 ) {
568 let this = self.eval_context_mut();
569 let thread = this.active_thread();
570 let mut rwlock = rwlock_ref.0.borrow_mut();
571 rwlock.reader_queue.push_back(thread);
572 assert!(rwlock.is_write_locked(), "read-queueing on not write locked rwlock");
573 drop(rwlock);
574 this.block_thread(
575 BlockReason::RwLock,
576 None,
577 callback!(
578 @capture<'tcx> {
579 rwlock_ref: RwLockRef,
580 retval: Scalar,
581 dest: MPlaceTy<'tcx>,
582 }
583 |this, unblock: UnblockKind| {
584 assert_eq!(unblock, UnblockKind::Ready);
585 this.rwlock_reader_lock(&rwlock_ref)?;
586 this.write_scalar(retval, &dest)?;
587 interp_ok(())
588 }
589 ),
590 );
591 }
592
593 #[inline]
595 fn rwlock_writer_lock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx> {
596 let this = self.eval_context_mut();
597 let thread = this.active_thread();
598 trace!("rwlock_writer_lock: now held by {:?}", thread);
599
600 let mut rwlock = rwlock_ref.0.borrow_mut();
601 assert!(!rwlock.is_locked(), "the rwlock is already locked");
602 rwlock.writer = Some(thread);
603 this.acquire_clock(&rwlock.clock_unlocked)?;
604 interp_ok(())
605 }
606
607 #[inline]
610 fn rwlock_writer_unlock(&mut self, rwlock_ref: &RwLockRef) -> InterpResult<'tcx, bool> {
611 let this = self.eval_context_mut();
612 let thread = this.active_thread();
613 let mut rwlock = rwlock_ref.0.borrow_mut();
614 interp_ok(if let Some(current_writer) = rwlock.writer {
615 if current_writer != thread {
616 return interp_ok(false);
618 }
619 rwlock.writer = None;
620 trace!("rwlock_writer_unlock: unlocked by {:?}", thread);
621 this.release_clock(|clock| rwlock.clock_unlocked.clone_from(clock))?;
623
624 if let Some(writer) = rwlock.writer_queue.pop_front() {
630 drop(rwlock); this.unblock_thread(writer, BlockReason::RwLock)?;
632 } else {
633 let readers = std::mem::take(&mut rwlock.reader_queue);
635 drop(rwlock); for reader in readers {
637 this.unblock_thread(reader, BlockReason::RwLock)?;
638 }
639 }
640 true
641 } else {
642 false
643 })
644 }
645
646 #[inline]
649 fn rwlock_enqueue_and_block_writer(
650 &mut self,
651 rwlock_ref: RwLockRef,
652 retval: Scalar,
653 dest: MPlaceTy<'tcx>,
654 ) {
655 let this = self.eval_context_mut();
656 let thread = this.active_thread();
657 let mut rwlock = rwlock_ref.0.borrow_mut();
658 rwlock.writer_queue.push_back(thread);
659 assert!(rwlock.is_locked(), "write-queueing on unlocked rwlock");
660 drop(rwlock);
661 this.block_thread(
662 BlockReason::RwLock,
663 None,
664 callback!(
665 @capture<'tcx> {
666 rwlock_ref: RwLockRef,
667 retval: Scalar,
668 dest: MPlaceTy<'tcx>,
669 }
670 |this, unblock: UnblockKind| {
671 assert_eq!(unblock, UnblockKind::Ready);
672 this.rwlock_writer_lock(&rwlock_ref)?;
673 this.write_scalar(retval, &dest)?;
674 interp_ok(())
675 }
676 ),
677 );
678 }
679
680 fn condvar_wait(
684 &mut self,
685 condvar_ref: CondvarRef,
686 mutex_ref: MutexRef,
687 timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
688 retval_succ: Scalar,
689 retval_timeout: Scalar,
690 dest: MPlaceTy<'tcx>,
691 ) -> InterpResult<'tcx> {
692 let this = self.eval_context_mut();
693 if let Some(old_locked_count) = this.mutex_unlock(&mutex_ref)? {
694 if old_locked_count != 1 {
695 throw_unsup_format!(
696 "awaiting a condvar on a mutex acquired multiple times is not supported"
697 );
698 }
699 } else {
700 throw_ub_format!(
701 "awaiting a condvar on a mutex that is unlocked or owned by a different thread"
702 );
703 }
704 let thread = this.active_thread();
705
706 condvar_ref.0.borrow_mut().waiters.push_back(thread);
707 this.block_thread(
708 BlockReason::Condvar,
709 timeout,
710 callback!(
711 @capture<'tcx> {
712 condvar_ref: CondvarRef,
713 mutex_ref: MutexRef,
714 retval_succ: Scalar,
715 retval_timeout: Scalar,
716 dest: MPlaceTy<'tcx>,
717 }
718 |this, unblock: UnblockKind| {
719 match unblock {
720 UnblockKind::Ready => {
721 this.acquire_clock(
723 &condvar_ref.0.borrow().clock,
724 )?;
725 this.condvar_reacquire_mutex(mutex_ref, retval_succ, dest)
728 }
729 UnblockKind::TimedOut => {
730 let thread = this.active_thread();
732 let waiters = &mut condvar_ref.0.borrow_mut().waiters;
733 waiters.retain(|waiter| *waiter != thread);
734 this.condvar_reacquire_mutex(mutex_ref, retval_timeout, dest)
736 }
737 }
738 }
739 ),
740 );
741 interp_ok(())
742 }
743
744 fn condvar_signal(&mut self, condvar_ref: &CondvarRef) -> InterpResult<'tcx, bool> {
747 let this = self.eval_context_mut();
748 let mut condvar = condvar_ref.0.borrow_mut();
749
750 this.release_clock(|clock| condvar.clock.clone_from(clock))?;
752 let Some(waiter) = condvar.waiters.pop_front() else {
753 return interp_ok(false);
754 };
755 drop(condvar);
756 this.unblock_thread(waiter, BlockReason::Condvar)?;
757 interp_ok(true)
758 }
759
760 fn futex_wait(
763 &mut self,
764 futex_ref: FutexRef,
765 bitset: u32,
766 timeout: Option<(TimeoutClock, TimeoutAnchor, Duration)>,
767 callback: DynUnblockCallback<'tcx>,
768 ) {
769 let this = self.eval_context_mut();
770 let thread = this.active_thread();
771 let mut futex = futex_ref.0.borrow_mut();
772 let waiters = &mut futex.waiters;
773 assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
774 waiters.push(FutexWaiter { thread, bitset });
775 drop(futex);
776
777 this.block_thread(
778 BlockReason::Futex,
779 timeout,
780 callback!(
781 @capture<'tcx> {
782 futex_ref: FutexRef,
783 callback: DynUnblockCallback<'tcx>,
784 }
785 |this, unblock: UnblockKind| {
786 match unblock {
787 UnblockKind::Ready => {
788 let futex = futex_ref.0.borrow();
789 this.acquire_clock(&futex.clock)?;
791 },
792 UnblockKind::TimedOut => {
793 let thread = this.active_thread();
795 let mut futex = futex_ref.0.borrow_mut();
796 futex.waiters.retain(|waiter| waiter.thread != thread);
797 },
798 }
799
800 callback.call(this, unblock)
801 }
802 ),
803 );
804 }
805
806 fn futex_wake(
809 &mut self,
810 futex_ref: &FutexRef,
811 bitset: u32,
812 count: usize,
813 ) -> InterpResult<'tcx, usize> {
814 let this = self.eval_context_mut();
815 let mut futex = futex_ref.0.borrow_mut();
816
817 this.release_clock(|clock| futex.clock.clone_from(clock))?;
819
820 let waiters: Vec<_> =
824 futex.waiters.extract_if(.., |w| w.bitset & bitset != 0).take(count).collect();
825 drop(futex);
826
827 let woken = waiters.len();
828 for waiter in waiters {
829 this.unblock_thread(waiter.thread, BlockReason::Futex)?;
830 }
831
832 interp_ok(woken)
833 }
834}