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 TlsAllocAction {
25 Deallocate,
27 Leak,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq)]
34pub enum UnblockKind {
35 Ready,
37 TimedOut,
39}
40
41pub type DynUnblockCallback<'tcx> = DynMachineCallback<'tcx, UnblockKind>;
44
45#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
47pub struct ThreadId(u32);
48
49impl ThreadId {
50 pub fn to_u32(self) -> u32 {
51 self.0
52 }
53
54 pub fn new_unchecked(id: u32) -> Self {
56 Self(id)
57 }
58
59 pub const MAIN_THREAD: ThreadId = ThreadId(0);
60}
61
62impl Idx for ThreadId {
63 fn new(idx: usize) -> Self {
64 ThreadId(u32::try_from(idx).unwrap())
65 }
66
67 fn index(self) -> usize {
68 usize::try_from(self.0).unwrap()
69 }
70}
71
72impl From<ThreadId> for u64 {
73 fn from(t: ThreadId) -> Self {
74 t.0.into()
75 }
76}
77
78#[derive(Debug, Copy, Clone, PartialEq, Eq)]
80pub enum BlockReason {
81 Join(ThreadId),
84 Sleep,
86 Mutex,
88 Condvar,
90 RwLock,
92 Futex,
94 InitOnce,
96 Readiness,
98 Eventfd,
100 VirtualSocket,
102 IO,
104 Genmc,
107}
108
109enum ThreadState<'tcx> {
111 Enabled,
113 Blocked { reason: BlockReason, deadline: Option<Deadline>, callback: DynUnblockCallback<'tcx> },
115 Terminated,
118}
119
120impl<'tcx> std::fmt::Debug for ThreadState<'tcx> {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 Self::Enabled => write!(f, "Enabled"),
124 Self::Blocked { reason, deadline, .. } =>
125 f.debug_struct("Blocked")
126 .field("reason", reason)
127 .field("deadline", deadline)
128 .finish(),
129 Self::Terminated => write!(f, "Terminated"),
130 }
131 }
132}
133
134impl<'tcx> ThreadState<'tcx> {
135 fn is_enabled(&self) -> bool {
136 matches!(self, ThreadState::Enabled)
137 }
138
139 fn is_terminated(&self) -> bool {
140 matches!(self, ThreadState::Terminated)
141 }
142
143 fn is_blocked_on(&self, reason: BlockReason) -> bool {
144 matches!(self, ThreadState::Blocked { reason: actual_reason, .. } if *actual_reason == reason)
145 }
146}
147
148#[derive(Debug, Copy, Clone, PartialEq, Eq)]
150enum ThreadJoinStatus {
151 Joinable,
153 Detached,
156 Joined,
158}
159
160pub struct Thread<'tcx> {
162 state: ThreadState<'tcx>,
163
164 thread_name: Option<Vec<u8>>,
166
167 stack: Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
169
170 pub(crate) origin_span: Span,
173
174 pub(crate) on_stack_empty: Option<StackEmptyCallback<'tcx>>,
179
180 top_user_relevant_frame: Option<usize>,
185
186 join_status: ThreadJoinStatus,
188
189 pub(crate) unwind_payloads: Vec<ImmTy<'tcx>>,
198
199 pub(crate) last_error: Option<MPlaceTy<'tcx>>,
201}
202
203pub type StackEmptyCallback<'tcx> =
204 Box<dyn FnMut(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Poll<()>> + 'tcx>;
205
206impl<'tcx> Thread<'tcx> {
207 fn thread_name(&self) -> Option<&[u8]> {
209 self.thread_name.as_deref()
210 }
211
212 pub fn is_enabled(&self) -> bool {
214 self.state.is_enabled()
215 }
216
217 pub fn is_terminated(&self) -> bool {
218 self.state.is_terminated()
219 }
220
221 pub fn is_blocked_on(&self, reason: BlockReason) -> bool {
222 self.state.is_blocked_on(reason)
223 }
224
225 fn thread_display_name(&self, id: ThreadId) -> String {
227 if let Some(ref thread_name) = self.thread_name {
228 String::from_utf8_lossy(thread_name).into_owned()
229 } else {
230 format!("unnamed-{}", id.index())
231 }
232 }
233
234 fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
240 let mut best = None;
242 for (idx, frame) in self.stack.iter().enumerate().rev().skip(skip) {
243 let relevance = frame.extra.user_relevance;
244 if relevance == u8::MAX {
245 return Some(idx);
247 }
248 if best.is_none_or(|(_best_idx, best_relevance)| best_relevance < relevance) {
249 best = Some((idx, relevance));
252 }
253 }
254 best.map(|(idx, _relevance)| idx)
255 }
256
257 pub fn recompute_top_user_relevant_frame(&mut self, skip: usize) {
260 self.top_user_relevant_frame = self.compute_top_user_relevant_frame(skip);
261 }
262
263 pub fn set_top_user_relevant_frame(&mut self, frame_idx: usize) {
266 debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame(0));
267 self.top_user_relevant_frame = Some(frame_idx);
268 }
269
270 pub fn top_user_relevant_frame(&self) -> Option<usize> {
273 self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1))
277 }
278
279 pub fn current_user_relevance(&self) -> u8 {
280 self.top_user_relevant_frame()
281 .map(|frame_idx| self.stack[frame_idx].extra.user_relevance)
282 .unwrap_or(0)
283 }
284
285 pub fn current_user_relevant_span(&self) -> Span {
286 debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame(0));
287 self.top_user_relevant_frame()
288 .map(|frame_idx| self.stack[frame_idx].current_span())
289 .unwrap_or(rustc_span::DUMMY_SP)
290 }
291}
292
293impl<'tcx> std::fmt::Debug for Thread<'tcx> {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 write!(
296 f,
297 "{}({:?}, {:?})",
298 String::from_utf8_lossy(self.thread_name().unwrap_or(b"<unnamed>")),
299 self.state,
300 self.join_status
301 )
302 }
303}
304
305impl<'tcx> Thread<'tcx> {
306 fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'tcx>>) -> Self {
307 Self {
308 state: ThreadState::Enabled,
309 thread_name: name.map(|name| Vec::from(name.as_bytes())),
310 stack: Vec::new(),
311 origin_span: DUMMY_SP,
312 top_user_relevant_frame: None,
313 join_status: ThreadJoinStatus::Joinable,
314 unwind_payloads: Vec::new(),
315 last_error: None,
316 on_stack_empty,
317 }
318 }
319}
320
321impl VisitProvenance for Thread<'_> {
322 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
323 let Thread {
324 unwind_payloads: panic_payload,
325 last_error,
326 stack,
327 origin_span: _,
328 top_user_relevant_frame: _,
329 state: _,
330 thread_name: _,
331 join_status: _,
332 on_stack_empty: _, } = self;
334
335 for payload in panic_payload {
336 payload.visit_provenance(visit);
337 }
338 last_error.visit_provenance(visit);
339 for frame in stack {
340 frame.visit_provenance(visit)
341 }
342 }
343}
344
345impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
346 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
347 let return_place = self.return_place();
348 let locals = self.locals();
349 let Frame {
350 extra,
351 ..
353 } = self;
354
355 return_place.visit_provenance(visit);
357 for local in locals.iter() {
359 match local.as_mplace_or_imm_ghost() {
361 None => {}
362 Some(Either::Left((ptr, meta))) => {
363 ptr.visit_provenance(visit);
364 meta.visit_provenance(visit);
365 }
366 Some(Either::Right(imm)) => {
367 imm.visit_provenance(visit);
368 }
369 }
370 }
371
372 extra.visit_provenance(visit);
373 }
374}
375
376#[derive(Debug, Copy, Clone)]
378pub enum ThreadLookupError {
379 InvalidId,
381 Terminated(ThreadId),
383}
384
385#[derive(Debug)]
387pub struct ThreadManager<'tcx> {
388 active_thread: ThreadId,
390 threads: IndexVec<ThreadId, Thread<'tcx>>,
394 thread_local_allocs: FxHashMap<(DefId, ThreadId), StrictPointer>,
396 pub(super) yield_active_thread: bool,
399 fixed_scheduling: bool,
401}
402
403impl VisitProvenance for ThreadManager<'_> {
404 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
405 let ThreadManager {
406 threads,
407 thread_local_allocs,
408 active_thread: _,
409 yield_active_thread: _,
410 fixed_scheduling: _,
411 } = self;
412
413 for thread in threads {
414 thread.visit_provenance(visit);
415 }
416 for ptr in thread_local_allocs.values() {
417 ptr.visit_provenance(visit);
418 }
419 }
420}
421
422impl<'tcx> ThreadManager<'tcx> {
423 pub(crate) fn new(config: &MiriConfig) -> Self {
424 let mut threads = IndexVec::new();
425 threads.push(Thread::new(Some("main"), None));
427 Self {
428 active_thread: ThreadId::MAIN_THREAD,
429 threads,
430 thread_local_allocs: Default::default(),
431 yield_active_thread: false,
432 fixed_scheduling: config.fixed_scheduling,
433 }
434 }
435
436 pub(crate) fn init(
437 ecx: &mut MiriInterpCx<'tcx>,
438 on_main_stack_empty: StackEmptyCallback<'tcx>,
439 ) {
440 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty =
441 Some(on_main_stack_empty);
442 if ecx.tcx.sess.target.os != Os::Windows {
443 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
445 ThreadJoinStatus::Detached;
446 }
447 }
448
449 pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
453 if let Ok(id) = id.try_into()
454 && usize::try_from(id).is_ok_and(|id| id < self.threads.len())
455 {
456 let thread_id = ThreadId(id);
457 if self.threads[thread_id].state.is_terminated() {
458 Err(ThreadLookupError::Terminated(thread_id))
459 } else {
460 Ok(thread_id)
461 }
462 } else {
463 Err(ThreadLookupError::InvalidId)
464 }
465 }
466
467 fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<StrictPointer> {
470 self.thread_local_allocs.get(&(def_id, self.active_thread)).cloned()
471 }
472
473 fn set_thread_local_alloc(&mut self, def_id: DefId, ptr: StrictPointer) {
478 self.thread_local_allocs.try_insert((def_id, self.active_thread), ptr).unwrap();
479 }
480
481 pub fn active_thread_stack(&self) -> &[Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
483 &self.threads[self.active_thread].stack
484 }
485
486 pub fn active_thread_stack_mut(
488 &mut self,
489 ) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
490 &mut self.threads[self.active_thread].stack
491 }
492
493 pub(super) fn all_threads(&self) -> impl Iterator<Item = (ThreadId, &Thread<'tcx>)> {
494 self.threads.iter_enumerated()
495 }
496
497 pub fn all_blocked_stacks(
498 &self,
499 ) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
500 self.threads
501 .iter_enumerated()
502 .filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. }))
503 .map(|(id, t)| (id, &t.stack[..]))
504 }
505
506 fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
508 let new_thread_id = ThreadId::new(self.threads.len());
509 self.threads.push(Thread::new(None, Some(on_stack_empty)));
510 new_thread_id
511 }
512
513 pub(super) fn set_active_thread(&mut self, id: ThreadId) -> ThreadId {
515 assert!(id.index() < self.threads.len());
516 info!(
517 "---------- Now executing on thread `{}` (previous: `{}`) ----------------------------------------",
518 self.get_thread_display_name(id),
519 self.get_thread_display_name(self.active_thread)
520 );
521 std::mem::replace(&mut self.active_thread, id)
522 }
523
524 pub fn active_thread(&self) -> ThreadId {
526 self.active_thread
527 }
528
529 pub fn get_total_thread_count(&self) -> usize {
531 self.threads.len()
532 }
533
534 pub fn get_live_thread_count(&self) -> usize {
537 self.threads.iter().filter(|t| !t.state.is_terminated()).count()
538 }
539
540 fn has_terminated(&self, thread_id: ThreadId) -> bool {
542 self.threads[thread_id].state.is_terminated()
543 }
544
545 fn have_all_terminated(&self) -> bool {
547 self.threads.iter().all(|thread| thread.state.is_terminated())
548 }
549
550 fn enable_thread(&mut self, thread_id: ThreadId) {
552 assert!(self.has_terminated(thread_id));
553 self.threads[thread_id].state = ThreadState::Enabled;
554 }
555
556 pub fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
558 &mut self.threads[self.active_thread]
559 }
560
561 pub fn active_thread_ref(&self) -> &Thread<'tcx> {
563 &self.threads[self.active_thread]
564 }
565
566 pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> {
567 &self.threads[thread_id]
568 }
569
570 fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
579 trace!("detaching {:?}", id);
581
582 let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() {
583 self.threads[id].join_status == ThreadJoinStatus::Detached
585 } else {
586 self.threads[id].join_status != ThreadJoinStatus::Joinable
587 };
588 if is_ub {
589 throw_ub_format!("trying to detach thread that was already detached or joined");
590 }
591
592 self.threads[id].join_status = ThreadJoinStatus::Detached;
593 interp_ok(())
594 }
595
596 pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
598 self.threads[thread].thread_name = Some(new_thread_name);
599 }
600
601 pub fn get_thread_name(&self, thread: ThreadId) -> Option<&[u8]> {
603 self.threads[thread].thread_name()
604 }
605
606 pub fn get_thread_display_name(&self, thread: ThreadId) -> String {
607 self.threads[thread].thread_display_name(thread)
608 }
609
610 fn block_thread(
612 &mut self,
613 reason: BlockReason,
614 deadline: Option<Deadline>,
615 callback: DynUnblockCallback<'tcx>,
616 ) {
617 let state = &mut self.threads[self.active_thread].state;
618 assert!(state.is_enabled());
619 *state = ThreadState::Blocked { reason, deadline, callback }
620 }
621
622 pub fn fixed_scheduling(&self) -> bool {
623 self.fixed_scheduling
624 }
625}
626
627impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
629pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
630 #[inline]
631 fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
632 self.eval_context_ref().machine.threads.thread_id_try_from(id)
633 }
634
635 fn get_or_create_thread_local_alloc(
638 &mut self,
639 def_id: DefId,
640 ) -> InterpResult<'tcx, StrictPointer> {
641 let this = self.eval_context_mut();
642 let tcx = this.tcx;
643 if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) {
644 interp_ok(old_alloc)
647 } else {
648 if tcx.is_foreign_item(def_id) {
652 throw_unsup_format!("foreign thread-local statics are not supported");
653 }
654 let params = this.machine.get_default_alloc_params();
655 let alloc = this.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
656 let mut alloc = alloc.inner().adjust_from_tcx(
658 &this.tcx,
659 |bytes, align| {
660 interp_ok(MiriAllocBytes::from_bytes(
661 std::borrow::Cow::Borrowed(bytes),
662 align,
663 params,
664 ))
665 },
666 |ptr| this.global_root_pointer(ptr),
667 )?;
668 alloc.mutability = Mutability::Mut;
670 let ptr = this.insert_allocation(alloc, MiriMemoryKind::Tls.into())?;
672 this.machine.threads.set_thread_local_alloc(def_id, ptr);
673 interp_ok(ptr)
674 }
675 }
676
677 #[inline]
679 fn start_regular_thread(
680 &mut self,
681 thread: Option<MPlaceTy<'tcx>>,
682 start_routine: Pointer,
683 start_abi: ExternAbi,
684 func_arg: ImmTy<'tcx>,
685 ret_layout: TyAndLayout<'tcx>,
686 ) -> InterpResult<'tcx, ThreadId> {
687 let this = self.eval_context_mut();
688
689 let current_span = this.machine.current_user_relevant_span();
691 let new_thread_id = this.machine.threads.create_thread({
692 let mut state = tls::TlsDtorsState::default();
693 Box::new(move |m| state.on_stack_empty(m))
694 });
695 match &mut this.machine.data_race {
696 GlobalDataRaceHandler::None => {}
697 GlobalDataRaceHandler::Vclocks(data_race) =>
698 data_race.thread_created(&this.machine.threads, new_thread_id, current_span),
699 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
700 genmc_ctx.handle_thread_create(
701 &this.machine.threads,
702 start_routine,
703 &func_arg,
704 new_thread_id,
705 )?,
706 }
707 if let Some(thread_info_place) = thread {
710 this.write_scalar(
711 Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
712 &thread_info_place,
713 )?;
714 }
715
716 let old_thread_id = this.machine.threads.set_active_thread(new_thread_id);
719
720 if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity
723 && let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
724 {
725 thread_cpu_affinity.insert(new_thread_id, cpuset);
726 }
727
728 let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
730
731 let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
735
736 this.call_thread_root_function(
737 instance,
738 start_abi,
739 &[func_arg],
740 Some(&ret_place),
741 current_span,
742 )?;
743
744 this.machine.threads.set_active_thread(old_thread_id);
746
747 interp_ok(new_thread_id)
748 }
749
750 fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
755 let this = self.eval_context_mut();
756
757 let thread = this.active_thread_mut();
759 assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");
760 thread.state = ThreadState::Terminated;
761
762 let gone_thread = this.active_thread();
764 {
765 let mut free_tls_statics = Vec::new();
766 this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut alloc_id| {
767 if thread != gone_thread {
768 return true;
770 }
771 free_tls_statics.push(alloc_id);
774 false
775 });
776 for ptr in free_tls_statics {
778 match tls_alloc_action {
779 TlsAllocAction::Deallocate =>
780 this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?,
781 TlsAllocAction::Leak =>
782 if let Some(alloc) = ptr.provenance.get_alloc_id() {
783 trace!(
784 "Thread-local static leaked and stored as static root: {:?}",
785 alloc
786 );
787 this.machine.static_roots.push(alloc);
788 },
789 }
790 }
791 }
792
793 match &mut this.machine.data_race {
794 GlobalDataRaceHandler::None => {}
795 GlobalDataRaceHandler::Vclocks(data_race) =>
796 data_race.thread_terminated(&this.machine.threads),
797 GlobalDataRaceHandler::Genmc(genmc_ctx) => {
798 genmc_ctx.handle_thread_finish(&this.machine.threads)
801 }
802 }
803
804 let unblock_reason = BlockReason::Join(gone_thread);
806 let threads = &this.machine.threads.threads;
807 let joining_threads = threads
808 .iter_enumerated()
809 .filter(|(_, thread)| thread.state.is_blocked_on(unblock_reason))
810 .map(|(id, _)| id)
811 .collect::<Vec<_>>();
812 for thread in joining_threads {
813 this.unblock_thread(thread, unblock_reason)?;
814 }
815
816 interp_ok(())
817 }
818
819 #[inline]
822 fn block_thread(
823 &mut self,
824 reason: BlockReason,
825 deadline: Option<Deadline>,
826 callback: DynUnblockCallback<'tcx>,
827 ) {
828 let this = self.eval_context_mut();
829 if deadline.is_some() && this.machine.data_race.as_genmc_ref().is_some() {
830 panic!("Unimplemented: Timeouts not yet supported in GenMC mode.");
831 }
832 if matches!(deadline, Some(Deadline::RealTime(_))) && !this.machine.communicate() {
833 panic!("cannot have `RealTime` timeout with isolation");
834 }
835 this.machine.threads.block_thread(reason, deadline, callback);
836 }
837
838 fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) -> InterpResult<'tcx> {
841 let this = self.eval_context_mut();
842 let old_state =
843 mem::replace(&mut this.machine.threads.threads[thread].state, ThreadState::Enabled);
844 let callback = match old_state {
845 ThreadState::Blocked { reason: actual_reason, callback, .. } => {
846 assert_eq!(
847 reason, actual_reason,
848 "unblock_thread: thread was blocked for the wrong reason"
849 );
850 callback
851 }
852 _ => panic!("unblock_thread: thread was not blocked"),
853 };
854 let old_thread = this.machine.threads.set_active_thread(thread);
856 callback.call(this, UnblockKind::Ready)?;
857 this.machine.threads.set_active_thread(old_thread);
858 interp_ok(())
859 }
860
861 fn unblock_expired_deadlines(&mut self) -> InterpResult<'tcx, Option<Duration>> {
866 let this = self.eval_context_mut();
867 let communicate = this.machine.communicate();
868
869 let mut min_wait_time = Option::<Duration>::None;
870 let mut callbacks = Vec::new();
871
872 for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() {
873 match &thread.state {
874 ThreadState::Blocked { deadline: Some(deadline), .. } => {
875 let wait_time = match deadline {
876 Deadline::Monotonic(instant) =>
877 instant.duration_since(this.machine.monotonic_clock.now()),
878 Deadline::RealTime(time) => {
879 assert!(communicate, "cannot have `RealTime` timeout with isolation");
880 time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)
881 }
882 };
883
884 if wait_time.is_zero() {
885 let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
887 let ThreadState::Blocked { callback, .. } = old_state else {
888 unreachable!()
889 };
890 callbacks.push((id, callback));
892 } else {
893 min_wait_time = Some(wait_time.min(min_wait_time.unwrap_or(Duration::MAX)));
896 }
897 }
898 _ => {}
899 }
900 }
901
902 for (thread, callback) in callbacks {
903 let old_thread = this.machine.threads.set_active_thread(thread);
910 callback.call(this, UnblockKind::TimedOut)?;
911 this.machine.threads.set_active_thread(old_thread);
912 }
913
914 interp_ok(min_wait_time)
915 }
916
917 #[inline]
918 fn detach_thread(
919 &mut self,
920 thread_id: ThreadId,
921 allow_terminated_joined: bool,
922 ) -> InterpResult<'tcx> {
923 let this = self.eval_context_mut();
924 this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
925 }
926
927 fn join_thread(
931 &mut self,
932 joined_thread_id: ThreadId,
933 success_retval: Scalar,
934 return_dest: &MPlaceTy<'tcx>,
935 ) -> InterpResult<'tcx> {
936 let this = self.eval_context_mut();
937 let thread_mgr = &mut this.machine.threads;
938 if thread_mgr.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
939 throw_ub_format!("trying to join a detached thread");
941 }
942
943 fn after_join<'tcx>(
944 this: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
945 joined_thread_id: ThreadId,
946 success_retval: Scalar,
947 return_dest: &MPlaceTy<'tcx>,
948 ) -> InterpResult<'tcx> {
949 let threads = &this.machine.threads;
950 match &mut this.machine.data_race {
951 GlobalDataRaceHandler::None => {}
952 GlobalDataRaceHandler::Vclocks(data_race) =>
953 data_race.thread_joined(threads, joined_thread_id),
954 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
955 genmc_ctx.handle_thread_join(threads.active_thread, joined_thread_id)?,
956 }
957 this.write_scalar(success_retval, return_dest)?;
958 interp_ok(())
959 }
960
961 thread_mgr.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
964 if !thread_mgr.threads[joined_thread_id].state.is_terminated() {
965 trace!(
966 "{:?} blocked on {:?} when trying to join",
967 thread_mgr.active_thread, joined_thread_id
968 );
969 if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
970 genmc_ctx.handle_thread_join(thread_mgr.active_thread, joined_thread_id)?;
971 }
972
973 let dest = return_dest.clone();
976 thread_mgr.block_thread(
977 BlockReason::Join(joined_thread_id),
978 None,
979 callback!(
980 @capture<'tcx> {
981 joined_thread_id: ThreadId,
982 dest: MPlaceTy<'tcx>,
983 success_retval: Scalar,
984 }
985 |this, unblock: UnblockKind| {
986 assert_eq!(unblock, UnblockKind::Ready);
987 after_join(this, joined_thread_id, success_retval, &dest)
988 }
989 ),
990 );
991 } else {
992 after_join(this, joined_thread_id, success_retval, return_dest)?;
994 }
995 interp_ok(())
996 }
997
998 fn join_thread_exclusive(
1003 &mut self,
1004 joined_thread_id: ThreadId,
1005 success_retval: Scalar,
1006 return_dest: &MPlaceTy<'tcx>,
1007 ) -> InterpResult<'tcx> {
1008 let this = self.eval_context_mut();
1009 let threads = &this.machine.threads.threads;
1010 if threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
1011 throw_ub_format!("trying to join an already joined thread");
1012 }
1013
1014 if joined_thread_id == this.machine.threads.active_thread {
1015 throw_ub_format!("trying to join itself");
1016 }
1017
1018 assert!(
1020 threads
1021 .iter()
1022 .all(|thread| { !thread.state.is_blocked_on(BlockReason::Join(joined_thread_id)) }),
1023 "this thread already has threads waiting for its termination"
1024 );
1025
1026 this.join_thread(joined_thread_id, success_retval, return_dest)
1027 }
1028
1029 #[inline]
1030 fn active_thread(&self) -> ThreadId {
1031 let this = self.eval_context_ref();
1032 this.machine.threads.active_thread()
1033 }
1034
1035 #[inline]
1036 fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
1037 let this = self.eval_context_mut();
1038 this.machine.threads.active_thread_mut()
1039 }
1040
1041 #[inline]
1042 fn active_thread_ref(&self) -> &Thread<'tcx> {
1043 let this = self.eval_context_ref();
1044 this.machine.threads.active_thread_ref()
1045 }
1046
1047 #[inline]
1048 fn get_total_thread_count(&self) -> usize {
1049 let this = self.eval_context_ref();
1050 this.machine.threads.get_total_thread_count()
1051 }
1052
1053 #[inline]
1054 fn have_all_terminated(&self) -> bool {
1055 let this = self.eval_context_ref();
1056 this.machine.threads.have_all_terminated()
1057 }
1058
1059 #[inline]
1060 fn enable_thread(&mut self, thread_id: ThreadId) {
1061 let this = self.eval_context_mut();
1062 this.machine.threads.enable_thread(thread_id);
1063 }
1064
1065 #[inline]
1066 fn active_thread_stack<'a>(&'a self) -> &'a [Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
1067 let this = self.eval_context_ref();
1068 this.machine.threads.active_thread_stack()
1069 }
1070
1071 #[inline]
1072 fn active_thread_stack_mut<'a>(
1073 &'a mut self,
1074 ) -> &'a mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
1075 let this = self.eval_context_mut();
1076 this.machine.threads.active_thread_stack_mut()
1077 }
1078
1079 #[inline]
1081 fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
1082 self.eval_context_mut().machine.threads.set_thread_name(thread, new_thread_name);
1083 }
1084
1085 #[inline]
1086 fn get_thread_name<'c>(&'c self, thread: ThreadId) -> Option<&'c [u8]>
1087 where
1088 'tcx: 'c,
1089 {
1090 self.eval_context_ref().machine.threads.get_thread_name(thread)
1091 }
1092
1093 #[inline]
1094 fn yield_active_thread(&mut self) {
1095 self.eval_context_mut().machine.threads.yield_active_thread = true;
1099 }
1100
1101 #[inline]
1102 fn maybe_preempt_active_thread(&mut self) {
1103 let this = self.eval_context_mut();
1104 if !this.machine.threads.fixed_scheduling
1105 && this.machine.rng.get_mut().random_bool(this.machine.preemption_rate)
1106 {
1107 this.yield_active_thread();
1108 }
1109 }
1110}