Skip to main content

miri/concurrency/
thread.rs

1//! Implements threads.
2
3use 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/// What to do with TLS allocations from terminated threads
23#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum TlsAllocAction {
25    /// Deallocate backing memory of thread-local statics as usual
26    Deallocate,
27    /// Skip deallocating backing memory of thread-local statics and consider all memory reachable
28    /// from them as "allowed to leak" (like global `static`s).
29    Leak,
30}
31
32/// The argument type for the "unblock" callback, indicating why the thread got unblocked.
33#[derive(Clone, Copy, Debug, PartialEq)]
34pub enum UnblockKind {
35    /// Operation completed successfully, thread continues normal execution.
36    Ready,
37    /// The operation did not complete within its specified duration.
38    TimedOut,
39}
40
41/// Type alias for unblock callbacks, i.e. machine callbacks invoked when
42/// a thread gets unblocked.
43pub type DynUnblockCallback<'tcx> = DynMachineCallback<'tcx, UnblockKind>;
44
45/// A thread identifier.
46#[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    /// Create a new thread id from a `u32` without checking if this thread exists.
55    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/// Keeps track of what the thread is blocked on.
79#[derive(Debug, Copy, Clone, PartialEq, Eq)]
80pub enum BlockReason {
81    /// The thread tried to join the specified thread and is blocked until that
82    /// thread terminates.
83    Join(ThreadId),
84    /// Waiting for time to pass.
85    Sleep,
86    /// Blocked on a mutex.
87    Mutex,
88    /// Blocked on a condition variable.
89    Condvar,
90    /// Blocked on a reader-writer lock.
91    RwLock,
92    /// Blocked on a semaphore.
93    Semaphore,
94    /// Blocked on a Futex variable.
95    Futex,
96    /// Blocked on an InitOnce.
97    InitOnce,
98    /// Blocked until a file description readiness is satisfied (e.g. epoll).
99    Readiness,
100    /// Blocked on eventfd.
101    Eventfd,
102    /// Blocked on virtual socket.
103    VirtualSocket,
104    /// Blocked on an IO operation.
105    IO,
106    /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only).
107    /// Will be implicitly unblocked when GenMC schedules this thread again.
108    Genmc,
109}
110
111/// The state of a thread.
112enum ThreadState<'tcx> {
113    /// The thread is enabled and can be executed.
114    Enabled,
115    /// The thread is blocked on something.
116    Blocked { reason: BlockReason, deadline: Option<Deadline>, callback: DynUnblockCallback<'tcx> },
117    /// The thread has terminated its execution. We do not delete terminated
118    /// threads (FIXME: why?).
119    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/// The join status of a thread.
151#[derive(Debug, Copy, Clone, PartialEq, Eq)]
152enum ThreadJoinStatus {
153    /// The thread can be joined.
154    Joinable,
155    /// A thread is detached if its join handle was destroyed and no other
156    /// thread can join it.
157    Detached,
158    /// The thread was already joined by some thread and cannot be joined again.
159    Joined,
160}
161
162/// A thread.
163pub struct Thread<'tcx> {
164    state: ThreadState<'tcx>,
165
166    /// Name of the thread.
167    thread_name: Option<Vec<u8>>,
168
169    /// The virtual call stack.
170    stack: Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
171
172    /// A span that explains where the thread (or more specifically, its current root
173    /// frame) "comes from".
174    pub(crate) origin_span: Span,
175
176    /// The function to call when the stack ran empty, to figure out what to do next.
177    /// Conceptually, this is the interpreter implementation of the things that happen 'after' the
178    /// Rust language entry point for this thread returns (usually implemented by the C or OS runtime).
179    /// (`None` is an error, it means the callback has not been set up yet or is actively running.)
180    pub(crate) on_stack_empty: Option<StackEmptyCallback<'tcx>>,
181
182    /// The index of the topmost user-relevant frame in `stack`. This field must contain
183    /// the value produced by `get_top_user_relevant_frame`.
184    /// This field is a cache to reduce how often we call that method. The cache is manually
185    /// maintained inside `MiriMachine::after_stack_push` and `MiriMachine::after_stack_pop`.
186    top_user_relevant_frame: Option<usize>,
187
188    /// The join status.
189    join_status: ThreadJoinStatus,
190
191    /// Stack of active unwind payloads for the current thread. Used for storing
192    /// the argument of the call to `miri_start_unwind` (the payload) when unwinding.
193    /// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
194    ///
195    /// In real unwinding, the payload gets passed as an argument to the landing pad,
196    /// which then forwards it to 'Resume'. However this argument is implicit in MIR,
197    /// so we have to store it out-of-band. When there are multiple active unwinds,
198    /// the innermost one is always caught first, so we can store them as a stack.
199    pub(crate) unwind_payloads: Vec<ImmTy<'tcx>>,
200
201    /// Last OS error location in memory. It is a 32-bit integer.
202    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    /// Get the name of the current thread if it was set.
210    fn thread_name(&self) -> Option<&[u8]> {
211        self.thread_name.as_deref()
212    }
213
214    /// Return whether this thread is enabled or not.
215    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    /// Get the name of the current thread for display purposes; will include thread ID if not set.
228    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    /// Return the top user-relevant frame, if there is one. `skip` indicates how many top frames
237    /// should be skipped.
238    /// Note that the choice to return `None` here when there is no user-relevant frame is part of
239    /// justifying the optimization that only pushes of user-relevant frames require updating the
240    /// `top_user_relevant_frame` field.
241    fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
242        // We are search for the frame with maximum relevance.
243        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                // We can short-circuit this search.
248                return Some(idx);
249            }
250            if best.is_none_or(|(_best_idx, best_relevance)| best_relevance < relevance) {
251                // The previous best frame has strictly worse relevance, so despite us being lower
252                // in the stack, we win.
253                best = Some((idx, relevance));
254            }
255        }
256        best.map(|(idx, _relevance)| idx)
257    }
258
259    /// Re-compute the top user-relevant frame from scratch. `skip` indicates how many top frames
260    /// should be skipped.
261    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    /// Set the top user-relevant frame to the given value. Must be equal to what
266    /// `get_top_user_relevant_frame` would return!
267    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    /// Returns the topmost frame that is considered user-relevant, or the
273    /// top of the stack if there is no such frame, or `None` if the stack is empty.
274    pub fn top_user_relevant_frame(&self) -> Option<usize> {
275        // This can be called upon creation of an allocation. We create allocations while setting up
276        // parts of the Rust runtime when we do not have any stack frames yet, so we need to handle
277        // empty stacks.
278        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: _, // we assume the closure captures no GC-relevant state
335        } = 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            // There are some private fields we cannot access; they contain no tags.
354            ..
355        } = self;
356
357        // Return place.
358        return_place.visit_provenance(visit);
359        // Locals.
360        for local in locals.iter() {
361            // We only need the provenance so it's good for this to not be a real read.
362            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/// An error signaling that the requested thread doesn't exist or has terminated.
379#[derive(Debug, Copy, Clone)]
380pub enum ThreadLookupError {
381    /// No thread with this ID exists.
382    InvalidId,
383    /// The thread exists but has already terminated.
384    Terminated(ThreadId),
385}
386
387/// A set of threads.
388#[derive(Debug)]
389pub struct ThreadManager<'tcx> {
390    /// Identifier of the currently active thread.
391    active_thread: ThreadId,
392    /// Threads used in the program.
393    ///
394    /// Note that this vector also contains terminated threads.
395    threads: IndexVec<ThreadId, Thread<'tcx>>,
396    /// A mapping from a thread-local static to the thread specific allocation.
397    thread_local_allocs: FxHashMap<(DefId, ThreadId), StrictPointer>,
398    /// A flag that indicates that we should change the active thread.
399    /// Completely ignored in GenMC mode.
400    pub(super) yield_active_thread: bool,
401    /// A flag that indicates that we should do round robin scheduling of threads else randomized scheduling is used.
402    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        // Create the main thread and add it to the list of threads.
428        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            // The main thread can *not* be joined on except on windows.
446            ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
447                ThreadJoinStatus::Detached;
448        }
449    }
450
451    /// Returns the `ThreadId` for the given raw thread id.
452    /// Returns `Err(ThreadNotFound::InvalidId)` if the id is out of range, or
453    /// `Err(ThreadNotFound::Terminated(id))` if the thread exists but has terminated.
454    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    /// Check if we have an allocation for the given thread local static for the
470    /// active thread.
471    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    /// Set the pointer for the allocation of the given thread local
476    /// static for the active thread.
477    ///
478    /// Panics if a thread local is initialized twice for the same thread.
479    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    /// Borrow the stack of the active thread.
484    pub fn active_thread_stack(&self) -> &[Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
485        &self.threads[self.active_thread].stack
486    }
487
488    /// Mutably borrow the stack of the active thread.
489    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    /// Create a new thread and returns its id.
509    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    /// Set an active thread and return the id of the thread that was active before.
516    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    /// Get the id of the currently active thread.
527    pub fn active_thread(&self) -> ThreadId {
528        self.active_thread
529    }
530
531    /// Get the total number of threads that were ever spawn by this program.
532    pub fn get_total_thread_count(&self) -> usize {
533        self.threads.len()
534    }
535
536    /// Get the total of threads that are currently live, i.e., not yet terminated.
537    /// (They might be blocked.)
538    pub fn get_live_thread_count(&self) -> usize {
539        self.threads.iter().filter(|t| !t.state.is_terminated()).count()
540    }
541
542    /// Has the given thread terminated?
543    fn has_terminated(&self, thread_id: ThreadId) -> bool {
544        self.threads[thread_id].state.is_terminated()
545    }
546
547    /// Have all threads terminated?
548    fn have_all_terminated(&self) -> bool {
549        self.threads.iter().all(|thread| thread.state.is_terminated())
550    }
551
552    /// Enable the thread for execution. The thread must be terminated.
553    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    /// Get a mutable borrow of the currently active thread.
559    pub fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
560        &mut self.threads[self.active_thread]
561    }
562
563    /// Get a shared borrow of the currently active thread.
564    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    /// Mark the thread as detached, which means that no other thread will try
573    /// to join it and the thread is responsible for cleaning up.
574    ///
575    /// `allow_terminated_joined` allows detaching joined threads that have already terminated.
576    /// This matches Windows's behavior for `CloseHandle`.
577    ///
578    /// See <https://docs.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers>:
579    /// > The handle is valid until closed, even after the thread it represents has been terminated.
580    fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
581        // NOTE: In GenMC mode, we treat detached threads like regular threads that are never joined, so there is no special handling required here.
582        trace!("detaching {:?}", id);
583
584        let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() {
585            // "Detached" in particular means "not yet joined". Redundant detaching is still UB.
586            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    /// Set the name of the given thread.
599    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    /// Get the name of the given thread.
604    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    /// Put the thread into the blocked state.
613    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
629// Public interface to thread management.
630impl<'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    /// Get a thread-specific allocation id for the given thread-local static.
638    /// If needed, allocate a new one.
639    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            // We already have a thread-specific allocation id for this
647            // thread-local static.
648            interp_ok(old_alloc)
649        } else {
650            // We need to allocate a thread-specific allocation id for this
651            // thread-local static.
652            // First, we compute the initial value for this static.
653            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            // We make a full copy of this allocation.
659            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            // This allocation will be deallocated when the thread dies, so it is not in read-only memory.
671            alloc.mutability = Mutability::Mut;
672            // Create a fresh allocation with this content.
673            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    /// Start a regular (non-main) thread.
680    #[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        // Create the new thread
692        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        // Write the current thread-id, switch to the next thread later
710        // to treat this write operation as occurring on the current thread.
711        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        // Finally switch to new thread so that we can push the first stackframe.
719        // After this all accesses will be treated as occurring in the new thread.
720        let old_thread_id = this.machine.threads.set_active_thread(new_thread_id);
721
722        // The child inherits its parent's cpu affinity.
723        // Skips this if `machine.thread_cpu_affinity` is not initialized.
724        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        // Perform the function pointer load in the new thread frame.
731        let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
732
733        // Note: the returned value is currently ignored (see the FIXME in
734        // pthread_join in shims/unix/thread.rs) because the Rust standard library does not use
735        // it.
736        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        // Restore the old active thread frame.
747        this.machine.threads.set_active_thread(old_thread_id);
748
749        interp_ok(new_thread_id)
750    }
751
752    /// Handles thread termination of the active thread: wakes up threads joining on this one,
753    /// and deals with the thread's thread-local statics according to `tls_alloc_action`.
754    ///
755    /// This is called by the eval loop when a thread's on_stack_empty returns `Ready`.
756    fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
757        let this = self.eval_context_mut();
758
759        // Mark thread as terminated.
760        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        // Deallocate TLS.
765        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                    // A different thread, keep this static around.
771                    return true;
772                }
773                // Delete this static from the map and from memory.
774                // We cannot free directly here as we cannot use `?` in this context.
775                free_tls_statics.push(alloc_id);
776                false
777            });
778            // Now free the TLS statics.
779            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                // Inform GenMC that the thread finished.
801                // This needs to happen once all accesses to the thread are done, including freeing any TLS statics.
802                genmc_ctx.handle_thread_finish(&this.machine.threads)
803            }
804        }
805
806        // Unblock joining threads.
807        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    /// Block the current thread, with an optional timeout.
822    /// The callback will be invoked when the thread gets unblocked.
823    #[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    /// Put the blocked thread into the enabled state.
841    /// Sanity-checks that the thread previously was blocked for the right reason.
842    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        // The callback must be executed in the previously blocked thread.
857        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    /// Find all threads with expired timeouts, unblock them and execute their timeout callbacks.
864    ///
865    /// This method returns the minimum duration until the next thread deadline.
866    /// If all ready threads have no deadline set, [`None`] is returned.
867    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                        // The timeout expired for this thread.
888                        let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
889                        let ThreadState::Blocked { callback, .. } = old_state else {
890                            unreachable!()
891                        };
892                        // Add callback to list to be run after this loop because of borrow-checking.
893                        callbacks.push((id, callback));
894                    } else {
895                        // Update `min_wait_time` to contain the smallest duration until
896                        // the next timeout expires.
897                        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            // This back-and-forth with `set_active_thread` is here because of two
906            // design decisions:
907            // 1. Make the caller and not the callback responsible for changing
908            //    thread.
909            // 2. Make the scheduler the only place that can change the active
910            //    thread.
911            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    /// Mark that the active thread tries to join the thread with `joined_thread_id`.
930    ///
931    /// When the join is successful (immediately, or as soon as the joined thread finishes), `success_retval` will be written to `return_dest`.
932    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            // On Windows this corresponds to joining on a closed handle.
942            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        // Mark the joined thread as being joined so that we detect if other
964        // threads try to join it.
965        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            // The joined thread is still running, we need to wait for it.
976            // Once we get unblocked, perform the appropriate synchronization and write the return value.
977            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            // The thread has already terminated - establish happens-before and write the return value.
995            after_join(this, joined_thread_id, success_retval, return_dest)?;
996        }
997        interp_ok(())
998    }
999
1000    /// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`.
1001    /// If the thread is already joined by another thread, it will throw UB.
1002    ///
1003    /// When the join is successful (immediately, or as soon as the joined thread finishes), `success_retval` will be written to `return_dest`.
1004    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        // Sanity check `join_status`.
1021        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    /// Set the name of the current thread. The buffer must not include the null terminator.
1082    #[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        // We do not yield immediately, as swapping out the current stack while executing a MIR statement
1098        // could lead to all sorts of confusion.
1099        // We should only switch stacks between steps.
1100        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}