@@ -7,6 +7,7 @@ use log::trace;
77use rustc_data_structures:: fx:: FxHashMap ;
88use rustc_index:: vec:: { Idx , IndexVec } ;
99
10+ use super :: thread:: MachineCallback ;
1011use super :: vector_clock:: VClock ;
1112use 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}
0 commit comments