@@ -7,6 +7,7 @@ use log::trace;
7
7
use rustc_data_structures:: fx:: FxHashMap ;
8
8
use rustc_index:: vec:: { Idx , IndexVec } ;
9
9
10
+ use super :: thread:: MachineCallback ;
10
11
use super :: vector_clock:: VClock ;
11
12
use crate :: * ;
12
13
@@ -149,13 +150,68 @@ struct FutexWaiter {
149
150
bitset : u32 ,
150
151
}
151
152
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
+
152
199
/// The state of all synchronization variables.
153
200
#[ derive( Default , Debug ) ]
154
- pub ( crate ) struct SynchronizationState {
201
+ pub ( crate ) struct SynchronizationState < ' mir , ' tcx > {
155
202
mutexes : IndexVec < MutexId , Mutex > ,
156
203
rwlocks : IndexVec < RwLockId , RwLock > ,
157
204
condvars : IndexVec < CondvarId , Condvar > ,
158
205
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
+ }
159
215
}
160
216
161
217
// Private extension trait for local helper methods
@@ -581,4 +637,144 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
581
637
futex. waiters . retain ( |waiter| waiter. thread != thread) ;
582
638
}
583
639
}
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
+ }
584
780
}
0 commit comments