Skip to content

Commit 3244c11

Browse files
beepster4096RalfJung
authored andcommitted
add windows one time initialization
1 parent 4207f9e commit 3244c11

File tree

5 files changed

+368
-32
lines changed

5 files changed

+368
-32
lines changed

src/tools/miri/src/concurrency/sync.rs

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use log::trace;
77
use rustc_data_structures::fx::FxHashMap;
88
use rustc_index::vec::{Idx, IndexVec};
99

10+
use super::thread::MachineCallback;
1011
use super::vector_clock::VClock;
1112
use crate::*;
1213

@@ -149,13 +150,68 @@ struct FutexWaiter {
149150
bitset: u32,
150151
}
151152

153+
declare_id!(InitOnceId);
154+
155+
struct InitOnceWaiter<'mir, 'tcx> {
156+
thread: ThreadId,
157+
callback: Box<dyn MachineCallback<'mir, 'tcx> + 'tcx>,
158+
}
159+
160+
impl<'mir, 'tcx> std::fmt::Debug for InitOnceWaiter<'mir, 'tcx> {
161+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162+
f.debug_struct("InitOnce")
163+
.field("thread", &self.thread)
164+
.field("callback", &"dyn MachineCallback")
165+
.finish()
166+
}
167+
}
168+
169+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
170+
/// The current status of a one time initialization.
171+
pub enum InitOnceStatus {
172+
Uninitialized,
173+
Begun,
174+
Complete,
175+
}
176+
177+
impl Default for InitOnceStatus {
178+
fn default() -> Self {
179+
Self::Uninitialized
180+
}
181+
}
182+
183+
/// The one time initialization state.
184+
#[derive(Default, Debug)]
185+
struct InitOnce<'mir, 'tcx> {
186+
status: InitOnceStatus,
187+
waiters: VecDeque<InitOnceWaiter<'mir, 'tcx>>,
188+
data_race: VClock,
189+
}
190+
191+
impl<'mir, 'tcx> VisitTags for InitOnce<'mir, 'tcx> {
192+
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
193+
for waiter in self.waiters.iter() {
194+
waiter.callback.visit_tags(visit);
195+
}
196+
}
197+
}
198+
152199
/// The state of all synchronization variables.
153200
#[derive(Default, Debug)]
154-
pub(crate) struct SynchronizationState {
201+
pub(crate) struct SynchronizationState<'mir, 'tcx> {
155202
mutexes: IndexVec<MutexId, Mutex>,
156203
rwlocks: IndexVec<RwLockId, RwLock>,
157204
condvars: IndexVec<CondvarId, Condvar>,
158205
futexes: FxHashMap<u64, Futex>,
206+
init_onces: IndexVec<InitOnceId, InitOnce<'mir, 'tcx>>,
207+
}
208+
209+
impl<'mir, 'tcx> VisitTags for SynchronizationState<'mir, 'tcx> {
210+
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
211+
for init_once in self.init_onces.iter() {
212+
init_once.visit_tags(visit);
213+
}
214+
}
159215
}
160216

