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 Frame {
349 locals,
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() {
360 None => {}
361 Some(Either::Left((ptr, meta))) => {
362 ptr.visit_provenance(visit);
363 meta.visit_provenance(visit);
364 }
365 Some(Either::Right(imm)) => {
366 imm.visit_provenance(visit);
367 }
368 }
369 }
370
371 extra.visit_provenance(visit);
372 }
373}
374
375#[derive(Debug, Copy, Clone)]
377pub enum ThreadLookupError {
378 InvalidId,
380 Terminated(ThreadId),
382}
383
384#[derive(Debug)]
386pub struct ThreadManager<'tcx> {
387 active_thread: ThreadId,
389 threads: IndexVec<ThreadId, Thread<'tcx>>,
393 thread_local_allocs: FxHashMap<(DefId, ThreadId), StrictPointer>,
395 pub(super) yield_active_thread: bool,
398 fixed_scheduling: bool,
400}
401
402impl VisitProvenance for ThreadManager<'_> {
403 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
404 let ThreadManager {
405 threads,
406 thread_local_allocs,
407 active_thread: _,
408 yield_active_thread: _,
409 fixed_scheduling: _,
410 } = self;
411
412 for thread in threads {
413 thread.visit_provenance(visit);
414 }
415 for ptr in thread_local_allocs.values() {
416 ptr.visit_provenance(visit);
417 }
418 }
419}
420
421impl<'tcx> ThreadManager<'tcx> {
422 pub(crate) fn new(config: &MiriConfig) -> Self {
423 let mut threads = IndexVec::new();
424 threads.push(Thread::new(Some("main"), None));
426 Self {
427 active_thread: ThreadId::MAIN_THREAD,
428 threads,
429 thread_local_allocs: Default::default(),
430 yield_active_thread: false,
431 fixed_scheduling: config.fixed_scheduling,
432 }
433 }
434
435 pub(crate) fn init(
436 ecx: &mut MiriInterpCx<'tcx>,
437 on_main_stack_empty: StackEmptyCallback<'tcx>,
438 ) {
439 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty =
440 Some(on_main_stack_empty);
441 if ecx.tcx.sess.target.os != Os::Windows {
442 ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
444 ThreadJoinStatus::Detached;
445 }
446 }
447
448 pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
452 if let Ok(id) = id.try_into()
453 && usize::try_from(id).is_ok_and(|id| id < self.threads.len())
454 {
455 let thread_id = ThreadId(id);
456 if self.threads[thread_id].state.is_terminated() {
457 Err(ThreadLookupError::Terminated(thread_id))
458 } else {
459 Ok(thread_id)
460 }
461 } else {
462 Err(ThreadLookupError::InvalidId)
463 }
464 }
465
466 fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<StrictPointer> {
469 self.thread_local_allocs.get(&(def_id, self.active_thread)).cloned()
470 }
471
472 fn set_thread_local_alloc(&mut self, def_id: DefId, ptr: StrictPointer) {
477 self.thread_local_allocs.try_insert((def_id, self.active_thread), ptr).unwrap();
478 }
479
480 pub fn active_thread_stack(&self) -> &[Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
482 &self.threads[self.active_thread].stack
483 }
484
485 pub fn active_thread_stack_mut(
487 &mut self,
488 ) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
489 &mut self.threads[self.active_thread].stack
490 }
491
492 pub(super) fn all_threads(&self) -> impl Iterator<Item = (ThreadId, &Thread<'tcx>)> {
493 self.threads.iter_enumerated()
494 }
495
496 pub fn all_blocked_stacks(
497 &self,
498 ) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
499 self.threads
500 .iter_enumerated()
501 .filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. }))
502 .map(|(id, t)| (id, &t.stack[..]))
503 }
504
505 fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
507 let new_thread_id = ThreadId::new(self.threads.len());
508 self.threads.push(Thread::new(None, Some(on_stack_empty)));
509 new_thread_id
510 }
511
512 pub(super) fn set_active_thread(&mut self, id: ThreadId) -> ThreadId {
514 assert!(id.index() < self.threads.len());
515 info!(
516 "---------- Now executing on thread `{}` (previous: `{}`) ----------------------------------------",
517 self.get_thread_display_name(id),
518 self.get_thread_display_name(self.active_thread)
519 );
520 std::mem::replace(&mut self.active_thread, id)
521 }
522
523 pub fn active_thread(&self) -> ThreadId {
525 self.active_thread
526 }
527
528 pub fn get_total_thread_count(&self) -> usize {
530 self.threads.len()
531 }
532
533 pub fn get_live_thread_count(&self) -> usize {
536 self.threads.iter().filter(|t| !t.state.is_terminated()).count()
537 }
538
539 fn has_terminated(&self, thread_id: ThreadId) -> bool {
541 self.threads[thread_id].state.is_terminated()
542 }
543
544 fn have_all_terminated(&self) -> bool {
546 self.threads.iter().all(|thread| thread.state.is_terminated())
547 }
548
549 fn enable_thread(&mut self, thread_id: ThreadId) {
551 assert!(self.has_terminated(thread_id));
552 self.threads[thread_id].state = ThreadState::Enabled;
553 }
554
555 pub fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
557 &mut self.threads[self.active_thread]
558 }
559
560 pub fn active_thread_ref(&self) -> &Thread<'tcx> {
562 &self.threads[self.active_thread]
563 }
564
565 pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> {
566 &self.threads[thread_id]
567 }
568
569 fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
578 trace!("detaching {:?}", id);
580
581 let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() {
582 self.threads[id].join_status == ThreadJoinStatus::Detached
584 } else {
585 self.threads[id].join_status != ThreadJoinStatus::Joinable
586 };
587 if is_ub {
588 throw_ub_format!("trying to detach thread that was already detached or joined");
589 }
590
591 self.threads[id].join_status = ThreadJoinStatus::Detached;
592 interp_ok(())
593 }
594
595 pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
597 self.threads[thread].thread_name = Some(new_thread_name);
598 }
599
600 pub fn get_thread_name(&self, thread: ThreadId) -> Option<&[u8]> {
602 self.threads[thread].thread_name()
603 }
604
605 pub fn get_thread_display_name(&self, thread: ThreadId) -> String {
606 self.threads[thread].thread_display_name(thread)
607 }
608
609 fn block_thread(
611 &mut self,
612 reason: BlockReason,
613 deadline: Option<Deadline>,
614 callback: DynUnblockCallback<'tcx>,
615 ) {
616 let state = &mut self.threads[self.active_thread].state;
617 assert!(state.is_enabled());
618 *state = ThreadState::Blocked { reason, deadline, callback }
619 }
620
621 pub fn fixed_scheduling(&self) -> bool {
622 self.fixed_scheduling
623 }
624}
625
626impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
628pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
629 #[inline]
630 fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
631 self.eval_context_ref().machine.threads.thread_id_try_from(id)
632 }
633
634 fn get_or_create_thread_local_alloc(
637 &mut self,
638 def_id: DefId,
639 ) -> InterpResult<'tcx, StrictPointer> {
640 let this = self.eval_context_mut();
641 let tcx = this.tcx;
642 if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) {
643 interp_ok(old_alloc)
646 } else {
647 if tcx.is_foreign_item(def_id) {
651 throw_unsup_format!("foreign thread-local statics are not supported");
652 }
653 let params = this.machine.get_default_alloc_params();
654 let alloc = this.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
655 let mut alloc = alloc.inner().adjust_from_tcx(
657 &this.tcx,
658 |bytes, align| {
659 interp_ok(MiriAllocBytes::from_bytes(
660 std::borrow::Cow::Borrowed(bytes),
661 align,
662 params,
663 ))
664 },
665 |ptr| this.global_root_pointer(ptr),
666 )?;
667 alloc.mutability = Mutability::Mut;
669 let ptr = this.insert_allocation(alloc, MiriMemoryKind::Tls.into())?;
671 this.machine.threads.set_thread_local_alloc(def_id, ptr);
672 interp_ok(ptr)
673 }
674 }
675
676 #[inline]
678 fn start_regular_thread(
679 &mut self,
680 thread: Option<MPlaceTy<'tcx>>,
681 start_routine: Pointer,
682 start_abi: ExternAbi,
683 func_arg: ImmTy<'tcx>,
684 ret_layout: TyAndLayout<'tcx>,
685 ) -> InterpResult<'tcx, ThreadId> {
686 let this = self.eval_context_mut();
687
688 let current_span = this.machine.current_user_relevant_span();
690 let new_thread_id = this.machine.threads.create_thread({
691 let mut state = tls::TlsDtorsState::default();
692 Box::new(move |m| state.on_stack_empty(m))
693 });
694 match &mut this.machine.data_race {
695 GlobalDataRaceHandler::None => {}
696 GlobalDataRaceHandler::Vclocks(data_race) =>
697 data_race.thread_created(&this.machine.threads, new_thread_id, current_span),
698 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
699 genmc_ctx.handle_thread_create(
700 &this.machine.threads,
701 start_routine,
702 &func_arg,
703 new_thread_id,
704 )?,
705 }
706 if let Some(thread_info_place) = thread {
709 this.write_scalar(
710 Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
711 &thread_info_place,
712 )?;
713 }
714
715 let old_thread_id = this.machine.threads.set_active_thread(new_thread_id);
718
719 if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity
722 && let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
723 {
724 thread_cpu_affinity.insert(new_thread_id, cpuset);
725 }
726
727 let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
729
730 let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
734
735 this.call_thread_root_function(
736 instance,
737 start_abi,
738 &[func_arg],
739 Some(&ret_place),
740 current_span,
741 )?;
742
743 this.machine.threads.set_active_thread(old_thread_id);
745
746 interp_ok(new_thread_id)
747 }
748
749 fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
754 let this = self.eval_context_mut();
755
756 let thread = this.active_thread_mut();
758 assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");
759 thread.state = ThreadState::Terminated;
760
761 let gone_thread = this.active_thread();
763 {
764 let mut free_tls_statics = Vec::new();
765 this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut alloc_id| {
766 if thread != gone_thread {
767 return true;
769 }
770 free_tls_statics.push(alloc_id);
773 false
774 });
775 for ptr in free_tls_statics {
777 match tls_alloc_action {
778 TlsAllocAction::Deallocate =>
779 this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?,
780 TlsAllocAction::Leak =>
781 if let Some(alloc) = ptr.provenance.get_alloc_id() {
782 trace!(
783 "Thread-local static leaked and stored as static root: {:?}",
784 alloc
785 );
786 this.machine.static_roots.push(alloc);
787 },
788 }
789 }
790 }
791
792 match &mut this.machine.data_race {
793 GlobalDataRaceHandler::None => {}
794 GlobalDataRaceHandler::Vclocks(data_race) =>
795 data_race.thread_terminated(&this.machine.threads),
796 GlobalDataRaceHandler::Genmc(genmc_ctx) => {
797 genmc_ctx.handle_thread_finish(&this.machine.threads)
800 }
801 }
802
803 let unblock_reason = BlockReason::Join(gone_thread);
805 let threads = &this.machine.threads.threads;
806 let joining_threads = threads
807 .iter_enumerated()
808 .filter(|(_, thread)| thread.state.is_blocked_on(unblock_reason))
809 .map(|(id, _)| id)
810 .collect::<Vec<_>>();
811 for thread in joining_threads {
812 this.unblock_thread(thread, unblock_reason)?;
813 }
814
815 interp_ok(())
816 }
817
818 #[inline]
821 fn block_thread(
822 &mut self,
823 reason: BlockReason,
824 deadline: Option<Deadline>,
825 callback: DynUnblockCallback<'tcx>,
826 ) {
827 let this = self.eval_context_mut();
828 if deadline.is_some() && this.machine.data_race.as_genmc_ref().is_some() {
829 panic!("Unimplemented: Timeouts not yet supported in GenMC mode.");
830 }
831 if matches!(deadline, Some(Deadline::RealTime(_))) && !this.machine.communicate() {
832 panic!("cannot have `RealTime` timeout with isolation");
833 }
834 this.machine.threads.block_thread(reason, deadline, callback);
835 }
836
837 fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) -> InterpResult<'tcx> {
840 let this = self.eval_context_mut();
841 let old_state =
842 mem::replace(&mut this.machine.threads.threads[thread].state, ThreadState::Enabled);
843 let callback = match old_state {
844 ThreadState::Blocked { reason: actual_reason, callback, .. } => {
845 assert_eq!(
846 reason, actual_reason,
847 "unblock_thread: thread was blocked for the wrong reason"
848 );
849 callback
850 }
851 _ => panic!("unblock_thread: thread was not blocked"),
852 };
853 let old_thread = this.machine.threads.set_active_thread(thread);
855 callback.call(this, UnblockKind::Ready)?;
856 this.machine.threads.set_active_thread(old_thread);
857 interp_ok(())
858 }
859
860 fn unblock_expired_deadlines(&mut self) -> InterpResult<'tcx, Option<Duration>> {
865 let this = self.eval_context_mut();
866 let communicate = this.machine.communicate();
867
868 let mut min_wait_time = Option::<Duration>::None;
869 let mut callbacks = Vec::new();
870
871 for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() {
872 match &thread.state {
873 ThreadState::Blocked { deadline: Some(deadline), .. } => {
874 let wait_time = match deadline {
875 Deadline::Monotonic(instant) =>
876 instant.duration_since(this.machine.monotonic_clock.now()),
877 Deadline::RealTime(time) => {
878 assert!(communicate, "cannot have `RealTime` timeout with isolation");
879 time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)
880 }
881 };
882
883 if wait_time.is_zero() {
884 let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
886 let ThreadState::Blocked { callback, .. } = old_state else {
887 unreachable!()
888 };
889 callbacks.push((id, callback));
891 } else {
892 min_wait_time = Some(wait_time.min(min_wait_time.unwrap_or(Duration::MAX)));
895 }
896 }
897 _ => {}
898 }
899 }
900
901 for (thread, callback) in callbacks {
902 let old_thread = this.machine.threads.set_active_thread(thread);
909 callback.call(this, UnblockKind::TimedOut)?;
910 this.machine.threads.set_active_thread(old_thread);
911 }
912
913 interp_ok(min_wait_time)
914 }
915
916 #[inline]
917 fn detach_thread(
918 &mut self,
919 thread_id: ThreadId,
920 allow_terminated_joined: bool,
921 ) -> InterpResult<'tcx> {
922 let this = self.eval_context_mut();
923 this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
924 }
925
926 fn join_thread(
930 &mut self,
931 joined_thread_id: ThreadId,
932 success_retval: Scalar,
933 return_dest: &MPlaceTy<'tcx>,
934 ) -> InterpResult<'tcx> {
935 let this = self.eval_context_mut();
936 let thread_mgr = &mut this.machine.threads;
937 if thread_mgr.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
938 throw_ub_format!("trying to join a detached thread");
940 }
941
942 fn after_join<'tcx>(
943 this: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
944 joined_thread_id: ThreadId,
945 success_retval: Scalar,
946 return_dest: &MPlaceTy<'tcx>,
947 ) -> InterpResult<'tcx> {
948 let threads = &this.machine.threads;
949 match &mut this.machine.data_race {
950 GlobalDataRaceHandler::None => {}
951 GlobalDataRaceHandler::Vclocks(data_race) =>
952 data_race.thread_joined(threads, joined_thread_id),
953 GlobalDataRaceHandler::Genmc(genmc_ctx) =>
954 genmc_ctx.handle_thread_join(threads.active_thread, joined_thread_id)?,
955 }
956 this.write_scalar(success_retval, return_dest)?;
957 interp_ok(())
958 }
959
960 thread_mgr.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
963 if !thread_mgr.threads[joined_thread_id].state.is_terminated() {
964 trace!(
965 "{:?} blocked on {:?} when trying to join",
966 thread_mgr.active_thread, joined_thread_id
967 );
968 if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
969 genmc_ctx.handle_thread_join(thread_mgr.active_thread, joined_thread_id)?;
970 }
971
972 let dest = return_dest.clone();
975 thread_mgr.block_thread(
976 BlockReason::Join(joined_thread_id),
977 None,
978 callback!(
979 @capture<'tcx> {
980 joined_thread_id: ThreadId,
981 dest: MPlaceTy<'tcx>,
982 success_retval: Scalar,
983 }
984 |this, unblock: UnblockKind| {
985 assert_eq!(unblock, UnblockKind::Ready);
986 after_join(this, joined_thread_id, success_retval, &dest)
987 }
988 ),
989 );
990 } else {
991 after_join(this, joined_thread_id, success_retval, return_dest)?;
993 }
994 interp_ok(())
995 }
996
997 fn join_thread_exclusive(
1002 &mut self,
1003 joined_thread_id: ThreadId,
1004 success_retval: Scalar,
1005 return_dest: &MPlaceTy<'tcx>,
1006 ) -> InterpResult<'tcx> {
1007 let this = self.eval_context_mut();
1008 let threads = &this.machine.threads.threads;
1009 if threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
1010 throw_ub_format!("trying to join an already joined thread");
1011 }
1012
1013 if joined_thread_id == this.machine.threads.active_thread {
1014 throw_ub_format!("trying to join itself");
1015 }
1016
1017 assert!(
1019 threads
1020 .iter()
1021 .all(|thread| { !thread.state.is_blocked_on(BlockReason::Join(joined_thread_id)) }),
1022 "this thread already has threads waiting for its termination"
1023 );
1024
1025 this.join_thread(joined_thread_id, success_retval, return_dest)
1026 }
1027
1028 #[inline]
1029 fn active_thread(&self) -> ThreadId {
1030 let this = self.eval_context_ref();
1031 this.machine.threads.active_thread()
1032 }
1033
1034 #[inline]
1035 fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
1036 let this = self.eval_context_mut();
1037 this.machine.threads.active_thread_mut()
1038 }
1039
1040 #[inline]
1041 fn active_thread_ref(&self) -> &Thread<'tcx> {
1042 let this = self.eval_context_ref();
1043 this.machine.threads.active_thread_ref()
1044 }
1045
1046 #[inline]
1047 fn get_total_thread_count(&self) -> usize {
1048 let this = self.eval_context_ref();
1049 this.machine.threads.get_total_thread_count()
1050 }
1051
1052 #[inline]
1053 fn have_all_terminated(&self) -> bool {
1054 let this = self.eval_context_ref();
1055 this.machine.threads.have_all_terminated()
1056 }
1057
1058 #[inline]
1059 fn enable_thread(&mut self, thread_id: ThreadId) {
1060 let this = self.eval_context_mut();
1061 this.machine.threads.enable_thread(thread_id);
1062 }
1063
1064 #[inline]
1065 fn active_thread_stack<'a>(&'a self) -> &'a [Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
1066 let this = self.eval_context_ref();
1067 this.machine.threads.active_thread_stack()
1068 }
1069
1070 #[inline]
1071 fn active_thread_stack_mut<'a>(
1072 &'a mut self,
1073 ) -> &'a mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
1074 let this = self.eval_context_mut();
1075 this.machine.threads.active_thread_stack_mut()
1076 }
1077
1078 #[inline]
1080 fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
1081 self.eval_context_mut().machine.threads.set_thread_name(thread, new_thread_name);
1082 }
1083
1084 #[inline]
1085 fn get_thread_name<'c>(&'c self, thread: ThreadId) -> Option<&'c [u8]>
1086 where
1087 'tcx: 'c,
1088 {
1089 self.eval_context_ref().machine.threads.get_thread_name(thread)
1090 }
1091
1092 #[inline]
1093 fn yield_active_thread(&mut self) {
1094 self.eval_context_mut().machine.threads.yield_active_thread = true;
1098 }
1099
1100 #[inline]
1101 fn maybe_preempt_active_thread(&mut self) {
1102 let this = self.eval_context_mut();
1103 if !this.machine.threads.fixed_scheduling
1104 && this.machine.rng.get_mut().random_bool(this.machine.preemption_rate)
1105 {
1106 this.yield_active_thread();
1107 }
1108 }
1109}