Skip to content

Commit 03da888

Browse files
committed
slight refactoring
1 parent 9680544 commit 03da888

File tree

7 files changed

+305
-274
lines changed

7 files changed

+305
-274
lines changed

src/concurrency/init_once.rs

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use std::collections::VecDeque;
2+
use std::num::NonZeroU32;
3+
4+
use rustc_index::vec::Idx;
5+
6+
use super::sync::EvalContextExtPriv;
7+
use super::thread::MachineCallback;
8+
use super::vector_clock::VClock;
9+
use crate::*;
10+
11+
declare_id!(InitOnceId);
12+
13+
/// A thread waiting on an InitOnce object.
14+
struct InitOnceWaiter<'mir, 'tcx> {
15+
/// The thread that is waiting.
16+
thread: ThreadId,
17+
/// The callback that should be executed, after the thread has been woken up.
18+
callback: Box<dyn MachineCallback<'mir, 'tcx> + 'tcx>,
19+
}
20+
21+
impl<'mir, 'tcx> std::fmt::Debug for InitOnceWaiter<'mir, 'tcx> {
22+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23+
f.debug_struct("InitOnce")
24+
.field("thread", &self.thread)
25+
.field("callback", &"dyn MachineCallback")
26+
.finish()
27+
}
28+
}
29+
30+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
31+
/// The current status of a one time initialization.
32+
pub enum InitOnceStatus {
33+
#[default]
34+
Uninitialized,
35+
Begun,
36+
Complete,
37+
}
38+
39+
/// The one time initialization state.
40+
#[derive(Default, Debug)]
41+
pub(super) struct InitOnce<'mir, 'tcx> {
42+
status: InitOnceStatus,
43+
waiters: VecDeque<InitOnceWaiter<'mir, 'tcx>>,
44+
data_race: VClock,
45+
}
46+
47+
impl<'mir, 'tcx> VisitTags for InitOnce<'mir, 'tcx> {
48+
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
49+
for waiter in self.waiters.iter() {
50+
waiter.callback.visit_tags(visit);
51+
}
52+
}
53+
}
54+
55+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
56+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
57+
fn init_once_get_or_create_id(
58+
&mut self,
59+
lock_op: &OpTy<'tcx, Provenance>,
60+
offset: u64,
61+
) -> InterpResult<'tcx, InitOnceId> {
62+
let this = self.eval_context_mut();
63+
this.init_once_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset))
64+
}
65+
66+
#[inline]
67+
/// Create state for a new one time initialization.
68+
fn init_once_create(&mut self) -> InitOnceId {
69+
let this = self.eval_context_mut();
70+
this.machine.threads.sync.init_onces.push(Default::default())
71+
}
72+
73+
#[inline]
74+
/// Provides the closure with the next InitOnceId. Creates that InitOnce if the closure returns None,
75+
/// otherwise returns the value from the closure
76+
fn init_once_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, InitOnceId>
77+
where
78+
F: FnOnce(
79+
&mut MiriInterpCx<'mir, 'tcx>,
80+
InitOnceId,
81+
) -> InterpResult<'tcx, Option<InitOnceId>>,
82+
{
83+
let this = self.eval_context_mut();
84+
let next_index = this.machine.threads.sync.init_onces.next_index();
85+
if let Some(old) = existing(this, next_index)? {
86+
Ok(old)
87+
} else {
88+
let new_index = this.machine.threads.sync.init_onces.push(Default::default());
89+
assert_eq!(next_index, new_index);
90+
Ok(new_index)
91+
}
92+
}
93+
94+
#[inline]
95+
fn init_once_status(&mut self, id: InitOnceId) -> InitOnceStatus {
96+
let this = self.eval_context_ref();
97+
this.machine.threads.sync.init_onces[id].status
98+
}
99+
100+
/// Put the thread into the queue waiting for the initialization.
101+
#[inline]
102+
fn init_once_enqueue_and_block(
103+
&mut self,
104+
id: InitOnceId,
105+
thread: ThreadId,
106+
callback: Box<dyn MachineCallback<'mir, 'tcx> + 'tcx>,
107+
) {
108+
let this = self.eval_context_mut();
109+
let init_once = &mut this.machine.threads.sync.init_onces[id];
110+
assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
111+
init_once.waiters.push_back(InitOnceWaiter { thread, callback });
112+
this.block_thread(thread);
113+
}
114+
115+
/// Begin initializing this InitOnce. Must only be called after checking that it is currently
116+
/// uninitialized.
117+
#[inline]
118+
fn init_once_begin(&mut self, id: InitOnceId) {
119+
let this = self.eval_context_mut();
120+
let init_once = &mut this.machine.threads.sync.init_onces[id];
121+
assert_eq!(
122+
init_once.status,
123+
InitOnceStatus::Uninitialized,
124+
"begining already begun or complete init once"
125+
);
126+
init_once.status = InitOnceStatus::Begun;
127+
}
128+
129+
#[inline]
130+
fn init_once_complete(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
131+
let this = self.eval_context_mut();
132+
let current_thread = this.get_active_thread();
133+
let init_once = &mut this.machine.threads.sync.init_onces[id];
134+
135+
assert_eq!(
136+
init_once.status,
137+
InitOnceStatus::Begun,
138+
"completing already complete or uninit init once"
139+
);
140+
141+
init_once.status = InitOnceStatus::Complete;
142+
143+
// Each complete happens-before the end of the wait
144+
if let Some(data_race) = &this.machine.data_race {
145+
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
146+
}
147+
148+
// Wake up everyone.
149+
// need to take the queue to avoid having `this` be borrowed multiple times
150+
for waiter in std::mem::take(&mut init_once.waiters) {
151+
this.unblock_thread(waiter.thread);
152+
153+
this.set_active_thread(waiter.thread);
154+
waiter.callback.call(this)?;
155+
this.set_active_thread(current_thread);
156+
157+
if let Some(data_race) = &this.machine.data_race {
158+
data_race.validate_lock_acquire(
159+
&this.machine.threads.sync.init_onces[id].data_race,
160+
waiter.thread,
161+
);
162+
}
163+
}
164+
165+
Ok(())
166+
}
167+
168+
#[inline]
169+
fn init_once_fail(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
170+
let this = self.eval_context_mut();
171+
let current_thread = this.get_active_thread();
172+
let init_once = &mut this.machine.threads.sync.init_onces[id];
173+
assert_eq!(
174+
init_once.status,
175+
InitOnceStatus::Begun,
176+
"failing already completed or uninit init once"
177+
);
178+
179+
// Each complete happens-before the end of the wait
180+
// FIXME: should this really induce synchronization? If we think of it as a lock, then yes,
181+
// but the docs don't talk about such details.
182+
if let Some(data_race) = &this.machine.data_race {
183+
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
184+
}
185+
186+
// Wake up one waiting thread, so they can go ahead and try to init this.
187+
if let Some(waiter) = init_once.waiters.pop_front() {
188+
// try initializing again on a different thread
189+
init_once.status = InitOnceStatus::Begun;
190+
191+
this.unblock_thread(waiter.thread);
192+
193+
this.set_active_thread(waiter.thread);
194+
waiter.callback.call(this)?;
195+
this.set_active_thread(current_thread);
196+
197+
if let Some(data_race) = &this.machine.data_race {
198+
data_race.validate_lock_acquire(
199+
&this.machine.threads.sync.init_onces[id].data_race,
200+
waiter.thread,
201+
);
202+
}
203+
} else {
204+
init_once.status = InitOnceStatus::Uninitialized;
205+
}
206+
207+
Ok(())
208+
}
209+
}

src/concurrency/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
pub mod data_race;
22
mod range_object_map;
3+
#[macro_use]
34
pub mod sync;
5+
pub mod init_once;
46
pub mod thread;
57
mod vector_clock;
68
pub mod weak_memory;

0 commit comments

Comments
 (0)