161217
// Private extension trait for local helper methods
@@ -581,4 +637,144 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
581637
futex.waiters.retain(|waiter| waiter.thread != thread);
582638
}
583639
}
640+
641+
#[inline]
642+
/// Create state for a new one time initialization.
643+
fn init_once_create(&mut self) -> InitOnceId {
644+
let this = self.eval_context_mut();
645+
this.machine.threads.sync.init_onces.push(Default::default())
646+
}
647+
648+
#[inline]
649+
/// Provides the closure with the next InitOnceId. Creates that InitOnce if the closure returns None,
650+
/// otherwise returns the value from the closure
651+
fn init_once_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, InitOnceId>
652+
where
653+
F: FnOnce(
654+
&mut MiriInterpCx<'mir, 'tcx>,
655+
InitOnceId,
656+
) -> InterpResult<'tcx, Option<InitOnceId>>,
657+
{
658+
let this = self.eval_context_mut();
659+
let next_index = this.machine.threads.sync.init_onces.next_index();
660+
if let Some(old) = existing(this, next_index)? {
661+
Ok(old)
662+
} else {
663+
let new_index = this.machine.threads.sync.init_onces.push(Default::default());
664+
assert_eq!(next_index, new_index);
665+
Ok(new_index)
666+
}
667+
}
668+
669+
#[inline]
670+
fn init_once_status(&mut self, id: InitOnceId) -> InitOnceStatus {
671+
let this = self.eval_context_ref();
672+
this.machine.threads.sync.init_onces[id].status
673+
}
674+
675+
#[inline]
676+
/// Put the thread into the queue waiting for the initialization.
677+
fn init_once_enqueue_and_block(
678+
&mut self,
679+
id: InitOnceId,
680+
thread: ThreadId,
681+
callback: Box<dyn MachineCallback<'mir, 'tcx> + 'tcx>,
682+
) {
683+
let this = self.eval_context_mut();
684+
let init_once = &mut this.machine.threads.sync.init_onces[id];
685+
assert_ne!(init_once.status, InitOnceStatus::Complete, "queueing on complete init once");
686+
init_once.waiters.push_back(InitOnceWaiter { thread, callback });
687+
this.block_thread(thread);
688+
}
689+
690+
#[inline]
691+
fn init_once_begin(&mut self, id: InitOnceId) {
692+
let this = self.eval_context_mut();
693+
let init_once = &mut this.machine.threads.sync.init_onces[id];
694+
assert_eq!(
695+
init_once.status,
696+
InitOnceStatus::Uninitialized,
697+
"begining already begun or complete init once"
698+
);
699+
init_once.status = InitOnceStatus::Begun;
700+
}
701+
702+
#[inline]
703+
fn init_once_complete(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
704+
let this = self.eval_context_mut();
705+
let current_thread = this.get_active_thread();
706+
let init_once = &mut this.machine.threads.sync.init_onces[id];
707+
708+
assert_eq!(
709+
init_once.status,
710+
InitOnceStatus::Begun,
711+
"completing already complete or uninit init once"
712+
);
713+
714+
init_once.status = InitOnceStatus::Complete;
715+
716+
// Each complete happens-before the end of the wait
717+
if let Some(data_race) = &this.machine.data_race {
718+
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
719+
}
720+
721+
// need to take the queue to avoid having `this` be borrowed multiple times
722+
for waiter in std::mem::take(&mut init_once.waiters) {
723+
this.unblock_thread(waiter.thread);
724+
725+
this.set_active_thread(waiter.thread);
726+
waiter.callback.call(this)?;
727+
this.set_active_thread(current_thread);
728+
729+
if let Some(data_race) = &this.machine.data_race {
730+
data_race.validate_lock_acquire(
731+
&this.machine.threads.sync.init_onces[id].data_race,
732+
waiter.thread,
733+
);
734+
}
735+
}
736+
737+
Ok(())
738+
}
739+
740+
#[inline]
741+
fn init_once_fail(&mut self, id: InitOnceId) -> InterpResult<'tcx> {
742+
let this = self.eval_context_mut();
743+
let current_thread = this.get_active_thread();
744+
let init_once = &mut this.machine.threads.sync.init_onces[id];
745+
assert_eq!(
746+
init_once.status,
747+
InitOnceStatus::Begun,
748+
"failing already completed or uninit init once"
749+
);
750+
751+
// Each complete happens-before the end of the wait
752+
if let Some(data_race) = &this.machine.data_race {
753+
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
754+
}
755+
756+
// the behavior of failing the initialization is left vague by the docs
757+
// it had to be determined experimentally
758+
if let Some(waiter) = init_once.waiters.pop_front() {
759+
// try initializing again on a different thread
760+
init_once.status = InitOnceStatus::Begun;
761+
762+
this.unblock_thread(waiter.thread);
763+
764+
this.set_active_thread(waiter.thread);
765+
waiter.callback.call(this)?;
766+
this.set_active_thread(current_thread);
767+
768+
if let Some(data_race) = &this.machine.data_race {
769+
data_race.validate_lock_acquire(
770+
&this.machine.threads.sync.init_onces[id].data_race,
771+
waiter.thread,
772+
);
773+
}
774+
} else {
775+
init_once.status = InitOnceStatus::Uninitialized;
776+
}
777+
778+
Ok(())
779+
}
584780
}

src/tools/miri/src/concurrency/thread.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ pub enum SchedulingAction {
3030
Stop,
3131
}
3232

33-
/// Timeout callbacks can be created by synchronization primitives to tell the
34-
/// scheduler that they should be called once some period of time passes.
33+
/// Trait for callbacks that can be executed when some event happens, such as after a timeout.
3534
pub trait MachineCallback<'mir, 'tcx>: VisitTags {
3635
fn call(&self, ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx>;
3736
}
@@ -269,7 +268,7 @@ pub struct ThreadManager<'mir, 'tcx> {
269268
threads: IndexVec<ThreadId, Thread<'mir, 'tcx>>,
270269
/// This field is pub(crate) because the synchronization primitives
271270
/// (`crate::sync`) need a way to access it.
272-
pub(crate) sync: SynchronizationState,
271+
pub(crate) sync: SynchronizationState<'mir, 'tcx>,
273272
/// A mapping from a thread-local static to an allocation id of a thread
274273
/// specific allocation.
275274
thread_local_alloc_ids: RefCell<FxHashMap<(DefId, ThreadId), Pointer<Provenance>>>,
@@ -303,7 +302,7 @@ impl VisitTags for ThreadManager<'_, '_> {
303302
timeout_callbacks,
304303
active_thread: _,
305304
yield_active_thread: _,
306-
sync: _,
305+
sync,
307306
} = self;
308307

309308
for thread in threads {
@@ -315,6 +314,7 @@ impl VisitTags for ThreadManager<'_, '_> {
315314
for callback in timeout_callbacks.values() {
316315
callback.callback.visit_tags(visit);
317316
}
317+
sync.visit_tags(visit);
318318
}
319319
}
320320

src/tools/miri/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub use crate::concurrency::{
8787
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd,
8888
EvalContextExt as DataRaceEvalContextExt,
8989
},
90-
sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId},
90+
sync::{CondvarId, EvalContextExt as SyncEvalContextExt, InitOnceId, MutexId, RwLockId},
9191
thread::{
9292
EvalContextExt as ThreadsEvalContextExt, SchedulingAction, ThreadId, ThreadManager,
9393
ThreadState, Time,

src/tools/miri/src/shims/windows/foreign_items.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
261261
let ret = this.TryAcquireSRWLockShared(ptr)?;
262262
this.write_scalar(ret, dest)?;
263263
}
264+
"InitOnceBeginInitialize" => {
265+
let [ptr, flags, pending, context] =
266+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
267+
let result = this.InitOnceBeginInitialize(ptr, flags, pending, context)?;
268+
this.write_scalar(result, dest)?;
269+
}
270+
"InitOnceComplete" => {
271+
let [ptr, flags, context] =
272+
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
273+
let result = this.InitOnceComplete(ptr, flags, context)?;
274+
this.write_scalar(result, dest)?;
275+
}
264276

265277
// Dynamic symbol loading
266278
"GetProcAddress" => {

0 commit comments

Comments
 (0)