miri/concurrency/
init_once.rs

1use std::cell::RefCell;
2use std::collections::VecDeque;
3use std::rc::Rc;
4
5use super::thread::DynUnblockCallback;
6use super::vector_clock::VClock;
7use crate::*;
8
9#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
10/// The current status of a one time initialization.
11pub enum InitOnceStatus {
12    #[default]
13    Uninitialized,
14    Begun,
15    Complete,
16}
17
18/// The one time initialization state.
19#[derive(Default, Debug)]
20pub(super) struct InitOnce {
21    status: InitOnceStatus,
22    waiters: VecDeque<ThreadId>,
23    clock: VClock,
24}
25
26impl InitOnce {
27    #[inline]
28    pub fn status(&self) -> InitOnceStatus {
29        self.status
30    }
31
32    /// Begin initializing this InitOnce. Must only be called after checking that it is currently
33    /// uninitialized.
34    #[inline]
35    pub fn begin(&mut self) {
36        assert_eq!(
37            self.status(),
38            InitOnceStatus::Uninitialized,
39            "beginning already begun or complete init once"
40        );
41        self.status = InitOnceStatus::Begun;
42    }
43}
44
45#[derive(Default, Clone, Debug)]
46pub struct InitOnceRef(Rc<RefCell<InitOnce>>);
47
48impl InitOnceRef {
49    pub fn new() -> Self {
50        Self(Default::default())
51    }
52
53    pub fn status(&self) -> InitOnceStatus {
54        self.0.borrow().status()
55    }
56
57    pub fn begin(&self) {
58        self.0.borrow_mut().begin();
59    }
60}
61
62impl VisitProvenance for InitOnceRef {
63    // InitOnce contains no provenance.
64    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
65}
66
67impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
68pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
69    /// Put the thread into the queue waiting for the initialization.
70    #[inline]
71    fn init_once_enqueue_and_block(
72        &mut self,
73        init_once_ref: InitOnceRef,
74        callback: DynUnblockCallback<'tcx>,
75    ) {
76        let this = self.eval_context_mut();
77        let thread = this.active_thread();
78        let mut init_once = init_once_ref.0.borrow_mut();
79        assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
80
81        init_once.waiters.push_back(thread);
82        this.block_thread(BlockReason::InitOnce, None, callback);
83    }
84
85    #[inline]
86    fn init_once_complete(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> {
87        let this = self.eval_context_mut();
88
89        let mut init_once = init_once_ref.0.borrow_mut();
90        assert_eq!(
91            init_once.status,
92            InitOnceStatus::Begun,
93            "completing already complete or uninit init once"
94        );
95
96        init_once.status = InitOnceStatus::Complete;
97
98        // Each complete happens-before the end of the wait
99        if let Some(data_race) = this.machine.data_race.as_vclocks_ref() {
100            data_race
101                .release_clock(&this.machine.threads, |clock| init_once.clock.clone_from(clock));
102        }
103
104        // Wake up everyone.
105        // need to take the queue to avoid having `this` be borrowed multiple times
106        let waiters = std::mem::take(&mut init_once.waiters);
107        drop(init_once);
108        for waiter in waiters {
109            this.unblock_thread(waiter, BlockReason::InitOnce)?;
110        }
111
112        interp_ok(())
113    }
114
115    #[inline]
116    fn init_once_fail(&mut self, init_once_ref: &InitOnceRef) -> InterpResult<'tcx> {
117        let this = self.eval_context_mut();
118        let mut init_once = init_once_ref.0.borrow_mut();
119        assert_eq!(
120            init_once.status,
121            InitOnceStatus::Begun,
122            "failing already completed or uninit init once"
123        );
124        // This is again uninitialized.
125        init_once.status = InitOnceStatus::Uninitialized;
126
127        // Each complete happens-before the end of the wait
128        if let Some(data_race) = this.machine.data_race.as_vclocks_ref() {
129            data_race
130                .release_clock(&this.machine.threads, |clock| init_once.clock.clone_from(clock));
131        }
132
133        // Wake up one waiting thread, so they can go ahead and try to init this.
134        if let Some(waiter) = init_once.waiters.pop_front() {
135            drop(init_once);
136            this.unblock_thread(waiter, BlockReason::InitOnce)?;
137        }
138
139        interp_ok(())
140    }
141
142    /// Synchronize with the previous completion of an InitOnce.
143    /// Must only be called after checking that it is complete.
144    #[inline]
145    fn init_once_observe_completed(&mut self, init_once_ref: &InitOnceRef) {
146        let this = self.eval_context_mut();
147        let init_once = init_once_ref.0.borrow();
148
149        assert_eq!(
150            init_once.status,
151            InitOnceStatus::Complete,
152            "observing the completion of incomplete init once"
153        );
154
155        this.acquire_clock(&init_once.clock);
156    }
157}