1use std::mem;
4use std::task::Poll;
5use std::time::{Duration, SystemTime};
6
7use rand::RngExt;
8use rustc_abi::ExternAbi;
9use rustc_data_structures::either::Either;
10use rustc_data_structures::fx::FxHashMap;
11use rustc_hir::def_id::DefId;
12use rustc_index::{Idx, IndexVec};
13use rustc_middle::mir::Mutability;
14use rustc_middle::ty::layout::TyAndLayout;
15use rustc_span::{DUMMY_SP, Span};
16use rustc_target::spec::Os;
17
18use crate::concurrency::GlobalDataRaceHandler;
19use crate::shims::tls;
20use crate::*;
21
22#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum UnblockKind {
25 Ready,
27 TimedOut,
29}
30
31pub type DynUnblockCallback<'tcx> = DynMachineCallback<'tcx, UnblockKind>;
34
35#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
37pub struct ThreadId(u32);
38
39impl ThreadId {
40 pub fn to_u32(self) -> u32 {
41 self.0
42 }
43
44 pub fn new_unchecked(id: u32) -> Self {
46 Self(id)
47 }
48
49 pub const MAIN_THREAD: ThreadId = ThreadId(0);
50}
51
52impl Idx for ThreadId {
53 fn new(idx: usize) -> Self {
54 ThreadId(u32::try_from(idx).unwrap())
55 }
56
57 fn index(self) -> usize {
58 usize::try_from(self.0).unwrap()
59 }
60}
61
62impl From<ThreadId> for u64 {
63 fn from(t: ThreadId) -> Self {
64 t.0.into()
65 }
66}
67
68#[derive(Debug, Copy, Clone, PartialEq, Eq)]
70pub enum BlockReason {
71 Join(ThreadId),
74 Sleep,
76 Mutex,
78 Condvar,
80 RwLock,
82 Semaphore,
84 Futex,
86 InitOnce,
88 Readiness,
90 Eventfd,
92 VirtualSocket,
94 IO,
96 Pause,
98 Genmc,
101}
102
103enum ThreadState<'tcx> {
105 Enabled,
107 Blocked { reason: BlockReason, deadline: Option<Deadline>, callback: DynUnblockCallback<'tcx> },
109 Terminated,
112}
113
114impl<'tcx> std::fmt::Debug for ThreadState<'tcx> {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 match self {
117 Self::Enabled => write!(f, "Enabled"),
118 Self::Blocked { reason, deadline, .. } =>
119 f.debug_struct("Blocked")
120 .field("reason", reason)
121 .field("deadline", deadline)
122 .finish(),
123 Self::Terminated => write!(f, "Terminated"),
124 }
125 }
126}
127
128impl<'tcx> ThreadState<'tcx> {
129 fn is_enabled(&self) -> bool {
130 matches!(self, ThreadState::Enabled)
131 }
132
133 fn is_terminated(&self) -> bool {
134 matches!(self, ThreadState::Terminated)
135 }
136
137 fn is_blocked_on(&self, reason: BlockReason) -> bool {
138 matches!(self, ThreadState::Blocked { reason: actual_reason, .. } if *actual_reason == reason)
139 }
140}
141
142#[derive(Debug, Copy, Clone, PartialEq, Eq)]
144enum ThreadJoinStatus {
145 Joinable,
147 Detached,
150 Joined,
152}
153
154pub struct Thread<'tcx> {
156 state: ThreadState<'tcx>,
157
158 thread_name: Option<Vec<u8>>,
160
161 stack: Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
163
164 pub(crate) origin_span: Span,
167
168 pub(crate) on_stack_empty: Option<StackEmptyCallback<'tcx>>,
173
174 top_user_relevant_frame: Option<usize>,
179
180 join_status: ThreadJoinStatus,
182
183 pub(crate) unwind_payloads: Vec<ImmTy<'tcx>>,
192
193 pub(crate) last_error: Option<MPlaceTy<'tcx>>,
195}
196
197pub type StackEmptyCallback<'tcx> =
198 Box<dyn FnMut(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Poll<()>> + 'tcx>;
199
200impl<'tcx> Thread<'tcx> {
201 fn thread_name(&self) -> Option<&[u8]> {
203 self.thread_name.as_deref()
204 }
205
206 pub fn is_enabled(&self) -> bool {
208 self.state.is_enabled()
209 }
210
211 pub fn is_terminated(&self) -> bool {
212 self.state.is_terminated()
213 }
214
215 pub fn is_blocked_on(&self, reason: BlockReason) -> bool {
216 self.state.is_blocked_on(reason)
217 }
218
219 fn thread_display_name(&self, id: ThreadId) -> String {
221 if let Some(ref thread_name) = self.thread_name {
222 String::from_utf8_lossy(thread_name).into_owned()
223 } else {
224 format!("unnamed-{}", id.index())
225 }
226 }
227
228 fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
234 let mut best = None;
236 for (idx, frame) in self.stack.iter().enumerate().rev().skip(skip) {
237 let relevance = frame.extra.user_relevance;
238 if relevance == u8::MAX {
239 return Some(idx);
241 }
242 if best.is_none_or(|(_best_idx, best_relevance)| best_relevance < relevance) {
243 best = Some((idx, relevance));
246 }
247 }
248 best.map(|(idx, _relevance)| idx)
249 }
250
251 pub fn recompute_top_user_relevant_frame(&mut self, skip: usize) {
254 self.top_user_relevant_frame = self.compute_top_user_relevant_frame(skip);
255 }
256
257 pub fn set_top_user_relevant_frame(&mut self, frame_idx: usize) {
260 debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame(0));
261 self.top_user_relevant_frame = Some(frame_idx);
262 }
263
264 pub fn top_user_relevant_frame(&self) -> Option<usize> {
267 self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1))
271 }
272
273 pub fn current_user_relevance(&self) -> u8 {
274 self.top_user_relevant_frame()
275 .map(|frame_idx| self.stack[frame_idx].extra.user_relevance)
276 .unwrap_or(0)
277 }
278
279 pub fn current_user_relevant_span(&self) -> Span {
280 debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame(0));
281 self.top_user_relevant_frame()
282 .map(|frame_idx| self.stack[frame_idx].current_span())
283 .unwrap_or(rustc_span::DUMMY_SP)
284 }
285}
286
287impl<'tcx> std::fmt::Debug for Thread<'tcx> {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 write!(
290 f,
291 "{}({:?}, {:?})",
292 String::from_utf8_lossy(self.thread_name().unwrap_or(b"<unnamed>")),
293 self.state,
294 self.join_status
295 )
296 }
297}
298
299impl<'tcx> Thread<'tcx> {
300 fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'tcx>>) -> Self {
301 Self {
302 state: ThreadState::Enabled,
303 thread_name: name.map(|name| Vec::from(name.as_bytes())),
304 stack: Vec::new(),
305 origin_span: DUMMY_SP,
306 top_user_relevant_frame: None,
307 join_status: ThreadJoinStatus::Joinable,
308 unwind_payloads: Vec::new(),
309 last_error: None,
310 on_stack_empty,
311 }
312 }
313}
314
315impl VisitProvenance for Thread<'_> {
316 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
317 let Thread {
318 unwind_payloads: panic_payload,
319 last_error,
320 stack,
321 origin_span: _,
322 top_user_relevant_frame: _,
323 state: _,
324 thread_name: _,
325 join_status: _,
326 on_stack_empty: _, } = self;
328
329 for payload in panic_payload {
330 payload.visit_provenance(visit);
331 }
332 last_error.visit_provenance(visit);
333 for frame in stack {
334 frame.visit_provenance(visit)
335 }
336 }
337}
338
339impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
340 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
341 let return_place = self.return_place();
342 let locals = self.locals();
343 let Frame {
344 extra,
345 ..
347 } = self;
348
349 return_place.visit_provenance(visit);
351 for local in locals.iter() {
353 match local.as_mplace_or_imm_ghost() {
355 None => {}
356 Some(Either::Left((ptr, meta))) => {
357 ptr.visit_provenance(visit);
358 meta.visit_provenance(visit);
359 }
360 Some(Either::Right(imm)) => {
361 imm.visit_provenance(visit);
362 }
363 }
364 }
365
366 extra.visit_provenance(visit);
367 }
368}
369
370#[derive(Debug, Copy, Clone)]
372pub enum ThreadLookupError {
373 InvalidId,
375 Terminated(ThreadId),
377}
378
379#[derive(Debug)]
381pub struct ThreadManager<'tcx> {
382 active_thread: ThreadId,
384 threads: IndexVec<ThreadId, Thread<'tcx>>,
388 thread_local_allocs: FxHashMap<(DefId, ThreadId), StrictPointer>,
390 pub(super) yield_active_thread: bool,
393 fixed_scheduling: bool,
395}
396
397impl VisitProvenance for ThreadManager<'_> {
398 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
399 let ThreadManager {
400 threads,
401 thread_local_allocs,
402 active_thread: _,
403 yield_active_thread: _,
404 fixed_scheduling: _,
405 } = self;
406
407 for thread in threads {
408 thread.visit_provenance(visit);
409 }
410 for ptr in thread_local_allocs.values() {
411 ptr.visit_provenance(visit);
412 }
413 }
414}
415
416impl<'tcx> ThreadManager<'tcx> {
417 pub(crate) fn new(config: &MiriConfig) -> Self {
418 let mut threads = IndexVec::new();
419 threads.push(Thread::new(Some("main"), None));
421 Self {
422 active_thread: ThreadId::MAIN_THREAD,
423 threads,
424 thread_local_allocs: Default::default(),
425 yield_active_thread: false,
426 fixed_scheduling: config.fixed_scheduling,
427 }
428 }
429
430 pub(crate) fn init(
431 ecx: &mut MiriInterpCx<'tcx>,
432 on_main_stack_empty: StackEmptyCallback<'tcx>,
433 ) {
434 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty =
435 Some(on_main_stack_empty);
436 if ecx.tcx.sess.target.os != Os::Windows {
437 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
439 ThreadJoinStatus::Detached;
440 }
441 }
442
443 pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
447 if let Ok(id) = id.try_into()
448 && usize::try_from(id).is_ok_and(|id| id < self.threads.len())
449 {
450 let thread_id = ThreadId(id);
451 if self.threads[thread_id].state.is_terminated() {
452 Err(ThreadLookupError::Terminated(thread_id))
453 } else {
454 Ok(thread_id)
455 }
456 } else {
457 Err(ThreadLookupError::InvalidId)
458 }
459 }
460
461 fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<StrictPointer> {
464 self.thread_local_allocs.get(&(def_id, self.active_thread)).cloned()
465 }
466
467 fn set_thread_local_alloc(&mut self, def_id: DefId, ptr: StrictPointer) {
472 self.thread_local_allocs.try_insert((def_id, self.active_thread), ptr).unwrap();
473 }
474
475 pub fn active_thread_stack(&self) -> &[Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
477 &self.threads[self.active_thread].stack
478 }
479
480 pub fn active_thread_stack_mut(
482 &mut self,
483 ) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
484 &mut self.threads[self.active_thread].stack
485 }
486
487 pub(super) fn all_threads(&self) -> impl Iterator<Item = (ThreadId, &Thread<'tcx>)> {
488 self.threads.iter_enumerated()
489 }
490
491 pub fn all_blocked_stacks(
492 &self,
493 ) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
494 self.threads
495 .iter_enumerated()
496 .filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. }))
497 .map(|(id, t)| (id, &t.stack[..]))
498 }
499
500 fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
502 let new_thread_id = ThreadId::new(self.threads.len());
503 self.threads.push(Thread::new(None, Some(on_stack_empty)));
504 new_thread_id
505 }
506
507 pub(super) fn set_active_thread(&mut self, id: ThreadId) -> ThreadId {
509 assert!(id.index() < self.threads.len());
510 info!(
511 "---------- Now executing on thread `{}` (previous: `{}`) ----------------------------------------",
512 self.get_thread_display_name(id),
513 self.get_thread_display_name(self.active_thread)
514 );
515 std::mem::replace(&mut self.active_thread, id)
516 }
517
518 pub fn active_thread(&self) -> ThreadId {
520 self.active_thread
521 }
522
523 pub fn get_total_thread_count(&self) -> usize {
525 self.threads.len()
526 }
527
528 pub fn get_live_thread_count(&self) -> usize {
531 self.threads.iter().filter(|t| !t.state.is_terminated()).count()
532 }
533
534 fn has_terminated(&self, thread_id: ThreadId) -> bool {
536 self.threads[thread_id].state.is_terminated()
537 }
538
539 fn have_all_terminated(&self) -> bool {
541 self.threads.iter().all(|thread| thread.state.is_terminated())
542 }
543
544 fn enable_thread(&mut self, thread_id: ThreadId) {
546 assert!(self.has_terminated(thread_id));
547 self.threads[thread_id].state = ThreadState::Enabled;
548 }
549
550 pub fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
552 &mut self.threads[self.active_thread]
553 }
554
555 pub fn active_thread_ref(&self) -> &Thread<'tcx> {
557 &self.threads[self.active_thread]
558 }
559
560 pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> {
561 &self.threads[thread_id]
562 }
563
564 fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
573 trace!("detaching {:?}", id);
575
576 let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() {
577 self.threads[id].join_status == ThreadJoinStatus::Detached
579 } else {
580 self.threads[id].join_status != ThreadJoinStatus::Joinable
581 };
582 if is_ub {
583 throw_ub_format!("trying to detach thread that was already detached or joined");
584 }
585
586 self.threads[id].join_status = ThreadJoinStatus::Detached;
587 interp_ok(())
588 }
589
590 pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
592 self.threads[thread].thread_name = Some(new_thread_name);
593 }
594
595 pub fn get_thread_name(&self, thread: ThreadId) -> Option<&[u8]> {
597 self.threads[thread].thread_name()
598 }
599
600 pub fn get_thread_display_name(&self, thread: ThreadId) -> String {
601 self.threads[thread].thread_display_name(thread)
602 }
603
604 fn block_thread(
606 &mut self,
607 reason: BlockReason,
608 deadline: Option<Deadline>,
609 callback: DynUnblockCallback<'tcx>,
610 ) {
611 let state = &mut self.threads[self.active_thread].state;
612 assert!(state.is_enabled());
613 *state = ThreadState::Blocked { reason, deadline, callback }
614 }
615
616 pub fn fixed_scheduling(&self) -> bool {
617 self.fixed_scheduling
618 }
619}
620
621impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
623pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
624 #[inline]
625 fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
626 self.eval_context_ref().machine.threads.thread_id_try_from(id)
627 }
628
629 fn get_or_create_thread_local_alloc(
632 &mut self,
633 def_id: DefId,
634 ) -> InterpResult<'tcx, StrictPointer> {
635 let ecx = self.eval_context_mut();
636 let tcx = ecx.tcx;
637 if let Some(old_alloc) = ecx.machine.threads.get_thread_local_alloc_id(def_id) {
638 interp_ok(old_alloc)
641 } else {
642 if tcx.is_foreign_item(def_id) {
646 throw_unsup_format!("foreign thread-local statics are not supported");
647 }
648 let params = ecx.machine.get_default_alloc_params();
649 let alloc = ecx.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
650 let mut alloc = alloc.inner().adjust_from_tcx(
652 &ecx.tcx,
653 |bytes, align| {
654 interp_ok(MiriAllocBytes::from_bytes(
655 std::borrow::Cow::Borrowed(bytes),
656 align,
657 params,
658 ))
659 },
660 |ptr| ecx.global_root_pointer(ptr),
661 )?;
662 alloc.mutability = Mutability::Mut;
664 let ptr = ecx.insert_allocation(alloc, MiriMemoryKind::Tls.into())?;
666 ecx.machine.threads.set_thread_local_alloc(def_id, ptr);
667 interp_ok(ptr)
668 }
669 }
670
671 #[inline]
673 fn start_regular_thread(
674 &mut self,
675 thread: Option<MPlaceTy<'tcx>>,
676 start_routine: Pointer,
677 start_abi: ExternAbi,
678 func_arg: ImmTy<'tcx>,
679 ret_layout: TyAndLayout<'tcx>,
680 ) -> InterpResult<'tcx, ThreadId> {
681 let ecx = self.eval_context_mut();
682
683 let current_span = ecx.machine.current_user_relevant_span();
685 let new_thread_id = ecx.machine.threads.create_thread({
686 let mut state = tls::TlsDtorsState::new_for_thread_finish();
687 Box::new(move |m| state.on_stack_empty(m))
688 });
689 match &mut ecx.machine.data_race {
690 GlobalDataRaceHandler::None => {}
691 GlobalDataRaceHandler::Vclocks(data_race) =>
692 data_race.thread_created(&ecx.machine.threads, new_thread_id, current_span),
693 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
694 genmc_ctx.handle_thread_create(
695 &ecx.machine.threads,
696 start_routine,
697 &func_arg,
698 new_thread_id,
699 )?,
700 }
701 if let Some(thread_info_place) = thread {
704 ecx.write_scalar(
705 Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
706 &thread_info_place,
707 )?;
708 }
709
710 let old_thread_id = ecx.machine.threads.set_active_thread(new_thread_id);
713
714 if let Some(thread_cpu_affinity) = &mut ecx.machine.thread_cpu_affinity
717 && let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
718 {
719 thread_cpu_affinity.insert(new_thread_id, cpuset);
720 }
721
722 let instance = ecx.get_ptr_fn(start_routine)?.as_instance()?;
724
725 let ret_place = ecx.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
729
730 ecx.call_thread_root_function(
731 instance,
732 start_abi,
733 &[func_arg],
734 Some(&ret_place),
735 current_span,
736 )?;
737
738 ecx.machine.threads.set_active_thread(old_thread_id);
740
741 interp_ok(new_thread_id)
742 }
743
744 fn replace_active_thread_stack(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) {
748 let ecx = self.eval_context_mut();
749 let thread = ecx.active_thread_mut();
750 let span = thread.current_user_relevant_span();
751
752 thread.stack.clear();
753 thread.origin_span = span;
754 thread.top_user_relevant_frame = None;
755 thread.on_stack_empty = Some(on_stack_empty);
756 }
757
758 fn terminate_active_thread(&mut self, for_exit: bool) -> InterpResult<'tcx> {
764 let ecx = self.eval_context_mut();
765
766 let thread = ecx.active_thread_mut();
768 thread.state = ThreadState::Terminated;
769 assert!(
770 for_exit || thread.stack.is_empty(),
771 "only threads with an empty stack can be terminated"
772 );
773
774 let gone_thread = ecx.active_thread();
776 {
777 let mut free_tls_statics = Vec::new();
778 ecx.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut ptr| {
779 if thread != gone_thread {
780 return true;
782 }
783 free_tls_statics.push(ptr);
786 false
787 });
788 for ptr in free_tls_statics {
790 if for_exit {
791 if let Some(alloc) = ptr.provenance.get_alloc_id() {
793 trace!("Thread-local static leaked and stored as static root: {:?}", alloc);
794 ecx.machine.static_roots.push(alloc);
795 }
796 } else {
797 ecx.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?;
798 }
799 }
800 }
801
802 match &mut ecx.machine.data_race {
803 GlobalDataRaceHandler::None => {}
804 GlobalDataRaceHandler::Vclocks(data_race) =>
805 data_race.thread_terminated(&ecx.machine.threads),
806 GlobalDataRaceHandler::Genmc(genmc_ctx) => {
807 genmc_ctx.handle_thread_finish(&ecx.machine.threads)
810 }
811 }
812
813 let unblock_reason = BlockReason::Join(gone_thread);
815 let threads = &ecx.machine.threads.threads;
816 let joining_threads = threads
817 .iter_enumerated()
818 .filter(|(_, thread)| thread.state.is_blocked_on(unblock_reason))
819 .map(|(id, _)| id)
820 .collect::<Vec<_>>();
821 for thread in joining_threads {
822 ecx.unblock_thread(thread, unblock_reason)?;
823 }
824
825 interp_ok(())
826 }
827
828 #[inline]
831 fn block_thread(
832 &mut self,
833 reason: BlockReason,
834 deadline: Option<Deadline>,
835 callback: DynUnblockCallback<'tcx>,
836 ) {
837 let ecx = self.eval_context_mut();
838 if deadline.is_some() && ecx.machine.data_race.as_genmc_ref().is_some() {
839 panic!("Unimplemented: Timeouts not yet supported in GenMC mode.");
840 }
841 if matches!(deadline, Some(Deadline::RealTime(_))) && !ecx.machine.communicate() {
842 panic!("cannot have `RealTime` timeout with isolation");
843 }
844 ecx.machine.threads.block_thread(reason, deadline, callback);
845 }
846
847 fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) -> InterpResult<'tcx> {
850 let ecx = self.eval_context_mut();
851 let old_state =
852 mem::replace(&mut ecx.machine.threads.threads[thread].state, ThreadState::Enabled);
853 let callback = match old_state {
854 ThreadState::Blocked { reason: actual_reason, callback, .. } => {
855 assert_eq!(
856 reason, actual_reason,
857 "unblock_thread: thread was blocked for the wrong reason"
858 );
859 callback
860 }
861 _ => panic!("unblock_thread: thread was not blocked"),
862 };
863 let old_thread = ecx.machine.threads.set_active_thread(thread);
865 callback.call(ecx, UnblockKind::Ready)?;
866 ecx.machine.threads.set_active_thread(old_thread);
867 interp_ok(())
868 }
869
870 fn unblock_expired_deadlines(&mut self) -> InterpResult<'tcx, Option<Duration>> {
875 let ecx = self.eval_context_mut();
876 let communicate = ecx.machine.communicate();
877
878 let mut min_wait_time = Option::<Duration>::None;
879 let mut callbacks = Vec::new();
880
881 for (id, thread) in ecx.machine.threads.threads.iter_enumerated_mut() {
882 match &thread.state {
883 ThreadState::Blocked { deadline: Some(deadline), .. } => {
884 let wait_time = match deadline {
885 Deadline::Monotonic(instant) =>
886 instant.duration_since(ecx.machine.monotonic_clock.now()),
887 Deadline::RealTime(time) => {
888 assert!(communicate, "cannot have `RealTime` timeout with isolation");
889 time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)
890 }
891 };
892
893 if wait_time.is_zero() {
894 let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
896 let ThreadState::Blocked { callback, .. } = old_state else {
897 unreachable!()
898 };
899 callbacks.push((id, callback));
901 } else {
902 min_wait_time = Some(wait_time.min(min_wait_time.unwrap_or(Duration::MAX)));
905 }
906 }
907 _ => {}
908 }
909 }
910
911 for (thread, callback) in callbacks {
912 let old_thread = ecx.machine.threads.set_active_thread(thread);
919 callback.call(ecx, UnblockKind::TimedOut)?;
920 ecx.machine.threads.set_active_thread(old_thread);
921 }
922
923 interp_ok(min_wait_time)
924 }
925
926 #[inline]
927 fn detach_thread(
928 &mut self,
929 thread_id: ThreadId,
930 allow_terminated_joined: bool,
931 ) -> InterpResult<'tcx> {
932 let ecx = self.eval_context_mut();
933 ecx.machine.threads.detach_thread(thread_id, allow_terminated_joined)
934 }
935
936 fn join_thread(
940 &mut self,
941 joined_thread_id: ThreadId,
942 success_retval: Scalar,
943 return_dest: &MPlaceTy<'tcx>,
944 ) -> InterpResult<'tcx> {
945 let ecx = self.eval_context_mut();
946 let thread_mgr = &mut ecx.machine.threads;
947 if thread_mgr.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
948 throw_ub_format!("trying to join a detached thread");
950 }
951
952 fn after_join<'tcx>(
953 ecx: &mut MiriInterpCx<'tcx>,
954 joined_thread_id: ThreadId,
955 success_retval: Scalar,
956 return_dest: &MPlaceTy<'tcx>,
957 ) -> InterpResult<'tcx> {
958 let threads = &ecx.machine.threads;
959 match &mut ecx.machine.data_race {
960 GlobalDataRaceHandler::None => {}
961 GlobalDataRaceHandler::Vclocks(data_race) =>
962 data_race.thread_joined(threads, joined_thread_id),
963 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
964 genmc_ctx.handle_thread_join(threads.active_thread, joined_thread_id)?,
965 }
966 ecx.write_scalar(success_retval, return_dest)?;
967 interp_ok(())
968 }
969
970 thread_mgr.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
973 if !thread_mgr.threads[joined_thread_id].state.is_terminated() {
974 trace!(
975 "{:?} blocked on {:?} when trying to join",
976 thread_mgr.active_thread, joined_thread_id
977 );
978 if let Some(genmc_ctx) = ecx.machine.data_race.as_genmc_ref() {
979 genmc_ctx.handle_thread_join(thread_mgr.active_thread, joined_thread_id)?;
980 }
981
982 let dest = return_dest.clone();
985 thread_mgr.block_thread(
986 BlockReason::Join(joined_thread_id),
987 None,
988 callback!(
989 @capture<'tcx> {
990 joined_thread_id: ThreadId,
991 dest: MPlaceTy<'tcx>,
992 success_retval: Scalar,
993 }
994 |ecx, unblock: UnblockKind| {
995 assert_eq!(unblock, UnblockKind::Ready);
996 after_join(ecx, joined_thread_id, success_retval, &dest)
997 }
998 ),
999 );
1000 } else {
1001 after_join(ecx, joined_thread_id, success_retval, return_dest)?;
1003 }
1004 interp_ok(())
1005 }
1006
1007 fn join_thread_exclusive(
1012 &mut self,
1013 joined_thread_id: ThreadId,
1014 success_retval: Scalar,
1015 return_dest: &MPlaceTy<'tcx>,
1016 ) -> InterpResult<'tcx> {
1017 let ecx = self.eval_context_mut();
1018 let threads = &ecx.machine.threads.threads;
1019 if threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
1020 throw_ub_format!("trying to join an already joined thread");
1021 }
1022
1023 if joined_thread_id == ecx.machine.threads.active_thread {
1024 throw_ub_format!("trying to join itself");
1025 }
1026
1027 assert!(
1029 threads
1030 .iter()
1031 .all(|thread| { !thread.state.is_blocked_on(BlockReason::Join(joined_thread_id)) }),
1032 "this thread already has threads waiting for its termination"
1033 );
1034
1035 ecx.join_thread(joined_thread_id, success_retval, return_dest)
1036 }
1037
1038 #[inline]
1039 fn active_thread(&self) -> ThreadId {
1040 let ecx = self.eval_context_ref();
1041 ecx.machine.threads.active_thread()
1042 }
1043
1044 #[inline]
1045 fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
1046 let ecx = self.eval_context_mut();
1047 ecx.machine.threads.active_thread_mut()
1048 }
1049
1050 #[inline]
1051 fn active_thread_ref(&self) -> &Thread<'tcx> {
1052 let ecx = self.eval_context_ref();
1053 ecx.machine.threads.active_thread_ref()
1054 }
1055
1056 #[inline]
1057 fn get_total_thread_count(&self) -> usize {
1058 let ecx = self.eval_context_ref();
1059 ecx.machine.threads.get_total_thread_count()
1060 }
1061
1062 #[inline]
1063 fn have_all_terminated(&self) -> bool {
1064 let ecx = self.eval_context_ref();
1065 ecx.machine.threads.have_all_terminated()
1066 }
1067
1068 #[inline]
1069 fn enable_thread(&mut self, thread_id: ThreadId) {
1070 let ecx = self.eval_context_mut();
1071 ecx.machine.threads.enable_thread(thread_id);
1072 }
1073
1074 #[inline]
1075 fn active_thread_stack<'a>(&'a self) -> &'a [Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
1076 let ecx = self.eval_context_ref();
1077 ecx.machine.threads.active_thread_stack()
1078 }
1079
1080 #[inline]
1081 fn active_thread_stack_mut<'a>(
1082 &'a mut self,
1083 ) -> &'a mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
1084 let ecx = self.eval_context_mut();
1085 ecx.machine.threads.active_thread_stack_mut()
1086 }
1087
1088 #[inline]
1090 fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
1091 self.eval_context_mut().machine.threads.set_thread_name(thread, new_thread_name);
1092 }
1093
1094 #[inline]
1095 fn get_thread_name<'c>(&'c self, thread: ThreadId) -> Option<&'c [u8]>
1096 where
1097 'tcx: 'c,
1098 {
1099 self.eval_context_ref().machine.threads.get_thread_name(thread)
1100 }
1101
1102 #[inline]
1103 fn yield_active_thread(&mut self) {
1104 self.eval_context_mut().machine.threads.yield_active_thread = true;
1108 }
1109
1110 #[inline]
1111 fn maybe_preempt_active_thread(&mut self) {
1112 let ecx = self.eval_context_mut();
1113 if !ecx.machine.threads.fixed_scheduling
1114 && ecx.machine.rng.get_mut().random_bool(ecx.machine.preemption_rate)
1115 {
1116 ecx.yield_active_thread();
1117 }
1118 }
1119}