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 Futex variable.
93    Futex,
94    /// Blocked on an InitOnce.
95    InitOnce,
96    /// Blocked until a file description readiness is satisfied (e.g. epoll).
97    Readiness,
98    /// Blocked on eventfd.
99    Eventfd,
100    /// Blocked on virtual socket.
101    VirtualSocket,
102    /// Blocked on an IO operation.
103    IO,
104    /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only).
105    /// Will be implicitly unblocked when GenMC schedules this thread again.
106    Genmc,
107}
108
109/// The state of a thread.
110enum ThreadState<'tcx> {
111    /// The thread is enabled and can be executed.
112    Enabled,
113    /// The thread is blocked on something.
114    Blocked { reason: BlockReason, deadline: Option<Deadline>, callback: DynUnblockCallback<'tcx> },
115    /// The thread has terminated its execution. We do not delete terminated
116    /// threads (FIXME: why?).
117    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/// The join status of a thread.
149#[derive(Debug, Copy, Clone, PartialEq, Eq)]
150enum ThreadJoinStatus {
151    /// The thread can be joined.
152    Joinable,
153    /// A thread is detached if its join handle was destroyed and no other
154    /// thread can join it.
155    Detached,
156    /// The thread was already joined by some thread and cannot be joined again.
157    Joined,
158}
159
160/// A thread.
161pub struct Thread<'tcx> {
162    state: ThreadState<'tcx>,
163
164    /// Name of the thread.
165    thread_name: Option<Vec<u8>>,
166
167    /// The virtual call stack.
168    stack: Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>>,
169
170    /// A span that explains where the thread (or more specifically, its current root
171    /// frame) "comes from".
172    pub(crate) origin_span: Span,
173
174    /// The function to call when the stack ran empty, to figure out what to do next.
175    /// Conceptually, this is the interpreter implementation of the things that happen 'after' the
176    /// Rust language entry point for this thread returns (usually implemented by the C or OS runtime).
177    /// (`None` is an error, it means the callback has not been set up yet or is actively running.)
178    pub(crate) on_stack_empty: Option<StackEmptyCallback<'tcx>>,
179
180    /// The index of the topmost user-relevant frame in `stack`. This field must contain
181    /// the value produced by `get_top_user_relevant_frame`.
182    /// This field is a cache to reduce how often we call that method. The cache is manually
183    /// maintained inside `MiriMachine::after_stack_push` and `MiriMachine::after_stack_pop`.
184    top_user_relevant_frame: Option<usize>,
185
186    /// The join status.
187    join_status: ThreadJoinStatus,
188
189    /// Stack of active unwind payloads for the current thread. Used for storing
190    /// the argument of the call to `miri_start_unwind` (the payload) when unwinding.
191    /// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`.
192    ///
193    /// In real unwinding, the payload gets passed as an argument to the landing pad,
194    /// which then forwards it to 'Resume'. However this argument is implicit in MIR,
195    /// so we have to store it out-of-band. When there are multiple active unwinds,
196    /// the innermost one is always caught first, so we can store them as a stack.
197    pub(crate) unwind_payloads: Vec<ImmTy<'tcx>>,
198
199    /// Last OS error location in memory. It is a 32-bit integer.
200    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    /// Get the name of the current thread if it was set.
208    fn thread_name(&self) -> Option<&[u8]> {
209        self.thread_name.as_deref()
210    }
211
212    /// Return whether this thread is enabled or not.
213    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    /// Get the name of the current thread for display purposes; will include thread ID if not set.
226    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    /// Return the top user-relevant frame, if there is one. `skip` indicates how many top frames
235    /// should be skipped.
236    /// Note that the choice to return `None` here when there is no user-relevant frame is part of
237    /// justifying the optimization that only pushes of user-relevant frames require updating the
238    /// `top_user_relevant_frame` field.
239    fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
240        // We are search for the frame with maximum relevance.
241        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                // We can short-circuit this search.
246                return Some(idx);
247            }
248            if best.is_none_or(|(_best_idx, best_relevance)| best_relevance < relevance) {
249                // The previous best frame has strictly worse relevance, so despite us being lower
250                // in the stack, we win.
251                best = Some((idx, relevance));
252            }
253        }
254        best.map(|(idx, _relevance)| idx)
255    }
256
257    /// Re-compute the top user-relevant frame from scratch. `skip` indicates how many top frames
258    /// should be skipped.
259    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    /// Set the top user-relevant frame to the given value. Must be equal to what
264    /// `get_top_user_relevant_frame` would return!
265    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    /// Returns the topmost frame that is considered user-relevant, or the
271    /// top of the stack if there is no such frame, or `None` if the stack is empty.
272    pub fn top_user_relevant_frame(&self) -> Option<usize> {
273        // This can be called upon creation of an allocation. We create allocations while setting up
274        // parts of the Rust runtime when we do not have any stack frames yet, so we need to handle
275        // empty stacks.
276        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: _, // we assume the closure captures no GC-relevant state
333        } = self;
334
335        for payload in panic_payload {
336            payload.visit_provenance(visit);
337        }
338        last_error.visit_provenance(visit);
339        for frame in stack {
340            frame.visit_provenance(visit)
341        }
342    }
343}
344
345impl VisitProvenance for Frame<'_, Provenance, FrameExtra<'_>> {
346    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
347        let return_place = self.return_place();
348        let locals = self.locals();
349        let Frame {
350            extra,
351            // There are some private fields we cannot access; they contain no tags.
352            ..
353        } = self;
354
355        // Return place.
356        return_place.visit_provenance(visit);
357        // Locals.
358        for local in locals.iter() {
359            // We only need the provenance so it's good for this to not be a real read.
360            match local.as_mplace_or_imm_ghost() {
361                None => {}
362                Some(Either::Left((ptr, meta))) => {
363                    ptr.visit_provenance(visit);
364                    meta.visit_provenance(visit);
365                }
366                Some(Either::Right(imm)) => {
367                    imm.visit_provenance(visit);
368                }
369            }
370        }
371
372        extra.visit_provenance(visit);
373    }
374}
375
376/// An error signaling that the requested thread doesn't exist or has terminated.
377#[derive(Debug, Copy, Clone)]
378pub enum ThreadLookupError {
379    /// No thread with this ID exists.
380    InvalidId,
381    /// The thread exists but has already terminated.
382    Terminated(ThreadId),
383}
384
385/// A set of threads.
386#[derive(Debug)]
387pub struct ThreadManager<'tcx> {
388    /// Identifier of the currently active thread.
389    active_thread: ThreadId,
390    /// Threads used in the program.
391    ///
392    /// Note that this vector also contains terminated threads.
393    threads: IndexVec<ThreadId, Thread<'tcx>>,
394    /// A mapping from a thread-local static to the thread specific allocation.
395    thread_local_allocs: FxHashMap<(DefId, ThreadId), StrictPointer>,
396    /// A flag that indicates that we should change the active thread.
397    /// Completely ignored in GenMC mode.
398    pub(super) yield_active_thread: bool,
399    /// A flag that indicates that we should do round robin scheduling of threads else randomized scheduling is used.
400    fixed_scheduling: bool,
401}
402
403impl VisitProvenance for ThreadManager<'_> {
404    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
405        let ThreadManager {
406            threads,
407            thread_local_allocs,
408            active_thread: _,
409            yield_active_thread: _,
410            fixed_scheduling: _,
411        } = self;
412
413        for thread in threads {
414            thread.visit_provenance(visit);
415        }
416        for ptr in thread_local_allocs.values() {
417            ptr.visit_provenance(visit);
418        }
419    }
420}
421
422impl<'tcx> ThreadManager<'tcx> {
423    pub(crate) fn new(config: &MiriConfig) -> Self {
424        let mut threads = IndexVec::new();
425        // Create the main thread and add it to the list of threads.
426        threads.push(Thread::new(Some("main"), None));
427        Self {
428            active_thread: ThreadId::MAIN_THREAD,
429            threads,
430            thread_local_allocs: Default::default(),
431            yield_active_thread: false,
432            fixed_scheduling: config.fixed_scheduling,
433        }
434    }
435
436    pub(crate) fn init(
437        ecx: &mut MiriInterpCx<'tcx>,
438        on_main_stack_empty: StackEmptyCallback<'tcx>,
439    ) {
440        ecx.machine.threads.threads[ThreadId::MAIN_THREAD].on_stack_empty =
441            Some(on_main_stack_empty);
442        if ecx.tcx.sess.target.os != Os::Windows {
443            // The main thread can *not* be joined on except on windows.
444            ecx.machine.threads.threads[ThreadId::MAIN_THREAD].join_status =
445                ThreadJoinStatus::Detached;
446        }
447    }
448
449    /// Returns the `ThreadId` for the given raw thread id.
450    /// Returns `Err(ThreadNotFound::InvalidId)` if the id is out of range, or
451    /// `Err(ThreadNotFound::Terminated(id))` if the thread exists but has terminated.
452    pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
453        if let Ok(id) = id.try_into()
454            && usize::try_from(id).is_ok_and(|id| id < self.threads.len())
455        {
456            let thread_id = ThreadId(id);
457            if self.threads[thread_id].state.is_terminated() {
458                Err(ThreadLookupError::Terminated(thread_id))
459            } else {
460                Ok(thread_id)
461            }
462        } else {
463            Err(ThreadLookupError::InvalidId)
464        }
465    }
466
467    /// Check if we have an allocation for the given thread local static for the
468    /// active thread.
469    fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<StrictPointer> {
470        self.thread_local_allocs.get(&(def_id, self.active_thread)).cloned()
471    }
472
473    /// Set the pointer for the allocation of the given thread local
474    /// static for the active thread.
475    ///
476    /// Panics if a thread local is initialized twice for the same thread.
477    fn set_thread_local_alloc(&mut self, def_id: DefId, ptr: StrictPointer) {
478        self.thread_local_allocs.try_insert((def_id, self.active_thread), ptr).unwrap();
479    }
480
481    /// Borrow the stack of the active thread.
482    pub fn active_thread_stack(&self) -> &[Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
483        &self.threads[self.active_thread].stack
484    }
485
486    /// Mutably borrow the stack of the active thread.
487    pub fn active_thread_stack_mut(
488        &mut self,
489    ) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
490        &mut self.threads[self.active_thread].stack
491    }
492
493    pub(super) fn all_threads(&self) -> impl Iterator<Item = (ThreadId, &Thread<'tcx>)> {
494        self.threads.iter_enumerated()
495    }
496
497    pub fn all_blocked_stacks(
498        &self,
499    ) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
500        self.threads
501            .iter_enumerated()
502            .filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. }))
503            .map(|(id, t)| (id, &t.stack[..]))
504    }
505
506    /// Create a new thread and returns its id.
507    fn create_thread(&mut self, on_stack_empty: StackEmptyCallback<'tcx>) -> ThreadId {
508        let new_thread_id = ThreadId::new(self.threads.len());
509        self.threads.push(Thread::new(None, Some(on_stack_empty)));
510        new_thread_id
511    }
512
513    /// Set an active thread and return the id of the thread that was active before.
514    pub(super) fn set_active_thread(&mut self, id: ThreadId) -> ThreadId {
515        assert!(id.index() < self.threads.len());
516        info!(
517            "---------- Now executing on thread `{}` (previous: `{}`) ----------------------------------------",
518            self.get_thread_display_name(id),
519            self.get_thread_display_name(self.active_thread)
520        );
521        std::mem::replace(&mut self.active_thread, id)
522    }
523
524    /// Get the id of the currently active thread.
525    pub fn active_thread(&self) -> ThreadId {
526        self.active_thread
527    }
528
529    /// Get the total number of threads that were ever spawn by this program.
530    pub fn get_total_thread_count(&self) -> usize {
531        self.threads.len()
532    }
533
534    /// Get the total of threads that are currently live, i.e., not yet terminated.
535    /// (They might be blocked.)
536    pub fn get_live_thread_count(&self) -> usize {
537        self.threads.iter().filter(|t| !t.state.is_terminated()).count()
538    }
539
540    /// Has the given thread terminated?
541    fn has_terminated(&self, thread_id: ThreadId) -> bool {
542        self.threads[thread_id].state.is_terminated()
543    }
544
545    /// Have all threads terminated?
546    fn have_all_terminated(&self) -> bool {
547        self.threads.iter().all(|thread| thread.state.is_terminated())
548    }
549
550    /// Enable the thread for execution. The thread must be terminated.
551    fn enable_thread(&mut self, thread_id: ThreadId) {
552        assert!(self.has_terminated(thread_id));
553        self.threads[thread_id].state = ThreadState::Enabled;
554    }
555
556    /// Get a mutable borrow of the currently active thread.
557    pub fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
558        &mut self.threads[self.active_thread]
559    }
560
561    /// Get a shared borrow of the currently active thread.
562    pub fn active_thread_ref(&self) -> &Thread<'tcx> {
563        &self.threads[self.active_thread]
564    }
565
566    pub fn thread_ref(&self, thread_id: ThreadId) -> &Thread<'tcx> {
567        &self.threads[thread_id]
568    }
569
570    /// Mark the thread as detached, which means that no other thread will try
571    /// to join it and the thread is responsible for cleaning up.
572    ///
573    /// `allow_terminated_joined` allows detaching joined threads that have already terminated.
574    /// This matches Windows's behavior for `CloseHandle`.
575    ///
576    /// See <https://docs.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers>:
577    /// > The handle is valid until closed, even after the thread it represents has been terminated.
578    fn detach_thread(&mut self, id: ThreadId, allow_terminated_joined: bool) -> InterpResult<'tcx> {
579        // NOTE: In GenMC mode, we treat detached threads like regular threads that are never joined, so there is no special handling required here.
580        trace!("detaching {:?}", id);
581
582        let is_ub = if allow_terminated_joined && self.threads[id].state.is_terminated() {
583            // "Detached" in particular means "not yet joined". Redundant detaching is still UB.
584            self.threads[id].join_status == ThreadJoinStatus::Detached
585        } else {
586            self.threads[id].join_status != ThreadJoinStatus::Joinable
587        };
588        if is_ub {
589            throw_ub_format!("trying to detach thread that was already detached or joined");
590        }
591
592        self.threads[id].join_status = ThreadJoinStatus::Detached;
593        interp_ok(())
594    }
595
596    /// Set the name of the given thread.
597    pub fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
598        self.threads[thread].thread_name = Some(new_thread_name);
599    }
600
601    /// Get the name of the given thread.
602    pub fn get_thread_name(&self, thread: ThreadId) -> Option<&[u8]> {
603        self.threads[thread].thread_name()
604    }
605
606    pub fn get_thread_display_name(&self, thread: ThreadId) -> String {
607        self.threads[thread].thread_display_name(thread)
608    }
609
610    /// Put the thread into the blocked state.
611    fn block_thread(
612        &mut self,
613        reason: BlockReason,
614        deadline: Option<Deadline>,
615        callback: DynUnblockCallback<'tcx>,
616    ) {
617        let state = &mut self.threads[self.active_thread].state;
618        assert!(state.is_enabled());
619        *state = ThreadState::Blocked { reason, deadline, callback }
620    }
621
622    pub fn fixed_scheduling(&self) -> bool {
623        self.fixed_scheduling
624    }
625}
626
627// Public interface to thread management.
628impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
629pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
630    #[inline]
631    fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadLookupError> {
632        self.eval_context_ref().machine.threads.thread_id_try_from(id)
633    }
634
635    /// Get a thread-specific allocation id for the given thread-local static.
636    /// If needed, allocate a new one.
637    fn get_or_create_thread_local_alloc(
638        &mut self,
639        def_id: DefId,
640    ) -> InterpResult<'tcx, StrictPointer> {
641        let this = self.eval_context_mut();
642        let tcx = this.tcx;
643        if let Some(old_alloc) = this.machine.threads.get_thread_local_alloc_id(def_id) {
644            // We already have a thread-specific allocation id for this
645            // thread-local static.
646            interp_ok(old_alloc)
647        } else {
648            // We need to allocate a thread-specific allocation id for this
649            // thread-local static.
650            // First, we compute the initial value for this static.
651            if tcx.is_foreign_item(def_id) {
652                throw_unsup_format!("foreign thread-local statics are not supported");
653            }
654            let params = this.machine.get_default_alloc_params();
655            let alloc = this.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
656            // We make a full copy of this allocation.
657            let mut alloc = alloc.inner().adjust_from_tcx(
658                &this.tcx,
659                |bytes, align| {
660                    interp_ok(MiriAllocBytes::from_bytes(
661                        std::borrow::Cow::Borrowed(bytes),
662                        align,
663                        params,
664                    ))
665                },
666                |ptr| this.global_root_pointer(ptr),
667            )?;
668            // This allocation will be deallocated when the thread dies, so it is not in read-only memory.
669            alloc.mutability = Mutability::Mut;
670            // Create a fresh allocation with this content.
671            let ptr = this.insert_allocation(alloc, MiriMemoryKind::Tls.into())?;
672            this.machine.threads.set_thread_local_alloc(def_id, ptr);
673            interp_ok(ptr)
674        }
675    }
676
677    /// Start a regular (non-main) thread.
678    #[inline]
679    fn start_regular_thread(
680        &mut self,
681        thread: Option<MPlaceTy<'tcx>>,
682        start_routine: Pointer,
683        start_abi: ExternAbi,
684        func_arg: ImmTy<'tcx>,
685        ret_layout: TyAndLayout<'tcx>,
686    ) -> InterpResult<'tcx, ThreadId> {
687        let this = self.eval_context_mut();
688
689        // Create the new thread
690        let current_span = this.machine.current_user_relevant_span();
691        let new_thread_id = this.machine.threads.create_thread({
692            let mut state = tls::TlsDtorsState::default();
693            Box::new(move |m| state.on_stack_empty(m))
694        });
695        match &mut this.machine.data_race {
696            GlobalDataRaceHandler::None => {}
697            GlobalDataRaceHandler::Vclocks(data_race) =>
698                data_race.thread_created(&this.machine.threads, new_thread_id, current_span),
699            GlobalDataRaceHandler::Genmc(genmc_ctx) =>
700                genmc_ctx.handle_thread_create(
701                    &this.machine.threads,
702                    start_routine,
703                    &func_arg,
704                    new_thread_id,
705                )?,
706        }
707        // Write the current thread-id, switch to the next thread later
708        // to treat this write operation as occurring on the current thread.
709        if let Some(thread_info_place) = thread {
710            this.write_scalar(
711                Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size),
712                &thread_info_place,
713            )?;
714        }
715
716        // Finally switch to new thread so that we can push the first stackframe.
717        // After this all accesses will be treated as occurring in the new thread.
718        let old_thread_id = this.machine.threads.set_active_thread(new_thread_id);
719
720        // The child inherits its parent's cpu affinity.
721        // Skips this if `machine.thread_cpu_affinity` is not initialized.
722        if let Some(thread_cpu_affinity) = &mut this.machine.thread_cpu_affinity
723            && let Some(cpuset) = thread_cpu_affinity.get(&old_thread_id).cloned()
724        {
725            thread_cpu_affinity.insert(new_thread_id, cpuset);
726        }
727
728        // Perform the function pointer load in the new thread frame.
729        let instance = this.get_ptr_fn(start_routine)?.as_instance()?;
730
731        // Note: the returned value is currently ignored (see the FIXME in
732        // pthread_join in shims/unix/thread.rs) because the Rust standard library does not use
733        // it.
734        let ret_place = this.allocate(ret_layout, MiriMemoryKind::Machine.into())?;
735
736        this.call_thread_root_function(
737            instance,
738            start_abi,
739            &[func_arg],
740            Some(&ret_place),
741            current_span,
742        )?;
743
744        // Restore the old active thread frame.
745        this.machine.threads.set_active_thread(old_thread_id);
746
747        interp_ok(new_thread_id)
748    }
749
750    /// Handles thread termination of the active thread: wakes up threads joining on this one,
751    /// and deals with the thread's thread-local statics according to `tls_alloc_action`.
752    ///
753    /// This is called by the eval loop when a thread's on_stack_empty returns `Ready`.
754    fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
755        let this = self.eval_context_mut();
756
757        // Mark thread as terminated.
758        let thread = this.active_thread_mut();
759        assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");
760        thread.state = ThreadState::Terminated;
761
762        // Deallocate TLS.
763        let gone_thread = this.active_thread();
764        {
765            let mut free_tls_statics = Vec::new();
766            this.machine.threads.thread_local_allocs.retain(|&(_def_id, thread), &mut alloc_id| {
767                if thread != gone_thread {
768                    // A different thread, keep this static around.
769                    return true;
770                }
771                // Delete this static from the map and from memory.
772                // We cannot free directly here as we cannot use `?` in this context.
773                free_tls_statics.push(alloc_id);
774                false
775            });
776            // Now free the TLS statics.
777            for ptr in free_tls_statics {
778                match tls_alloc_action {
779                    TlsAllocAction::Deallocate =>
780                        this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?,
781                    TlsAllocAction::Leak =>
782                        if let Some(alloc) = ptr.provenance.get_alloc_id() {
783                            trace!(
784                                "Thread-local static leaked and stored as static root: {:?}",
785                                alloc
786                            );
787                            this.machine.static_roots.push(alloc);
788                        },
789                }
790            }
791        }
792
793        match &mut this.machine.data_race {
794            GlobalDataRaceHandler::None => {}
795            GlobalDataRaceHandler::Vclocks(data_race) =>
796                data_race.thread_terminated(&this.machine.threads),
797            GlobalDataRaceHandler::Genmc(genmc_ctx) => {
798                // Inform GenMC that the thread finished.
799                // This needs to happen once all accesses to the thread are done, including freeing any TLS statics.
800                genmc_ctx.handle_thread_finish(&this.machine.threads)
801            }
802        }
803
804        // Unblock joining threads.
805        let unblock_reason = BlockReason::Join(gone_thread);
806        let threads = &this.machine.threads.threads;
807        let joining_threads = threads
808            .iter_enumerated()
809            .filter(|(_, thread)| thread.state.is_blocked_on(unblock_reason))
810            .map(|(id, _)| id)
811            .collect::<Vec<_>>();
812        for thread in joining_threads {
813            this.unblock_thread(thread, unblock_reason)?;
814        }
815
816        interp_ok(())
817    }
818
819    /// Block the current thread, with an optional timeout.
820    /// The callback will be invoked when the thread gets unblocked.
821    #[inline]
822    fn block_thread(
823        &mut self,
824        reason: BlockReason,
825        deadline: Option<Deadline>,
826        callback: DynUnblockCallback<'tcx>,
827    ) {
828        let this = self.eval_context_mut();
829        if deadline.is_some() && this.machine.data_race.as_genmc_ref().is_some() {
830            panic!("Unimplemented: Timeouts not yet supported in GenMC mode.");
831        }
832        if matches!(deadline, Some(Deadline::RealTime(_))) && !this.machine.communicate() {
833            panic!("cannot have `RealTime` timeout with isolation");
834        }
835        this.machine.threads.block_thread(reason, deadline, callback);
836    }
837
838    /// Put the blocked thread into the enabled state.
839    /// Sanity-checks that the thread previously was blocked for the right reason.
840    fn unblock_thread(&mut self, thread: ThreadId, reason: BlockReason) -> InterpResult<'tcx> {
841        let this = self.eval_context_mut();
842        let old_state =
843            mem::replace(&mut this.machine.threads.threads[thread].state, ThreadState::Enabled);
844        let callback = match old_state {
845            ThreadState::Blocked { reason: actual_reason, callback, .. } => {
846                assert_eq!(
847                    reason, actual_reason,
848                    "unblock_thread: thread was blocked for the wrong reason"
849                );
850                callback
851            }
852            _ => panic!("unblock_thread: thread was not blocked"),
853        };
854        // The callback must be executed in the previously blocked thread.
855        let old_thread = this.machine.threads.set_active_thread(thread);
856        callback.call(this, UnblockKind::Ready)?;
857        this.machine.threads.set_active_thread(old_thread);
858        interp_ok(())
859    }
860
861    /// Find all threads with expired timeouts, unblock them and execute their timeout callbacks.
862    ///
863    /// This method returns the minimum duration until the next thread deadline.
864    /// If all ready threads have no deadline set, [`None`] is returned.
865    fn unblock_expired_deadlines(&mut self) -> InterpResult<'tcx, Option<Duration>> {
866        let this = self.eval_context_mut();
867        let communicate = this.machine.communicate();
868
869        let mut min_wait_time = Option::<Duration>::None;
870        let mut callbacks = Vec::new();
871
872        for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() {
873            match &thread.state {
874                ThreadState::Blocked { deadline: Some(deadline), .. } => {
875                    let wait_time = match deadline {
876                        Deadline::Monotonic(instant) =>
877                            instant.duration_since(this.machine.monotonic_clock.now()),
878                        Deadline::RealTime(time) => {
879                            assert!(communicate, "cannot have `RealTime` timeout with isolation");
880                            time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)
881                        }
882                    };
883
884                    if wait_time.is_zero() {
885                        // The timeout expired for this thread.
886                        let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
887                        let ThreadState::Blocked { callback, .. } = old_state else {
888                            unreachable!()
889                        };
890                        // Add callback to list to be run after this loop because of borrow-checking.
891                        callbacks.push((id, callback));
892                    } else {
893                        // Update `min_wait_time` to contain the smallest duration until
894                        // the next timeout expires.
895                        min_wait_time = Some(wait_time.min(min_wait_time.unwrap_or(Duration::MAX)));
896                    }
897                }
898                _ => {}
899            }
900        }
901
902        for (thread, callback) in callbacks {
903            // This back-and-forth with `set_active_thread` is here because of two
904            // design decisions:
905            // 1. Make the caller and not the callback responsible for changing
906            //    thread.
907            // 2. Make the scheduler the only place that can change the active
908            //    thread.
909            let old_thread = this.machine.threads.set_active_thread(thread);
910            callback.call(this, UnblockKind::TimedOut)?;
911            this.machine.threads.set_active_thread(old_thread);
912        }
913
914        interp_ok(min_wait_time)
915    }
916
917    #[inline]
918    fn detach_thread(
919        &mut self,
920        thread_id: ThreadId,
921        allow_terminated_joined: bool,
922    ) -> InterpResult<'tcx> {
923        let this = self.eval_context_mut();
924        this.machine.threads.detach_thread(thread_id, allow_terminated_joined)
925    }
926
927    /// Mark that the active thread tries to join the thread with `joined_thread_id`.
928    ///
929    /// When the join is successful (immediately, or as soon as the joined thread finishes), `success_retval` will be written to `return_dest`.
930    fn join_thread(
931        &mut self,
932        joined_thread_id: ThreadId,
933        success_retval: Scalar,
934        return_dest: &MPlaceTy<'tcx>,
935    ) -> InterpResult<'tcx> {
936        let this = self.eval_context_mut();
937        let thread_mgr = &mut this.machine.threads;
938        if thread_mgr.threads[joined_thread_id].join_status == ThreadJoinStatus::Detached {
939            // On Windows this corresponds to joining on a closed handle.
940            throw_ub_format!("trying to join a detached thread");
941        }
942
943        fn after_join<'tcx>(
944            this: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
945            joined_thread_id: ThreadId,
946            success_retval: Scalar,
947            return_dest: &MPlaceTy<'tcx>,
948        ) -> InterpResult<'tcx> {
949            let threads = &this.machine.threads;
950            match &mut this.machine.data_race {
951                GlobalDataRaceHandler::None => {}
952                GlobalDataRaceHandler::Vclocks(data_race) =>
953                    data_race.thread_joined(threads, joined_thread_id),
954                GlobalDataRaceHandler::Genmc(genmc_ctx) =>
955                    genmc_ctx.handle_thread_join(threads.active_thread, joined_thread_id)?,
956            }
957            this.write_scalar(success_retval, return_dest)?;
958            interp_ok(())
959        }
960
961        // Mark the joined thread as being joined so that we detect if other
962        // threads try to join it.
963        thread_mgr.threads[joined_thread_id].join_status = ThreadJoinStatus::Joined;
964        if !thread_mgr.threads[joined_thread_id].state.is_terminated() {
965            trace!(
966                "{:?} blocked on {:?} when trying to join",
967                thread_mgr.active_thread, joined_thread_id
968            );
969            if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
970                genmc_ctx.handle_thread_join(thread_mgr.active_thread, joined_thread_id)?;
971            }
972
973            // The joined thread is still running, we need to wait for it.
974            // Once we get unblocked, perform the appropriate synchronization and write the return value.
975            let dest = return_dest.clone();
976            thread_mgr.block_thread(
977                BlockReason::Join(joined_thread_id),
978                None,
979                callback!(
980                    @capture<'tcx> {
981                        joined_thread_id: ThreadId,
982                        dest: MPlaceTy<'tcx>,
983                        success_retval: Scalar,
984                    }
985                    |this, unblock: UnblockKind| {
986                        assert_eq!(unblock, UnblockKind::Ready);
987                        after_join(this, joined_thread_id, success_retval, &dest)
988                    }
989                ),
990            );
991        } else {
992            // The thread has already terminated - establish happens-before and write the return value.
993            after_join(this, joined_thread_id, success_retval, return_dest)?;
994        }
995        interp_ok(())
996    }
997
998    /// Mark that the active thread tries to exclusively join the thread with `joined_thread_id`.
999    /// If the thread is already joined by another thread, it will throw UB.
1000    ///
1001    /// When the join is successful (immediately, or as soon as the joined thread finishes), `success_retval` will be written to `return_dest`.
1002    fn join_thread_exclusive(
1003        &mut self,
1004        joined_thread_id: ThreadId,
1005        success_retval: Scalar,
1006        return_dest: &MPlaceTy<'tcx>,
1007    ) -> InterpResult<'tcx> {
1008        let this = self.eval_context_mut();
1009        let threads = &this.machine.threads.threads;
1010        if threads[joined_thread_id].join_status == ThreadJoinStatus::Joined {
1011            throw_ub_format!("trying to join an already joined thread");
1012        }
1013
1014        if joined_thread_id == this.machine.threads.active_thread {
1015            throw_ub_format!("trying to join itself");
1016        }
1017
1018        // Sanity check `join_status`.
1019        assert!(
1020            threads
1021                .iter()
1022                .all(|thread| { !thread.state.is_blocked_on(BlockReason::Join(joined_thread_id)) }),
1023            "this thread already has threads waiting for its termination"
1024        );
1025
1026        this.join_thread(joined_thread_id, success_retval, return_dest)
1027    }
1028
1029    #[inline]
1030    fn active_thread(&self) -> ThreadId {
1031        let this = self.eval_context_ref();
1032        this.machine.threads.active_thread()
1033    }
1034
1035    #[inline]
1036    fn active_thread_mut(&mut self) -> &mut Thread<'tcx> {
1037        let this = self.eval_context_mut();
1038        this.machine.threads.active_thread_mut()
1039    }
1040
1041    #[inline]
1042    fn active_thread_ref(&self) -> &Thread<'tcx> {
1043        let this = self.eval_context_ref();
1044        this.machine.threads.active_thread_ref()
1045    }
1046
1047    #[inline]
1048    fn get_total_thread_count(&self) -> usize {
1049        let this = self.eval_context_ref();
1050        this.machine.threads.get_total_thread_count()
1051    }
1052
1053    #[inline]
1054    fn have_all_terminated(&self) -> bool {
1055        let this = self.eval_context_ref();
1056        this.machine.threads.have_all_terminated()
1057    }
1058
1059    #[inline]
1060    fn enable_thread(&mut self, thread_id: ThreadId) {
1061        let this = self.eval_context_mut();
1062        this.machine.threads.enable_thread(thread_id);
1063    }
1064
1065    #[inline]
1066    fn active_thread_stack<'a>(&'a self) -> &'a [Frame<'tcx, Provenance, FrameExtra<'tcx>>] {
1067        let this = self.eval_context_ref();
1068        this.machine.threads.active_thread_stack()
1069    }
1070
1071    #[inline]
1072    fn active_thread_stack_mut<'a>(
1073        &'a mut self,
1074    ) -> &'a mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
1075        let this = self.eval_context_mut();
1076        this.machine.threads.active_thread_stack_mut()
1077    }
1078
1079    /// Set the name of the current thread. The buffer must not include the null terminator.
1080    #[inline]
1081    fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
1082        self.eval_context_mut().machine.threads.set_thread_name(thread, new_thread_name);
1083    }
1084
1085    #[inline]
1086    fn get_thread_name<'c>(&'c self, thread: ThreadId) -> Option<&'c [u8]>
1087    where
1088        'tcx: 'c,
1089    {
1090        self.eval_context_ref().machine.threads.get_thread_name(thread)
1091    }
1092
1093    #[inline]
1094    fn yield_active_thread(&mut self) {
1095        // We do not yield immediately, as swapping out the current stack while executing a MIR statement
1096        // could lead to all sorts of confusion.
1097        // We should only switch stacks between steps.
1098        self.eval_context_mut().machine.threads.yield_active_thread = true;
1099    }
1100
1101    #[inline]
1102    fn maybe_preempt_active_thread(&mut self) {
1103        let this = self.eval_context_mut();
1104        if !this.machine.threads.fixed_scheduling
1105            && this.machine.rng.get_mut().random_bool(this.machine.preemption_rate)
1106        {
1107            this.yield_active_thread();
1108        }
1109    }
1110}