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 Semaphore,
94 Futex,
96 InitOnce,
98 Readiness,
100 Eventfd,
102 VirtualSocket,
104 IO,
106 Genmc,
109}
110
111enum ThreadState<'tcx> {
113 Enabled,
115 Blocked { reason: BlockReason, deadline: Option<Deadline>, callback: DynUnblockCallback<'tcx> },
117 Terminated,
120}
121
122impl<'tcx> std::fmt::Debug for ThreadState<'tcx> {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 Self::Enabled => write!(f, "Enabled"),
126 Self::Blocked { reason, deadline, .. } =>
127 f.debug_struct("Blocked")
128 .field("reason", reason)
129 .field("deadline", deadline)
130 .finish(),
131 Self::Terminated => write!(f, "Terminated"),
132 }
133 }
134}
135
136impl<'tcx> ThreadState<'tcx> {
137 fn is_enabled(&self) -> bool {
138 matches!(self, ThreadState::Enabled)
139 }
140
141 fn is_terminated(&self) -> bool {
142 matches!(self, ThreadState::Terminated)
143 }
144
145 fn is_blocked_on(&self, reason: BlockReason) -> bool {
146 matches!(self, ThreadState::Blocked { reason: actual_reason, .. } if *actual_reason == reason)
147 }
148}
149
150#[derive(Debug, Copy, Clone, PartialEq, Eq)]
152enum ThreadJoinStatus {
153 Joinable,
155 Detached,
158 Joined,
160}
161
162pub struct Thread<'tcx> {
164 state: ThreadState<'tcx>,
165
166 thread_name: Option<Vec<u8>>,
168
169 stack: Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
171
172 pub(crate) origin_span: Span,
175
176 pub(crate) on_stack_empty: Option<StackEmptyCallback<'tcx>>,
181
182 top_user_relevant_frame: Option<usize>,
187
188 join_status: ThreadJoinStatus,
190
191 pub(crate) unwind_payloads: Vec<ImmTy<'tcx>>,
200
201 pub(crate) last_error: Option<MPlaceTy<'tcx>>,
203}
204
205pub type StackEmptyCallback<'tcx> =
206 Box<dyn FnMut(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Poll<()>> + 'tcx>;
207
208impl<'tcx> Thread<'tcx> {
209 fn thread_name(&self) -> Option<&[u8]> {
211 self.thread_name.as_deref()
212 }
213
214 pub fn is_enabled(&self) -> bool {
216 self.state.is_enabled()
217 }
218
219 pub fn is_terminated(&self) -> bool {
220 self.state.is_terminated()
221 }
222
223 pub fn is_blocked_on(&self, reason: BlockReason) -> bool {
224 self.state.is_blocked_on(reason)
225 }
226
227 fn thread_display_name(&self, id: ThreadId) -> String {
229 if let Some(ref thread_name) = self.thread_name {
230 String::from_utf8_lossy(thread_name).into_owned()
231 } else {
232 format!("unnamed-{}", id.index())
233 }
234 }
235
236 fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
242 let mut best = None;
244 for (idx, frame) in self.stack.iter().enumerate().rev().skip(skip) {
245 let relevance = frame.extra.user_relevance;
246 if relevance == u8::MAX {
247 return Some(idx);
249 }
250 if best.is_none_or(|(_best_idx, best_relevance)| best_relevance < relevance) {
251 best = Some((idx, relevance));
254 }
255 }
256 best.map(|(idx, _relevance)| idx)
257 }
258
259 pub fn recompute_top_user_relevant_frame(&mut self, skip: usize) {
262 self.top_user_relevant_frame = self.compute_top_user_relevant_frame(skip);
263 }
264
265 pub fn set_top_user_relevant_frame(&mut self, frame_idx: usize) {
268 debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame(0));
269 self.top_user_relevant_frame = Some(frame_idx);
270 }
271
272 pub fn top_user_relevant_frame(&self) -> Option<usize> {
275 self.top_user_relevant_frame.or_else(|| self.stack.len().checked_sub(1))
279 }
280
281 pub fn current_user_relevance(&self) -> u8 {
282 self.top_user_relevant_frame()
283 .map(|frame_idx| self.stack[frame_idx].extra.user_relevance)
284 .unwrap_or(0)
285 }
286
287 pub fn current_user_relevant_span(&self) -> Span {
288 debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame(0));
289 self.top_user_relevant_frame()
290 .map(|frame_idx| self.stack[frame_idx].current_span())
291 .unwrap_or(rustc_span::DUMMY_SP)
292 }
293}
294
295impl<'tcx> std::fmt::Debug for Thread<'tcx> {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 write!(
298 f,
299 "{}({:?}, {:?})",
300 String::from_utf8_lossy(self.thread_name().unwrap_or(b"<unnamed>")),
301 self.state,
302 self.join_status
303 )
304 }
305}
306
307impl<'tcx> Thread<'tcx> {
308 fn new(name: Option<&str>, on_stack_empty: Option<StackEmptyCallback<'tcx>>) -> Self {
309 Self {
310 state: ThreadState::Enabled,
311 thread_name: name.map(|name| Vec::from(name.as_bytes())),
312 stack: Vec::new(),
313 origin_span: DUMMY_SP,
314 top_user_relevant_frame: None,
315 join_status: ThreadJoinStatus::Joinable,
316 unwind_payloads: Vec::new(),
317 last_error: None,
318 on_stack_empty,
319 }
320 }
321}
322
323impl VisitProvenance for Thread<'_> {
324 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
325 let Thread {
326 unwind_payloads: panic_payload,
327 last_error,
328 stack,
329 origin_span: _,
330 top_user_relevant_frame: _,
331 state: _,
332 thread_name: _,
333 join_status: _,
334 on_stack_empty: _, } = self;
336
337 for payload in panic_payload {
338 payload.visit_provenance(visit);
339 }
340 last_error.visit_provenance(visit);
341 for frame in stack {
342 frame.visit_provenance(visit)
343 }
344 }
345}
346
347impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
348 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
349 let return_place = self.return_place();
350 let locals = self.locals();
351 let Frame {
352 extra,
353 ..
355 } = self;
356
357 return_place.visit_provenance(visit);
359 for local in locals.iter() {
361 match local.as_mplace_or_imm_ghost() {
363 None => {}
364 Some(Either::Left((ptr, meta))) => {
365 ptr.visit_provenance(visit);
366 meta.visit_provenance(visit);
367 }
368 Some(Either::Right(imm)) => {
369 imm.visit_provenance(visit);
370 }
371 }
372 }
373
374 extra.visit_provenance(visit);
375 }
376}
377
378#[derive(Debug, Copy, Clone)]
380pub enum ThreadLookupError {
381 InvalidId,
383 Terminated(ThreadId),
385}
386
387#[derive(Debug)]
389pub struct ThreadManager<'tcx> {
390 active_thread: ThreadId,
392 threads: IndexVec<ThreadId, Thread<'tcx>>,
396 thread_local_allocs: FxHashMap<(DefId, ThreadId), StrictPointer>,
398 pub(super) yield_active_thread: bool,
401 fixed_scheduling: bool,
403}
404
405impl VisitProvenance for ThreadManager<'_> {
406 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
407 let ThreadManager {
408 threads,
409 thread_local_allocs,
410 active_thread: _,
411 yield_active_thread: _,
412 fixed_scheduling: _,
413 } = self;
414
415 for thread in threads {
416 thread.visit_provenance(visit);
417 }
418 for ptr in thread_local_allocs.values() {
419 ptr.visit_provenance(visit);
420 }
421 }
422}
423
424impl<'tcx> ThreadManager<'tcx> {
425 pub(crate) fn new(config: &MiriConfig) -> Self {
426 let mut threads = IndexVec::new();
427 threads.push(Thread::new(Some("main"), None));
429 Self {
430 active_thread: ThreadId::MAIN_THREAD,
431 threads,
432 thread_local_allocs: Default::default(),
433 yield_active_thread: false,
434 fixed_scheduling: config.fixed_scheduling,
435 }
436 }
437
438 pub(crate) fn init(
439 ecx: &mut MiriInterpCx<'tcx>,
440 on_main_stack_empty: StackEmptyCallback<'tcx>,
441 ) {
442 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty =
443 Some(on_main_stack_empty);
444 if ecx.tcx.sess.target.os != Os::Windows {
445 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
447 ThreadJoinStatus::Detached;
448 }
449 }
450
451 pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
455 if let Ok(id) = id.try_into()
456 && usize::try_from(id).is_ok_and(|id| id < self.threads.len())
457 {
458 let thread_id = ThreadId(id);
459 if self.threads[thread_id].state.is_terminated() {
460 Err(ThreadLookupError::Terminated(thread_id))
461 } else {
462 Ok(thread_id)
463 }
464 } else {
465 Err(ThreadLookupError::InvalidId)
466 }
467 }
468
469 fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<StrictPointer> {
472 self.thread_local_allocs.get(&(def_id, self.active_thread)).cloned()
473 }
474
475 fn set_thread_local_alloc(&mut self, def_id: DefId, ptr: StrictPointer) {
480 self.thread_local_allocs.try_insert((def_id, self.active_thread), ptr).unwrap();
481 }
482
483 pub fn active_thread_stack(&self) -> &[Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
485 &self.threads[self.active_thread].stack
486 }
487
488 pub fn active_thread_stack_mut(
490 &mut self,
491 ) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
492 &mut self.threads[self.active_thread].stack
493 }
494
495 pub(super) fn all_threads(&self) -> impl Iterator<Item = (ThreadId, &Thread<'tcx>)> {
496 self.threads.iter_enumerated()
497 }
498
499 pub fn all_blocked_stacks(
500 &self,
501 ) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
502 self.threads
503 .iter_enumerated()
504 .filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. }))
505 .map(|(id, t)| (id, &t.stack[..]))
506 }
507
508 fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
510 let new_thread_id = ThreadId::new(self.threads.len());
511 self.threads.push(Thread::new(None, Some(on_stack_empty)));
512 new_thread_id
513 }
514
515 pub(super) fn set_active_thread(&mut self, id: ThreadId) -> ThreadId {
517 assert!(id.index() < self.threads.len());
518 info!(
519 "---------- Now executing on thread `{}` (previous: `{}`) ----------------------------------------",
520 self.get_thread_display_name(id),
521 self.get_thread_display_name(self.active_thread)
522 );
523 std::mem::replace(&mut self.active_thread, id)
524 }
525
526 pub fn active_thread(&self) -> ThreadId {
528 self.active_thread
529 }
530
531 pub fn get_total_thread_count(&self) -> usize {
533 self.threads.len()
534 }
535
536 pub fn get_live_thread_count(&self) -> usize {
539 self.threads.iter().filter(|t| !t.state.is_terminated()).count()
540 }
541
542 fn has_terminated(&self, thread_id: ThreadId) -> bool {
544 self.threads[thread_id].state.is_terminated()
545 }
546
547 fn have_all_terminated(&self) -> bool {
549 self.threads.iter().all(|thread| thread.state.is_terminated())
550 }
551
552 fn enable_thread(&mut self, thread_id: ThreadId) {
554 assert!(self.has_terminated(thread_id));
555 self.threads[thread_id].state = ThreadState::Enabled;
556 }
557
558 pub fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
560 &mut self.threads[self.active_thread]
561 }
562
563 pub fn active_thread_ref(&self) -> &Thread<'tcx> {
565 &self.threads[self.active_thread]
566 }
567
568 pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> {
569 &self.threads[thread_id]
570 }
571
572 fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
581 trace!("detaching {:?}", id);
583
584 let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() {
585 self.threads[id].join_status == ThreadJoinStatus::Detached
587 } else {
588 self.threads[id].join_status != ThreadJoinStatus::Joinable
589 };
590 if is_ub {
591 throw_ub_format!("trying to detach thread that was already detached or joined");
592 }
593
594 self.threads[id].join_status = ThreadJoinStatus::Detached;
595 interp_ok(())
596 }
597
598 pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
600 self.threads[thread].thread_name = Some(new_thread_name);
601 }
602
603 pub fn get_thread_name(&self, thread: ThreadId) -> Option<&[u8]> {
605 self.threads[thread].thread_name()
606 }
607
608 pub fn get_thread_display_name(&self, thread: ThreadId) -> String {
609 self.threads[thread].thread_display_name(thread)
610 }
611
612 fn block_thread(
614 &mut self,
615 reason: BlockReason,
616 deadline: Option<Deadline>,
617 callback: DynUnblockCallback<'tcx>,
618 ) {
619 let state = &mut self.threads[self.active_thread].state;
620 assert!(state.is_enabled());
621 *state = ThreadState::Blocked { reason, deadline, callback }
622 }
623
624 pub fn fixed_scheduling(&self) -> bool {
625 self.fixed_scheduling
626 }
627}
628
629impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
631pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
632 #[inline]
633 fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
634 self.eval_context_ref().machine.threads.thread_id_try_from(id)
635 }
636
637 fn get_or_create_thread_local_alloc(
640 &mut self,
641 def_id: DefId,
642 ) -> InterpResult<'tcx, StrictPointer> {
643 let this = self.eval_context_mut();
644 let tcx = this.tcx;
645 if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) {
646 interp_ok(old_alloc)
649 } else {
650 if tcx.is_foreign_item(def_id) {
654 throw_unsup_format!("foreign thread-local statics are not supported");
655 }
656 let params = this.machine.get_default_alloc_params();
657 let alloc = this.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
658 let mut alloc = alloc.inner().adjust_from_tcx(
660 &this.tcx,
661 |bytes, align| {
662 interp_ok(MiriAllocBytes::from_bytes(
663 std::borrow::Cow::Borrowed(bytes),
664 align,
665 params,
666 ))
667 },
668 |ptr| this.global_root_pointer(ptr),
669 )?;
670 alloc.mutability = Mutability::Mut;
672 let ptr = this.insert_allocation(alloc, MiriMemoryKind::Tls.into())?;
674 this.machine.threads.set_thread_local_alloc(def_id, ptr);
675 interp_ok(ptr)
676 }
677 }
678
679 #[inline]
681 fn start_regular_thread(
682 &mut self,
683 thread: Option<MPlaceTy<'tcx>>,
684 start_routine: Pointer,
685 start_abi: ExternAbi,
686 func_arg: ImmTy<'tcx>,
687 ret_layout: TyAndLayout<'tcx>,
688 ) -> InterpResult<'tcx, ThreadId> {
689 let this = self.eval_context_mut();
690
691 let current_span = this.machine.current_user_relevant_span();
693 let new_thread_id = this.machine.threads.create_thread({
694 let mut state = tls::TlsDtorsState::default();
695 Box::new(move |m| state.on_stack_empty(m))
696 });
697 match &mut this.machine.data_race {
698 GlobalDataRaceHandler::None => {}
699 GlobalDataRaceHandler::Vclocks(data_race) =>
700 data_race.thread_created(&this.machine.threads, new_thread_id, current_span),
701 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
702 genmc_ctx.handle_thread_create(
703 &this.machine.threads,
704 start_routine,
705 &func_arg,
706 new_thread_id,
707 )?,
708 }
709 if let Some(thread_info_place) = thread {
712 this.write_scalar(
713 Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
714 &thread_info_place,
715 )?;
716 }
717
718 let old_thread_id = this.machine.threads.set_active_thread(new_thread_id);
721
722 if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity
725 && let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
726 {
727 thread_cpu_affinity.insert(new_thread_id, cpuset);
728 }
729
730 let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
732
733 let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
737
738 this.call_thread_root_function(
739 instance,
740 start_abi,
741 &[func_arg],
742 Some(&ret_place),
743 current_span,
744 )?;
745
746 this.machine.threads.set_active_thread(old_thread_id);
748
749 interp_ok(new_thread_id)
750 }
751
752 fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
757 let this = self.eval_context_mut();
758
759 let thread = this.active_thread_mut();
761 assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");
762 thread.state = ThreadState::Terminated;
763
764 let gone_thread = this.active_thread();
766 {
767 let mut free_tls_statics = Vec::new();
768 this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut alloc_id| {
769 if thread != gone_thread {
770 return true;
772 }
773 free_tls_statics.push(alloc_id);
776 false
777 });
778 for ptr in free_tls_statics {
780 match tls_alloc_action {
781 TlsAllocAction::Deallocate =>
782 this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?,
783 TlsAllocAction::Leak =>
784 if let Some(alloc) = ptr.provenance.get_alloc_id() {
785 trace!(
786 "Thread-local static leaked and stored as static root: {:?}",
787 alloc
788 );
789 this.machine.static_roots.push(alloc);
790 },
791 }
792 }
793 }
794
795 match &mut this.machine.data_race {
796 GlobalDataRaceHandler::None => {}
797 GlobalDataRaceHandler::Vclocks(data_race) =>
798 data_race.thread_terminated(&this.machine.threads),
799 GlobalDataRaceHandler::Genmc(genmc_ctx) => {
800 genmc_ctx.handle_thread_finish(&this.machine.threads)
803 }
804 }
805
806 let unblock_reason = BlockReason::Join(gone_thread);
808 let threads = &this.machine.threads.threads;
809 let joining_threads = threads
810 .iter_enumerated()
811 .filter(|(_, thread)| thread.state.is_blocked_on(unblock_reason))
812 .map(|(id, _)| id)
813 .collect::<Vec<_>>();
814 for thread in joining_threads {
815 this.unblock_thread(thread, unblock_reason)?;
816 }
817
818 interp_ok(())
819 }
820
821 #[inline]
824 fn block_thread(
825 &mut self,
826 reason: BlockReason,
827 deadline: Option<Deadline>,
828 callback: DynUnblockCallback<'tcx>,
829 ) {
830 let this = self.eval_context_mut();
831 if deadline.is_some() && this.machine.data_race.as_genmc_ref().is_some() {
832 panic!("Unimplemented: Timeouts not yet supported in GenMC mode.");
833 }
834 if matches!(deadline, Some(Deadline::RealTime(_))) && !this.machine.communicate() {
835 panic!("cannot have `RealTime` timeout with isolation");
836 }
837 this.machine.threads.block_thread(reason, deadline, callback);
838 }
839
840 fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) -> InterpResult<'tcx> {
843 let this = self.eval_context_mut();
844 let old_state =
845 mem::replace(&mut this.machine.threads.threads[thread].state, ThreadState::Enabled);
846 let callback = match old_state {
847 ThreadState::Blocked { reason: actual_reason, callback, .. } => {
848 assert_eq!(
849 reason, actual_reason,
850 "unblock_thread: thread was blocked for the wrong reason"
851 );
852 callback
853 }
854 _ => panic!("unblock_thread: thread was not blocked"),
855 };
856 let old_thread = this.machine.threads.set_active_thread(thread);
858 callback.call(this, UnblockKind::Ready)?;
859 this.machine.threads.set_active_thread(old_thread);
860 interp_ok(())
861 }
862
863 fn unblock_expired_deadlines(&mut self) -> InterpResult<'tcx, Option<Duration>> {
868 let this = self.eval_context_mut();
869 let communicate = this.machine.communicate();
870
871 let mut min_wait_time = Option::<Duration>::None;
872 let mut callbacks = Vec::new();
873
874 for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() {
875 match &thread.state {
876 ThreadState::Blocked { deadline: Some(deadline), .. } => {
877 let wait_time = match deadline {
878 Deadline::Monotonic(instant) =>
879 instant.duration_since(this.machine.monotonic_clock.now()),
880 Deadline::RealTime(time) => {
881 assert!(communicate, "cannot have `RealTime` timeout with isolation");
882 time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)
883 }
884 };
885
886 if wait_time.is_zero() {
887 let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
889 let ThreadState::Blocked { callback, .. } = old_state else {
890 unreachable!()
891 };
892 callbacks.push((id, callback));
894 } else {
895 min_wait_time = Some(wait_time.min(min_wait_time.unwrap_or(Duration::MAX)));
898 }
899 }
900 _ => {}
901 }
902 }
903
904 for (thread, callback) in callbacks {
905 let old_thread = this.machine.threads.set_active_thread(thread);
912 callback.call(this, UnblockKind::TimedOut)?;
913 this.machine.threads.set_active_thread(old_thread);
914 }
915
916 interp_ok(min_wait_time)
917 }
918
919 #[inline]
920 fn detach_thread(
921 &mut self,
922 thread_id: ThreadId,
923 allow_terminated_joined: bool,
924 ) -> InterpResult<'tcx> {
925 let this = self.eval_context_mut();
926 this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
927 }
928
929 fn join_thread(
933 &mut self,
934 joined_thread_id: ThreadId,
935 success_retval: Scalar,
936 return_dest: &MPlaceTy<'tcx>,
937 ) -> InterpResult<'tcx> {
938 let this = self.eval_context_mut();
939 let thread_mgr = &mut this.machine.threads;
940 if thread_mgr.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
941 throw_ub_format!("trying to join a detached thread");
943 }
944
945 fn after_join<'tcx>(
946 this: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
947 joined_thread_id: ThreadId,
948 success_retval: Scalar,
949 return_dest: &MPlaceTy<'tcx>,
950 ) -> InterpResult<'tcx> {
951 let threads = &this.machine.threads;
952 match &mut this.machine.data_race {
953 GlobalDataRaceHandler::None => {}
954 GlobalDataRaceHandler::Vclocks(data_race) =>
955 data_race.thread_joined(threads, joined_thread_id),
956 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
957 genmc_ctx.handle_thread_join(threads.active_thread, joined_thread_id)?,
958 }
959 this.write_scalar(success_retval, return_dest)?;
960 interp_ok(())
961 }
962
963 thread_mgr.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
966 if !thread_mgr.threads[joined_thread_id].state.is_terminated() {
967 trace!(
968 "{:?} blocked on {:?} when trying to join",
969 thread_mgr.active_thread, joined_thread_id
970 );
971 if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
972 genmc_ctx.handle_thread_join(thread_mgr.active_thread, joined_thread_id)?;
973 }
974
975 let dest = return_dest.clone();
978 thread_mgr.block_thread(
979 BlockReason::Join(joined_thread_id),
980 None,
981 callback!(
982 @capture<'tcx> {
983 joined_thread_id: ThreadId,
984 dest: MPlaceTy<'tcx>,
985 success_retval: Scalar,
986 }
987 |this, unblock: UnblockKind| {
988 assert_eq!(unblock, UnblockKind::Ready);
989 after_join(this, joined_thread_id, success_retval, &dest)
990 }
991 ),
992 );
993 } else {
994 after_join(this, joined_thread_id, success_retval, return_dest)?;
996 }
997 interp_ok(())
998 }
999
1000 fn join_thread_exclusive(
1005 &mut self,
1006 joined_thread_id: ThreadId,
1007 success_retval: Scalar,
1008 return_dest: &MPlaceTy<'tcx>,
1009 ) -> InterpResult<'tcx> {
1010 let this = self.eval_context_mut();
1011 let threads = &this.machine.threads.threads;
1012 if threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
1013 throw_ub_format!("trying to join an already joined thread");
1014 }
1015
1016 if joined_thread_id == this.machine.threads.active_thread {
1017 throw_ub_format!("trying to join itself");
1018 }
1019
1020 assert!(
1022 threads
1023 .iter()
1024 .all(|thread| { !thread.state.is_blocked_on(BlockReason::Join(joined_thread_id)) }),
1025 "this thread already has threads waiting for its termination"
1026 );
1027
1028 this.join_thread(joined_thread_id, success_retval, return_dest)
1029 }
1030
1031 #[inline]
1032 fn active_thread(&self) -> ThreadId {
1033 let this = self.eval_context_ref();
1034 this.machine.threads.active_thread()
1035 }
1036
1037 #[inline]
1038 fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
1039 let this = self.eval_context_mut();
1040 this.machine.threads.active_thread_mut()
1041 }
1042
1043 #[inline]
1044 fn active_thread_ref(&self) -> &Thread<'tcx> {
1045 let this = self.eval_context_ref();
1046 this.machine.threads.active_thread_ref()
1047 }
1048
1049 #[inline]
1050 fn get_total_thread_count(&self) -> usize {
1051 let this = self.eval_context_ref();
1052 this.machine.threads.get_total_thread_count()
1053 }
1054
1055 #[inline]
1056 fn have_all_terminated(&self) -> bool {
1057 let this = self.eval_context_ref();
1058 this.machine.threads.have_all_terminated()
1059 }
1060
1061 #[inline]
1062 fn enable_thread(&mut self, thread_id: ThreadId) {
1063 let this = self.eval_context_mut();
1064 this.machine.threads.enable_thread(thread_id);
1065 }
1066
1067 #[inline]
1068 fn active_thread_stack<'a>(&'a self) -> &'a [Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
1069 let this = self.eval_context_ref();
1070 this.machine.threads.active_thread_stack()
1071 }
1072
1073 #[inline]
1074 fn active_thread_stack_mut<'a>(
1075 &'a mut self,
1076 ) -> &'a mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
1077 let this = self.eval_context_mut();
1078 this.machine.threads.active_thread_stack_mut()
1079 }
1080
1081 #[inline]
1083 fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
1084 self.eval_context_mut().machine.threads.set_thread_name(thread, new_thread_name);
1085 }
1086
1087 #[inline]
1088 fn get_thread_name<'c>(&'c self, thread: ThreadId) -> Option<&'c [u8]>
1089 where
1090 'tcx: 'c,
1091 {
1092 self.eval_context_ref().machine.threads.get_thread_name(thread)
1093 }
1094
1095 #[inline]
1096 fn yield_active_thread(&mut self) {
1097 self.eval_context_mut().machine.threads.yield_active_thread = true;
1101 }
1102
1103 #[inline]
1104 fn maybe_preempt_active_thread(&mut self) {
1105 let this = self.eval_context_mut();
1106 if !this.machine.threads.fixed_scheduling
1107 && this.machine.rng.get_mut().random_bool(this.machine.preemption_rate)
1108 {
1109 this.yield_active_thread();
1110 }
1111 }
1112}