Skip to content

Commit 64757ed

Browse files
committed
Auto merge of #2601 - DrMeepster:windows_init_once, r=RalfJung
Add Windows InitOnceBeginInitialize and InitOnceComplete shims Fixes #2595
2 parents cd6b856 + c9b32cc commit 64757ed

File tree

12 files changed

+616
-194
lines changed

12 files changed

+616
-194
lines changed

rust-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
538f118da1409759ba198acc0ff62070bc6d2dce
1+
edabf59ca4646b3fc1a961c26431215001043f6a

src/concurrency/init_once.rs

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
/// Provides the closure with the next InitOnceId. Creates that InitOnce if the closure returns None,
67+
/// otherwise returns the value from the closure.
68+
#[inline]
69+
fn init_once_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, InitOnceId>
70+
where
71+
F: FnOnce(
72+
&mut MiriInterpCx<'mir, 'tcx>,
73+
InitOnceId,
74+
) -> InterpResult<'tcx, Option<InitOnceId>>,
75+
{
76+
let this = self.eval_context_mut();
77+
let next_index = this.machine.threads.sync.init_onces.next_index();
78+
if let Some(old) = existing(this, next_index)? {
79+
Ok(old)
80+
} else {
81+
let new_index = this.machine.threads.sync.init_onces.push(Default::default());
82+
assert_eq!(next_index, new_index);
83+
Ok(new_index)
84+
}
85+
}
86+
87+
#[inline]
88+
fn init_once_status(&mut self, id: InitOnceId) -> InitOnceStatus {
89+
let this = self.eval_context_ref();
90+
this.machine.threads.sync.init_onces[id].status
91+
}
92+
93+
/// Put the thread into the queue waiting for the initialization.
94+
#[inline]
95+
fn init_once_enqueue_and_block(
96+
&mut self,
97+
id: InitOnceId,
98+
thread: ThreadId,
99+
callback: Box<dyn MachineCallback<'mir, 'tcx> + 'tcx>,
100+
) {
101+
let this = self.eval_context_mut();
102+
let init_once = &mut this.machine.threads.sync.init_onces[id];
103+
assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
104+
init_once.waiters.push_back(InitOnceWaiter { thread, callback });
105+
this.block_thread(thread);
106+
}
107+
108+
/// Begin initializing this InitOnce. Must only be called after checking that it is currently
109+
/// uninitialized.
110+
#[inline]
111+
fn init_once_begin(&mut self, id: InitOnceId) {
112+
let this = self.eval_context_mut();
113+
let init_once = &mut this.machine.threads.sync.init_onces[id];
114+
assert_eq!(
115+
init_once.status,
116+
InitOnceStatus::Uninitialized,
117+
"begining already begun or complete init once"
118+
);
119+
init_once.status = InitOnceStatus::Begun;
120+
}
121+
122+
#[inline]
123+
fn init_once_complete(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
124+
let this = self.eval_context_mut();
125+
let current_thread = this.get_active_thread();
126+
let init_once = &mut this.machine.threads.sync.init_onces[id];
127+
128+
assert_eq!(
129+
init_once.status,
130+
InitOnceStatus::Begun,
131+
"completing already complete or uninit init once"
132+
);
133+
134+
init_once.status = InitOnceStatus::Complete;
135+
136+
// Each complete happens-before the end of the wait
137+
if let Some(data_race) = &this.machine.data_race {
138+
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
139+
}
140+
141+
// Wake up everyone.
142+
// need to take the queue to avoid having `this` be borrowed multiple times
143+
for waiter in std::mem::take(&mut init_once.waiters) {
144+
// End of the wait happens-before woken-up thread.
145+
if let Some(data_race) = &this.machine.data_race {
146+
data_race.validate_lock_acquire(
147+
&this.machine.threads.sync.init_onces[id].data_race,
148+
waiter.thread,
149+
);
150+
}
151+
152+
this.unblock_thread(waiter.thread);
153+
154+
// Call callback, with the woken-up thread as `current`.
155+
this.set_active_thread(waiter.thread);
156+
waiter.callback.call(this)?;
157+
this.set_active_thread(current_thread);
158+
}
159+
160+
Ok(())
161+
}
162+
163+
#[inline]
164+
fn init_once_fail(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
165+
let this = self.eval_context_mut();
166+
let current_thread = this.get_active_thread();
167+
let init_once = &mut this.machine.threads.sync.init_onces[id];
168+
assert_eq!(
169+
init_once.status,
170+
InitOnceStatus::Begun,
171+
"failing already completed or uninit init once"
172+
);
173+
174+
// Each complete happens-before the end of the wait
175+
// FIXME: should this really induce synchronization? If we think of it as a lock, then yes,
176+
// but the docs don't talk about such details.
177+
if let Some(data_race) = &this.machine.data_race {
178+
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
179+
}
180+
181+
// Wake up one waiting thread, so they can go ahead and try to init this.
182+
if let Some(waiter) = init_once.waiters.pop_front() {
183+
// End of the wait happens-before woken-up thread.
184+
if let Some(data_race) = &this.machine.data_race {
185+
data_race.validate_lock_acquire(
186+
&this.machine.threads.sync.init_onces[id].data_race,
187+
waiter.thread,
188+
);
189+
}
190+
191+
this.unblock_thread(waiter.thread);
192+
193+
// Call callback, with the woken-up thread as `current`.
194+
this.set_active_thread(waiter.thread);
195+
waiter.callback.call(this)?;
196+
this.set_active_thread(current_thread);
197+
} else {
198+
// Nobody there to take this, so go back to 'uninit'
199+
init_once.status = InitOnceStatus::Uninitialized;
200+
}
201+
202+
Ok(())
203+
}
204+
}

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)