1use atomic_refcell::AtomicRefCell;
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
4use std::time::{Duration, Instant};
5
6pub struct GlobalState {
16 pub(crate) gc_status: GcStatusWord,
18 pub(crate) pause_requested_time: AtomicRefCell<Option<Instant>>,
20 pub(crate) pause_start_time: AtomicRefCell<Option<Instant>>,
24 pub(crate) emergency_collection: AtomicBool,
27 pub(crate) user_triggered_collection: AtomicBool,
29 pub(crate) internal_triggered_collection: AtomicBool,
32 pub(crate) last_internal_triggered_collection: AtomicBool,
34 pub(crate) allocation_success: AtomicBool,
36 pub(crate) max_collection_attempts: AtomicUsize,
38 pub(crate) cur_collection_attempts: AtomicUsize,
40 pub(crate) scanned_stacks: AtomicUsize,
42 pub(crate) stacks_prepared: AtomicBool,
44 pub(crate) allocation_bytes: AtomicUsize,
46 pub(crate) inside_harness: AtomicBool,
48 #[cfg(feature = "malloc_counted_size")]
50 pub(crate) malloc_bytes: AtomicUsize,
51 pub(crate) live_bytes_in_last_gc: AtomicRefCell<HashMap<&'static str, LiveBytesStats>>,
53 pub(crate) used_pages_after_last_gc: AtomicUsize,
55}
56
57impl GlobalState {
58 pub fn is_initialized(&self) -> bool {
60 self.gc_status.is_initialized()
61 }
62
63 pub fn set_collection_kind(
66 &self,
67 last_collection_was_exhaustive: bool,
68 heap_can_grow: bool,
69 ) -> bool {
70 self.cur_collection_attempts.store(
71 if self.user_triggered_collection.load(Ordering::Relaxed) {
72 1
73 } else {
74 self.determine_collection_attempts()
75 },
76 Ordering::Relaxed,
77 );
78
79 let emergency_collection = !self.is_internal_triggered_collection()
80 && last_collection_was_exhaustive
81 && self.cur_collection_attempts.load(Ordering::Relaxed) > 1
82 && !heap_can_grow;
83 self.emergency_collection
84 .store(emergency_collection, Ordering::Relaxed);
85
86 emergency_collection
87 }
88
89 fn determine_collection_attempts(&self) -> usize {
90 if !self.allocation_success.load(Ordering::Relaxed) {
91 self.max_collection_attempts.fetch_add(1, Ordering::Relaxed);
92 } else {
93 self.allocation_success.store(false, Ordering::Relaxed);
94 self.max_collection_attempts.store(1, Ordering::Relaxed);
95 }
96
97 self.max_collection_attempts.load(Ordering::Relaxed)
98 }
99
100 fn is_internal_triggered_collection(&self) -> bool {
101 let is_internal_triggered = self
102 .last_internal_triggered_collection
103 .load(Ordering::SeqCst);
104 assert!(
106 !is_internal_triggered,
107 "We have no concurrent GC implemented. We should not have internally triggered GC"
108 );
109 is_internal_triggered
110 }
111
112 pub fn is_emergency_collection(&self) -> bool {
113 self.emergency_collection.load(Ordering::Relaxed)
114 }
115
116 pub fn is_user_triggered_collection(&self) -> bool {
118 self.user_triggered_collection.load(Ordering::Relaxed)
119 }
120
121 pub fn reset_collection_trigger(&self) {
123 self.last_internal_triggered_collection.store(
124 self.internal_triggered_collection.load(Ordering::SeqCst),
125 Ordering::Relaxed,
126 );
127 self.internal_triggered_collection
128 .store(false, Ordering::SeqCst);
129 self.user_triggered_collection
130 .store(false, Ordering::Relaxed);
131 }
132
133 pub fn stacks_prepared(&self) -> bool {
135 self.stacks_prepared.load(Ordering::SeqCst)
136 }
137
138 pub fn prepare_for_stack_scanning(&self) {
141 self.scanned_stacks.store(0, Ordering::SeqCst);
142 self.stacks_prepared.store(false, Ordering::SeqCst);
143 }
144
145 pub fn inform_stack_scanned(&self, n_mutators: usize) -> bool {
150 let old = self.scanned_stacks.fetch_add(1, Ordering::SeqCst);
151 debug_assert!(
152 old < n_mutators,
153 "The number of scanned stacks ({}) is more than the number of mutators ({})",
154 old,
155 n_mutators
156 );
157 let scanning_done = old + 1 == n_mutators;
158 if scanning_done {
159 self.stacks_prepared.store(true, Ordering::SeqCst);
160 }
161 scanning_done
162 }
163
164 pub fn increase_allocation_bytes_by(&self, size: usize) -> usize {
166 let old_allocation_bytes = self.allocation_bytes.fetch_add(size, Ordering::SeqCst);
167 trace!(
168 "Stress GC: old_allocation_bytes = {}, size = {}, allocation_bytes = {}",
169 old_allocation_bytes,
170 size,
171 self.allocation_bytes.load(Ordering::Relaxed),
172 );
173 old_allocation_bytes + size
174 }
175
176 #[cfg(feature = "malloc_counted_size")]
177 pub fn get_malloc_bytes_in_pages(&self) -> usize {
178 crate::util::conversions::bytes_to_pages_up(self.malloc_bytes.load(Ordering::Relaxed))
179 }
180
181 #[cfg(feature = "malloc_counted_size")]
182 pub(crate) fn increase_malloc_bytes_by(&self, size: usize) {
183 self.malloc_bytes.fetch_add(size, Ordering::SeqCst);
184 }
185
186 #[cfg(feature = "malloc_counted_size")]
187 pub(crate) fn decrease_malloc_bytes_by(&self, size: usize) {
188 self.malloc_bytes.fetch_sub(size, Ordering::SeqCst);
189 }
190
191 pub(crate) fn set_used_pages_after_last_gc(&self, pages: usize) {
192 self.used_pages_after_last_gc
193 .store(pages, Ordering::Relaxed);
194 }
195
196 pub(crate) fn get_used_pages_after_last_gc(&self) -> usize {
197 self.used_pages_after_last_gc.load(Ordering::Relaxed)
198 }
199
200 pub(crate) fn record_pause_requested_time(&self) {
203 let mut pause_requested_time = self.pause_requested_time.borrow_mut();
204 assert!(
205 pause_requested_time.is_none(),
206 "A pause was requested while a previous pause request time is still pending"
207 );
208 *pause_requested_time = Some(Instant::now());
209 }
210
211 pub(crate) fn take_time_to_yield(&self) -> Duration {
215 self.pause_requested_time
216 .borrow_mut()
217 .take()
218 .expect("Pause requested time was not recorded")
219 .elapsed()
220 }
221
222 pub(crate) fn record_pause_start_time(&self) {
224 let mut pause_start_time = self.pause_start_time.borrow_mut();
225 assert!(
226 pause_start_time.is_none(),
227 "A pause was started while a previous pause start time is still pending"
228 );
229 *pause_start_time = Some(Instant::now());
230 }
231
232 pub(crate) fn take_pause_time(&self) -> Duration {
236 self.pause_start_time
237 .borrow_mut()
238 .take()
239 .expect("Pause start time was not recorded")
240 .elapsed()
241 }
242}
243
244impl Default for GlobalState {
245 fn default() -> Self {
246 Self {
247 gc_status: GcStatusWord::new(GcStatus::Uninitialized),
248 pause_requested_time: AtomicRefCell::new(None),
249 pause_start_time: AtomicRefCell::new(None),
250 stacks_prepared: AtomicBool::new(false),
251 emergency_collection: AtomicBool::new(false),
252 user_triggered_collection: AtomicBool::new(false),
253 internal_triggered_collection: AtomicBool::new(false),
254 last_internal_triggered_collection: AtomicBool::new(false),
255 allocation_success: AtomicBool::new(false),
256 max_collection_attempts: AtomicUsize::new(0),
257 cur_collection_attempts: AtomicUsize::new(0),
258 scanned_stacks: AtomicUsize::new(0),
259 allocation_bytes: AtomicUsize::new(0),
260 inside_harness: AtomicBool::new(false),
261 #[cfg(feature = "malloc_counted_size")]
262 malloc_bytes: AtomicUsize::new(0),
263 live_bytes_in_last_gc: AtomicRefCell::new(HashMap::new()),
264 used_pages_after_last_gc: AtomicUsize::new(0),
265 }
266 }
267}
268
269#[derive(PartialEq, Copy, Clone, Debug)]
274pub enum GcStatus {
275 Uninitialized,
278 NotInGC,
280 InConcurrentGC,
283 InPause,
286 PauseRequested,
289 Disabled(usize),
294}
295
296pub(crate) struct GcStatusWord(AtomicUsize);
307
308impl GcStatusWord {
309 const TAG_BITS: u32 = 3;
312 const TAG_MASK: usize = (1 << Self::TAG_BITS) - 1;
313
314 fn encode(status: GcStatus) -> usize {
315 match status {
316 GcStatus::Uninitialized => 0,
317 GcStatus::NotInGC => 1,
318 GcStatus::InConcurrentGC => 2,
319 GcStatus::InPause => 3,
320 GcStatus::PauseRequested => 4,
321 GcStatus::Disabled(depth) => {
322 debug_assert!(
323 depth < (1 << (usize::BITS - Self::TAG_BITS)),
324 "GC-disable nesting depth overflows the bits reserved for it"
325 );
326 5 | (depth << Self::TAG_BITS)
327 }
328 }
329 }
330
331 fn decode(bits: usize) -> GcStatus {
332 match bits & Self::TAG_MASK {
333 0 => GcStatus::Uninitialized,
334 1 => GcStatus::NotInGC,
335 2 => GcStatus::InConcurrentGC,
336 3 => GcStatus::InPause,
337 4 => GcStatus::PauseRequested,
338 5 => GcStatus::Disabled(bits >> Self::TAG_BITS),
339 _ => unreachable!("invalid encoded GcStatus tag"),
340 }
341 }
342
343 pub(crate) fn new(status: GcStatus) -> Self {
344 GcStatusWord(AtomicUsize::new(Self::encode(status)))
345 }
346
347 pub(crate) fn load(&self) -> GcStatus {
349 Self::decode(self.0.load(Ordering::SeqCst))
350 }
351
352 fn transition_inner<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
355 let old_bits = self
356 .0
357 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
358 Some(Self::encode(f(Self::decode(bits))))
359 })
360 .unwrap(); Self::decode(old_bits)
362 }
363
364 fn transition<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
376 let mut maybe_new_state = None;
377 let old_state = self.transition_inner(|old_state| {
378 let new_state = f(old_state);
379 maybe_new_state = Some(new_state);
380 new_state
381 });
382 let new_state = maybe_new_state.unwrap();
383 log::trace!("GC status transitioned from {old_state:?} to {new_state:?}");
384 old_state
385 }
386
387 fn try_transition_inner<F: FnMut(GcStatus) -> Option<GcStatus>>(
390 &self,
391 mut f: F,
392 ) -> Result<GcStatus, GcStatus> {
393 self.0
394 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
395 f(Self::decode(bits)).map(Self::encode)
396 })
397 .map(Self::decode)
398 .map_err(Self::decode)
399 }
400
401 fn try_transition<F: FnMut(GcStatus) -> Option<GcStatus>>(
407 &self,
408 mut f: F,
409 ) -> Result<GcStatus, GcStatus> {
410 let mut maybe_new_state = None;
411 let result = self.try_transition_inner(|old_state| {
412 maybe_new_state = f(old_state);
413 maybe_new_state
414 });
415
416 match result {
417 Ok(old_state) => {
418 let new_state = maybe_new_state.unwrap();
419 log::trace!("GC status transitioned from {old_state:?} to {new_state:?}")
420 }
421 Err(old_state) => {
422 log::trace!("GC status transition attempted, but remains {old_state:?}")
423 }
424 }
425
426 result
427 }
428
429 pub(crate) fn is_initialized(&self) -> bool {
430 self.load() != GcStatus::Uninitialized
431 }
432
433 pub(crate) fn is_disabled(&self) -> bool {
434 matches!(self.load(), GcStatus::Disabled(_))
435 }
436
437 pub(crate) fn set_initialized(&self) {
439 self.transition(|status| {
440 assert!(
441 status == GcStatus::Uninitialized,
442 "Trying to set initialized GC status when it is not uninitialized"
443 );
444 GcStatus::NotInGC
445 });
446 }
447
448 pub(crate) fn set_uninitialized(&self) {
450 self.transition(|status| {
451 assert!(
452 status != GcStatus::Uninitialized,
453 "Trying to set uninitialized GC status when it is already uninitialized"
454 );
455 GcStatus::Uninitialized
456 });
457 }
458
459 pub(crate) fn set_in_pause(&self) {
461 self.transition(|status| {
462 assert!(
463 status == GcStatus::PauseRequested,
464 "Trying to set in-pause GC status in invalid status: {:?}",
465 status
466 );
467 GcStatus::InPause
468 });
469 }
470
471 pub(crate) fn set_in_concurrent_gc(&self) {
474 self.transition(|status| {
475 assert!(
476 status == GcStatus::InPause,
477 "Trying to set in-concurrent-gc GC status in invalid status: {:?}",
478 status
479 );
480 GcStatus::InConcurrentGC
481 });
482 }
483
484 pub(crate) fn set_not_in_gc(&self) {
486 self.transition(|status| {
487 assert!(
488 status == GcStatus::InPause,
489 "Trying to set not-in-gc GC status in invalid status: {:?}",
490 status
491 );
492 GcStatus::NotInGC
493 });
494 }
495
496 pub(crate) fn set_disabled(&self) -> Result<bool, GcStatus> {
505 self.try_transition(|status| match status {
506 GcStatus::Disabled(depth) => Some(GcStatus::Disabled(depth + 1)),
507 GcStatus::NotInGC => Some(GcStatus::Disabled(1)),
508 _ => None,
509 })
510 .map(|old_status| old_status == GcStatus::NotInGC)
511 }
512
513 pub(crate) fn set_enabled(&self) -> bool {
519 let old = self.transition(|status| match status {
520 GcStatus::Disabled(1) => GcStatus::NotInGC,
521 GcStatus::Disabled(depth) => GcStatus::Disabled(depth - 1),
522 other => other,
523 });
524 old == GcStatus::Disabled(1)
525 }
526
527 pub(crate) fn try_request_pause(&self) -> Result<GcStatus, GcStatus> {
532 self.try_transition(|status| match status {
533 GcStatus::Disabled(_) | GcStatus::Uninitialized | GcStatus::PauseRequested => None,
534 GcStatus::NotInGC | GcStatus::InConcurrentGC => Some(GcStatus::PauseRequested),
535 _ => panic!("Trying to request a GC pause in invalid status: {status:?}"),
536 })
537 }
538}
539
540#[cfg(test)]
541mod gc_status_tests {
542 use super::{GcStatus, GcStatusWord};
543
544 #[test]
545 fn encode_decode_roundtrip() {
546 let statuses = [
547 GcStatus::Uninitialized,
548 GcStatus::NotInGC,
549 GcStatus::InConcurrentGC,
550 GcStatus::InPause,
551 GcStatus::PauseRequested,
552 GcStatus::Disabled(1),
553 GcStatus::Disabled(42),
554 ];
555 for status in statuses {
556 assert_eq!(GcStatusWord::decode(GcStatusWord::encode(status)), status);
557 }
558 }
559
560 #[test]
561 fn new_and_load_roundtrip() {
562 let statuses = [
563 GcStatus::Uninitialized,
564 GcStatus::NotInGC,
565 GcStatus::InConcurrentGC,
566 GcStatus::InPause,
567 GcStatus::PauseRequested,
568 GcStatus::Disabled(1),
569 GcStatus::Disabled(42),
570 ];
571 for status in statuses {
572 assert_eq!(GcStatusWord::new(status).load(), status);
573 }
574 }
575
576 #[test]
577 fn set_initialized_from_uninitialized() {
578 let word = GcStatusWord::new(GcStatus::Uninitialized);
579 assert!(!word.is_initialized());
580 word.set_initialized();
581 assert_eq!(word.load(), GcStatus::NotInGC);
582 assert!(word.is_initialized());
583 }
584
585 #[test]
586 #[should_panic(expected = "not uninitialized")]
587 fn set_initialized_panics_if_already_initialized() {
588 GcStatusWord::new(GcStatus::NotInGC).set_initialized();
589 }
590
591 #[test]
592 fn set_uninitialized_from_not_in_gc() {
593 let word = GcStatusWord::new(GcStatus::NotInGC);
594 word.set_uninitialized();
595 assert_eq!(word.load(), GcStatus::Uninitialized);
596 }
597
598 #[test]
599 #[should_panic(expected = "already uninitialized")]
600 fn set_uninitialized_panics_if_already_uninitialized() {
601 GcStatusWord::new(GcStatus::Uninitialized).set_uninitialized();
602 }
603
604 #[test]
605 fn try_request_pause_from_not_in_gc() {
606 let word = GcStatusWord::new(GcStatus::NotInGC);
607 assert_eq!(word.try_request_pause(), Ok(GcStatus::NotInGC));
608 assert_eq!(word.load(), GcStatus::PauseRequested);
609 }
610
611 #[test]
612 fn try_request_pause_from_in_concurrent_gc() {
613 let word = GcStatusWord::new(GcStatus::InConcurrentGC);
614 assert_eq!(word.try_request_pause(), Ok(GcStatus::InConcurrentGC));
615 assert_eq!(word.load(), GcStatus::PauseRequested);
616 }
617
618 #[test]
619 fn try_request_pause_when_already_requested() {
620 let word = GcStatusWord::new(GcStatus::PauseRequested);
621 assert_eq!(word.try_request_pause(), Err(GcStatus::PauseRequested));
622 assert_eq!(word.load(), GcStatus::PauseRequested);
624 }
625
626 #[test]
631 #[should_panic(expected = "invalid status")]
632 fn try_request_pause_panics_when_already_in_pause() {
633 let _ = GcStatusWord::new(GcStatus::InPause).try_request_pause();
634 }
635
636 #[test]
637 fn try_request_pause_when_disabled() {
638 let word = GcStatusWord::new(GcStatus::Disabled(1));
639 assert_eq!(word.try_request_pause(), Err(GcStatus::Disabled(1)));
640 assert_eq!(word.load(), GcStatus::Disabled(1));
642 }
643
644 #[test]
650 fn try_request_pause_when_uninitialized() {
651 let word = GcStatusWord::new(GcStatus::Uninitialized);
652 assert_eq!(word.try_request_pause(), Err(GcStatus::Uninitialized));
653 assert_eq!(word.load(), GcStatus::Uninitialized);
654 }
655
656 #[test]
657 fn set_in_pause_from_pause_requested() {
658 let word = GcStatusWord::new(GcStatus::PauseRequested);
659 word.set_in_pause();
660 assert_eq!(word.load(), GcStatus::InPause);
661 }
662
663 #[test]
664 #[should_panic(expected = "invalid status")]
665 fn set_in_pause_panics_if_not_requested() {
666 GcStatusWord::new(GcStatus::NotInGC).set_in_pause();
667 }
668
669 #[test]
670 fn set_disabled_from_not_in_gc() {
671 let word = GcStatusWord::new(GcStatus::NotInGC);
672 assert_eq!(word.set_disabled(), Ok(true));
673 assert_eq!(word.load(), GcStatus::Disabled(1));
674 }
675
676 #[test]
677 fn set_disabled_nests() {
678 let word = GcStatusWord::new(GcStatus::Disabled(1));
679 assert_eq!(word.set_disabled(), Ok(false));
680 assert_eq!(word.load(), GcStatus::Disabled(2));
681
682 assert_eq!(word.set_disabled(), Ok(false));
683 assert_eq!(word.load(), GcStatus::Disabled(3));
684 }
685
686 #[test]
687 fn set_disabled_fails_without_changing_status() {
688 for status in [
689 GcStatus::Uninitialized,
690 GcStatus::InConcurrentGC,
691 GcStatus::PauseRequested,
692 GcStatus::InPause,
693 ] {
694 let word = GcStatusWord::new(status);
695 assert_eq!(word.set_disabled(), Err(status));
696 assert_eq!(word.load(), status);
697 }
698 }
699
700 #[test]
701 fn set_enabled_decrements_nesting() {
702 let word = GcStatusWord::new(GcStatus::Disabled(3));
703 assert!(!word.set_enabled());
704 assert_eq!(word.load(), GcStatus::Disabled(2));
705 }
706
707 #[test]
708 fn set_enabled_to_not_in_gc_at_zero_depth() {
709 let word = GcStatusWord::new(GcStatus::Disabled(1));
710 assert!(word.set_enabled());
711 assert_eq!(word.load(), GcStatus::NotInGC);
712 }
713
714 #[test]
715 fn set_disabled_and_set_enabled_nest_round_trip() {
716 let word = GcStatusWord::new(GcStatus::NotInGC);
717 assert!(word.set_disabled().is_ok());
718 assert!(word.set_disabled().is_ok());
719 assert!(word.set_disabled().is_ok());
720 assert_eq!(word.load(), GcStatus::Disabled(3));
721
722 assert!(!word.set_enabled());
725 assert_eq!(word.load(), GcStatus::Disabled(2));
726 assert!(!word.set_enabled());
727 assert_eq!(word.load(), GcStatus::Disabled(1));
728 assert!(word.set_enabled());
729 assert_eq!(word.load(), GcStatus::NotInGC);
730 }
731
732 #[test]
733 fn set_enabled_is_noop_if_not_disabled() {
734 for status in [
735 GcStatus::Uninitialized,
736 GcStatus::NotInGC,
737 GcStatus::InConcurrentGC,
738 GcStatus::InPause,
739 GcStatus::PauseRequested,
740 ] {
741 let word = GcStatusWord::new(status);
742 assert!(!word.set_enabled());
743 assert_eq!(word.load(), status);
744 }
745 }
746
747 #[test]
748 fn set_in_concurrent_gc_from_in_pause() {
749 let word = GcStatusWord::new(GcStatus::InPause);
750 word.set_in_concurrent_gc();
751 assert_eq!(word.load(), GcStatus::InConcurrentGC);
752 }
753
754 #[test]
755 #[should_panic(expected = "invalid status")]
756 fn set_in_concurrent_gc_panics_if_not_in_pause() {
757 GcStatusWord::new(GcStatus::NotInGC).set_in_concurrent_gc();
758 }
759
760 #[test]
761 fn set_not_in_gc_from_in_pause() {
762 let word = GcStatusWord::new(GcStatus::InPause);
763 word.set_not_in_gc();
764 assert_eq!(word.load(), GcStatus::NotInGC);
765 }
766
767 #[test]
768 #[should_panic(expected = "invalid status")]
769 fn set_not_in_gc_panics_if_not_in_pause() {
770 GcStatusWord::new(GcStatus::InConcurrentGC).set_not_in_gc();
771 }
772
773 #[test]
774 fn is_disabled_reflects_status() {
775 assert!(GcStatusWord::new(GcStatus::Disabled(1)).is_disabled());
776 assert!(!GcStatusWord::new(GcStatus::NotInGC).is_disabled());
777 }
778}
779
780#[derive(Copy, Clone, Debug)]
782pub struct LiveBytesStats {
783 pub live_bytes: usize,
785 pub used_pages: usize,
787 pub used_bytes: usize,
790}