1use atomic_refcell::AtomicRefCell;
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
4use std::time::Instant;
5
6pub struct GlobalState {
16 pub(crate) gc_status: GcStatusWord,
18 pub(crate) gc_start_time: AtomicRefCell<Option<Instant>>,
20 pub(crate) emergency_collection: AtomicBool,
23 pub(crate) user_triggered_collection: AtomicBool,
25 pub(crate) internal_triggered_collection: AtomicBool,
28 pub(crate) last_internal_triggered_collection: AtomicBool,
30 pub(crate) allocation_success: AtomicBool,
32 pub(crate) max_collection_attempts: AtomicUsize,
34 pub(crate) cur_collection_attempts: AtomicUsize,
36 pub(crate) scanned_stacks: AtomicUsize,
38 pub(crate) stacks_prepared: AtomicBool,
40 pub(crate) allocation_bytes: AtomicUsize,
42 pub(crate) inside_harness: AtomicBool,
44 #[cfg(feature = "malloc_counted_size")]
46 pub(crate) malloc_bytes: AtomicUsize,
47 pub(crate) live_bytes_in_last_gc: AtomicRefCell<HashMap<&'static str, LiveBytesStats>>,
49 pub(crate) used_pages_after_last_gc: AtomicUsize,
51}
52
53impl GlobalState {
54 pub fn is_initialized(&self) -> bool {
56 self.gc_status.is_initialized()
57 }
58
59 pub fn set_collection_kind(
62 &self,
63 last_collection_was_exhaustive: bool,
64 heap_can_grow: bool,
65 ) -> bool {
66 self.cur_collection_attempts.store(
67 if self.user_triggered_collection.load(Ordering::Relaxed) {
68 1
69 } else {
70 self.determine_collection_attempts()
71 },
72 Ordering::Relaxed,
73 );
74
75 let emergency_collection = !self.is_internal_triggered_collection()
76 && last_collection_was_exhaustive
77 && self.cur_collection_attempts.load(Ordering::Relaxed) > 1
78 && !heap_can_grow;
79 self.emergency_collection
80 .store(emergency_collection, Ordering::Relaxed);
81
82 emergency_collection
83 }
84
85 fn determine_collection_attempts(&self) -> usize {
86 if !self.allocation_success.load(Ordering::Relaxed) {
87 self.max_collection_attempts.fetch_add(1, Ordering::Relaxed);
88 } else {
89 self.allocation_success.store(false, Ordering::Relaxed);
90 self.max_collection_attempts.store(1, Ordering::Relaxed);
91 }
92
93 self.max_collection_attempts.load(Ordering::Relaxed)
94 }
95
96 fn is_internal_triggered_collection(&self) -> bool {
97 let is_internal_triggered = self
98 .last_internal_triggered_collection
99 .load(Ordering::SeqCst);
100 assert!(
102 !is_internal_triggered,
103 "We have no concurrent GC implemented. We should not have internally triggered GC"
104 );
105 is_internal_triggered
106 }
107
108 pub fn is_emergency_collection(&self) -> bool {
109 self.emergency_collection.load(Ordering::Relaxed)
110 }
111
112 pub fn is_user_triggered_collection(&self) -> bool {
114 self.user_triggered_collection.load(Ordering::Relaxed)
115 }
116
117 pub fn reset_collection_trigger(&self) {
119 self.last_internal_triggered_collection.store(
120 self.internal_triggered_collection.load(Ordering::SeqCst),
121 Ordering::Relaxed,
122 );
123 self.internal_triggered_collection
124 .store(false, Ordering::SeqCst);
125 self.user_triggered_collection
126 .store(false, Ordering::Relaxed);
127 }
128
129 pub fn stacks_prepared(&self) -> bool {
131 self.stacks_prepared.load(Ordering::SeqCst)
132 }
133
134 pub fn prepare_for_stack_scanning(&self) {
137 self.scanned_stacks.store(0, Ordering::SeqCst);
138 self.stacks_prepared.store(false, Ordering::SeqCst);
139 }
140
141 pub fn inform_stack_scanned(&self, n_mutators: usize) -> bool {
146 let old = self.scanned_stacks.fetch_add(1, Ordering::SeqCst);
147 debug_assert!(
148 old < n_mutators,
149 "The number of scanned stacks ({}) is more than the number of mutators ({})",
150 old,
151 n_mutators
152 );
153 let scanning_done = old + 1 == n_mutators;
154 if scanning_done {
155 self.stacks_prepared.store(true, Ordering::SeqCst);
156 }
157 scanning_done
158 }
159
160 pub fn increase_allocation_bytes_by(&self, size: usize) -> usize {
162 let old_allocation_bytes = self.allocation_bytes.fetch_add(size, Ordering::SeqCst);
163 trace!(
164 "Stress GC: old_allocation_bytes = {}, size = {}, allocation_bytes = {}",
165 old_allocation_bytes,
166 size,
167 self.allocation_bytes.load(Ordering::Relaxed),
168 );
169 old_allocation_bytes + size
170 }
171
172 #[cfg(feature = "malloc_counted_size")]
173 pub fn get_malloc_bytes_in_pages(&self) -> usize {
174 crate::util::conversions::bytes_to_pages_up(self.malloc_bytes.load(Ordering::Relaxed))
175 }
176
177 #[cfg(feature = "malloc_counted_size")]
178 pub(crate) fn increase_malloc_bytes_by(&self, size: usize) {
179 self.malloc_bytes.fetch_add(size, Ordering::SeqCst);
180 }
181
182 #[cfg(feature = "malloc_counted_size")]
183 pub(crate) fn decrease_malloc_bytes_by(&self, size: usize) {
184 self.malloc_bytes.fetch_sub(size, Ordering::SeqCst);
185 }
186
187 pub(crate) fn set_used_pages_after_last_gc(&self, pages: usize) {
188 self.used_pages_after_last_gc
189 .store(pages, Ordering::Relaxed);
190 }
191
192 pub(crate) fn get_used_pages_after_last_gc(&self) -> usize {
193 self.used_pages_after_last_gc.load(Ordering::Relaxed)
194 }
195}
196
197impl Default for GlobalState {
198 fn default() -> Self {
199 Self {
200 gc_status: GcStatusWord::new(GcStatus::Uninitialized),
201 gc_start_time: AtomicRefCell::new(None),
202 stacks_prepared: AtomicBool::new(false),
203 emergency_collection: AtomicBool::new(false),
204 user_triggered_collection: AtomicBool::new(false),
205 internal_triggered_collection: AtomicBool::new(false),
206 last_internal_triggered_collection: AtomicBool::new(false),
207 allocation_success: AtomicBool::new(false),
208 max_collection_attempts: AtomicUsize::new(0),
209 cur_collection_attempts: AtomicUsize::new(0),
210 scanned_stacks: AtomicUsize::new(0),
211 allocation_bytes: AtomicUsize::new(0),
212 inside_harness: AtomicBool::new(false),
213 #[cfg(feature = "malloc_counted_size")]
214 malloc_bytes: AtomicUsize::new(0),
215 live_bytes_in_last_gc: AtomicRefCell::new(HashMap::new()),
216 used_pages_after_last_gc: AtomicUsize::new(0),
217 }
218 }
219}
220
221#[derive(PartialEq, Copy, Clone, Debug)]
226pub enum GcStatus {
227 Uninitialized,
230 NotInGC,
232 InConcurrentGC,
235 InPause,
238 PauseRequested,
241 Disabled(usize),
246}
247
248pub(crate) struct GcStatusWord(AtomicUsize);
259
260impl GcStatusWord {
261 const TAG_BITS: u32 = 3;
264 const TAG_MASK: usize = (1 << Self::TAG_BITS) - 1;
265
266 fn encode(status: GcStatus) -> usize {
267 match status {
268 GcStatus::Uninitialized => 0,
269 GcStatus::NotInGC => 1,
270 GcStatus::InConcurrentGC => 2,
271 GcStatus::InPause => 3,
272 GcStatus::PauseRequested => 4,
273 GcStatus::Disabled(depth) => {
274 debug_assert!(
275 depth < (1 << (usize::BITS - Self::TAG_BITS)),
276 "GC-disable nesting depth overflows the bits reserved for it"
277 );
278 5 | (depth << Self::TAG_BITS)
279 }
280 }
281 }
282
283 fn decode(bits: usize) -> GcStatus {
284 match bits & Self::TAG_MASK {
285 0 => GcStatus::Uninitialized,
286 1 => GcStatus::NotInGC,
287 2 => GcStatus::InConcurrentGC,
288 3 => GcStatus::InPause,
289 4 => GcStatus::PauseRequested,
290 5 => GcStatus::Disabled(bits >> Self::TAG_BITS),
291 _ => unreachable!("invalid encoded GcStatus tag"),
292 }
293 }
294
295 pub(crate) fn new(status: GcStatus) -> Self {
296 GcStatusWord(AtomicUsize::new(Self::encode(status)))
297 }
298
299 pub(crate) fn load(&self) -> GcStatus {
301 Self::decode(self.0.load(Ordering::SeqCst))
302 }
303
304 fn transition_inner<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
307 let old_bits = self
308 .0
309 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
310 Some(Self::encode(f(Self::decode(bits))))
311 })
312 .unwrap(); Self::decode(old_bits)
314 }
315
316 fn transition<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
328 let mut maybe_new_state = None;
329 let old_state = self.transition_inner(|old_state| {
330 let new_state = f(old_state);
331 maybe_new_state = Some(new_state);
332 new_state
333 });
334 let new_state = maybe_new_state.unwrap();
335 log::trace!("GC status transitioned from {old_state:?} to {new_state:?}");
336 old_state
337 }
338
339 fn try_transition_inner<F: FnMut(GcStatus) -> Option<GcStatus>>(
342 &self,
343 mut f: F,
344 ) -> Result<GcStatus, GcStatus> {
345 self.0
346 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
347 f(Self::decode(bits)).map(Self::encode)
348 })
349 .map(Self::decode)
350 .map_err(Self::decode)
351 }
352
353 fn try_transition<F: FnMut(GcStatus) -> Option<GcStatus>>(
359 &self,
360 mut f: F,
361 ) -> Result<GcStatus, GcStatus> {
362 let mut maybe_new_state = None;
363 let result = self.try_transition_inner(|old_state| {
364 maybe_new_state = f(old_state);
365 maybe_new_state
366 });
367
368 match result {
369 Ok(old_state) => {
370 let new_state = maybe_new_state.unwrap();
371 log::trace!("GC status transitioned from {old_state:?} to {new_state:?}")
372 }
373 Err(old_state) => {
374 log::trace!("GC status transition attempted, but remains {old_state:?}")
375 }
376 }
377
378 result
379 }
380
381 pub(crate) fn is_initialized(&self) -> bool {
382 self.load() != GcStatus::Uninitialized
383 }
384
385 pub(crate) fn is_disabled(&self) -> bool {
386 matches!(self.load(), GcStatus::Disabled(_))
387 }
388
389 pub(crate) fn set_initialized(&self) {
391 self.transition(|status| {
392 assert!(
393 status == GcStatus::Uninitialized,
394 "Trying to set initialized GC status when it is not uninitialized"
395 );
396 GcStatus::NotInGC
397 });
398 }
399
400 pub(crate) fn set_uninitialized(&self) {
402 self.transition(|status| {
403 assert!(
404 status != GcStatus::Uninitialized,
405 "Trying to set uninitialized GC status when it is already uninitialized"
406 );
407 GcStatus::Uninitialized
408 });
409 }
410
411 pub(crate) fn set_in_pause(&self) {
413 self.transition(|status| {
414 assert!(
415 status == GcStatus::PauseRequested,
416 "Trying to set in-pause GC status in invalid status: {:?}",
417 status
418 );
419 GcStatus::InPause
420 });
421 }
422
423 pub(crate) fn set_in_concurrent_gc(&self) {
426 self.transition(|status| {
427 assert!(
428 status == GcStatus::InPause,
429 "Trying to set in-concurrent-gc GC status in invalid status: {:?}",
430 status
431 );
432 GcStatus::InConcurrentGC
433 });
434 }
435
436 pub(crate) fn set_not_in_gc(&self) {
438 self.transition(|status| {
439 assert!(
440 status == GcStatus::InPause,
441 "Trying to set not-in-gc GC status in invalid status: {:?}",
442 status
443 );
444 GcStatus::NotInGC
445 });
446 }
447
448 pub(crate) fn set_disabled(&self) -> Result<bool, GcStatus> {
457 self.try_transition(|status| match status {
458 GcStatus::Disabled(depth) => Some(GcStatus::Disabled(depth + 1)),
459 GcStatus::NotInGC => Some(GcStatus::Disabled(1)),
460 _ => None,
461 })
462 .map(|old_status| old_status == GcStatus::NotInGC)
463 }
464
465 pub(crate) fn set_enabled(&self) -> bool {
471 let old = self.transition(|status| match status {
472 GcStatus::Disabled(1) => GcStatus::NotInGC,
473 GcStatus::Disabled(depth) => GcStatus::Disabled(depth - 1),
474 other => other,
475 });
476 old == GcStatus::Disabled(1)
477 }
478
479 pub(crate) fn try_request_pause(&self) -> Result<(), GcStatus> {
484 self.try_transition(|status| match status {
485 GcStatus::Disabled(_) | GcStatus::Uninitialized | GcStatus::PauseRequested => None,
486 GcStatus::NotInGC | GcStatus::InConcurrentGC => Some(GcStatus::PauseRequested),
487 _ => panic!("Trying to request a GC pause in invalid status: {status:?}"),
488 })
489 .map(|_| ())
490 }
491}
492
493#[cfg(test)]
494mod gc_status_tests {
495 use super::{GcStatus, GcStatusWord};
496
497 #[test]
498 fn encode_decode_roundtrip() {
499 let statuses = [
500 GcStatus::Uninitialized,
501 GcStatus::NotInGC,
502 GcStatus::InConcurrentGC,
503 GcStatus::InPause,
504 GcStatus::PauseRequested,
505 GcStatus::Disabled(1),
506 GcStatus::Disabled(42),
507 ];
508 for status in statuses {
509 assert_eq!(GcStatusWord::decode(GcStatusWord::encode(status)), status);
510 }
511 }
512
513 #[test]
514 fn new_and_load_roundtrip() {
515 let statuses = [
516 GcStatus::Uninitialized,
517 GcStatus::NotInGC,
518 GcStatus::InConcurrentGC,
519 GcStatus::InPause,
520 GcStatus::PauseRequested,
521 GcStatus::Disabled(1),
522 GcStatus::Disabled(42),
523 ];
524 for status in statuses {
525 assert_eq!(GcStatusWord::new(status).load(), status);
526 }
527 }
528
529 #[test]
530 fn set_initialized_from_uninitialized() {
531 let word = GcStatusWord::new(GcStatus::Uninitialized);
532 assert!(!word.is_initialized());
533 word.set_initialized();
534 assert_eq!(word.load(), GcStatus::NotInGC);
535 assert!(word.is_initialized());
536 }
537
538 #[test]
539 #[should_panic(expected = "not uninitialized")]
540 fn set_initialized_panics_if_already_initialized() {
541 GcStatusWord::new(GcStatus::NotInGC).set_initialized();
542 }
543
544 #[test]
545 fn set_uninitialized_from_not_in_gc() {
546 let word = GcStatusWord::new(GcStatus::NotInGC);
547 word.set_uninitialized();
548 assert_eq!(word.load(), GcStatus::Uninitialized);
549 }
550
551 #[test]
552 #[should_panic(expected = "already uninitialized")]
553 fn set_uninitialized_panics_if_already_uninitialized() {
554 GcStatusWord::new(GcStatus::Uninitialized).set_uninitialized();
555 }
556
557 #[test]
558 fn try_request_pause_from_not_in_gc() {
559 let word = GcStatusWord::new(GcStatus::NotInGC);
560 assert!(word.try_request_pause().is_ok());
561 assert_eq!(word.load(), GcStatus::PauseRequested);
562 }
563
564 #[test]
565 fn try_request_pause_from_in_concurrent_gc() {
566 let word = GcStatusWord::new(GcStatus::InConcurrentGC);
567 assert!(word.try_request_pause().is_ok());
568 assert_eq!(word.load(), GcStatus::PauseRequested);
569 }
570
571 #[test]
572 fn try_request_pause_when_already_requested() {
573 let word = GcStatusWord::new(GcStatus::PauseRequested);
574 assert_eq!(word.try_request_pause(), Err(GcStatus::PauseRequested));
575 assert_eq!(word.load(), GcStatus::PauseRequested);
577 }
578
579 #[test]
584 #[should_panic(expected = "invalid status")]
585 fn try_request_pause_panics_when_already_in_pause() {
586 let _ = GcStatusWord::new(GcStatus::InPause).try_request_pause();
587 }
588
589 #[test]
590 fn try_request_pause_when_disabled() {
591 let word = GcStatusWord::new(GcStatus::Disabled(1));
592 assert_eq!(word.try_request_pause(), Err(GcStatus::Disabled(1)));
593 assert_eq!(word.load(), GcStatus::Disabled(1));
595 }
596
597 #[test]
603 fn try_request_pause_when_uninitialized() {
604 let word = GcStatusWord::new(GcStatus::Uninitialized);
605 assert_eq!(word.try_request_pause(), Err(GcStatus::Uninitialized));
606 assert_eq!(word.load(), GcStatus::Uninitialized);
607 }
608
609 #[test]
610 fn set_in_pause_from_pause_requested() {
611 let word = GcStatusWord::new(GcStatus::PauseRequested);
612 word.set_in_pause();
613 assert_eq!(word.load(), GcStatus::InPause);
614 }
615
616 #[test]
617 #[should_panic(expected = "invalid status")]
618 fn set_in_pause_panics_if_not_requested() {
619 GcStatusWord::new(GcStatus::NotInGC).set_in_pause();
620 }
621
622 #[test]
623 fn set_disabled_from_not_in_gc() {
624 let word = GcStatusWord::new(GcStatus::NotInGC);
625 assert_eq!(word.set_disabled(), Ok(true));
626 assert_eq!(word.load(), GcStatus::Disabled(1));
627 }
628
629 #[test]
630 fn set_disabled_nests() {
631 let word = GcStatusWord::new(GcStatus::Disabled(1));
632 assert_eq!(word.set_disabled(), Ok(false));
633 assert_eq!(word.load(), GcStatus::Disabled(2));
634
635 assert_eq!(word.set_disabled(), Ok(false));
636 assert_eq!(word.load(), GcStatus::Disabled(3));
637 }
638
639 #[test]
640 fn set_disabled_fails_without_changing_status() {
641 for status in [
642 GcStatus::Uninitialized,
643 GcStatus::InConcurrentGC,
644 GcStatus::PauseRequested,
645 GcStatus::InPause,
646 ] {
647 let word = GcStatusWord::new(status);
648 assert_eq!(word.set_disabled(), Err(status));
649 assert_eq!(word.load(), status);
650 }
651 }
652
653 #[test]
654 fn set_enabled_decrements_nesting() {
655 let word = GcStatusWord::new(GcStatus::Disabled(3));
656 assert!(!word.set_enabled());
657 assert_eq!(word.load(), GcStatus::Disabled(2));
658 }
659
660 #[test]
661 fn set_enabled_to_not_in_gc_at_zero_depth() {
662 let word = GcStatusWord::new(GcStatus::Disabled(1));
663 assert!(word.set_enabled());
664 assert_eq!(word.load(), GcStatus::NotInGC);
665 }
666
667 #[test]
668 fn set_disabled_and_set_enabled_nest_round_trip() {
669 let word = GcStatusWord::new(GcStatus::NotInGC);
670 assert!(word.set_disabled().is_ok());
671 assert!(word.set_disabled().is_ok());
672 assert!(word.set_disabled().is_ok());
673 assert_eq!(word.load(), GcStatus::Disabled(3));
674
675 assert!(!word.set_enabled());
678 assert_eq!(word.load(), GcStatus::Disabled(2));
679 assert!(!word.set_enabled());
680 assert_eq!(word.load(), GcStatus::Disabled(1));
681 assert!(word.set_enabled());
682 assert_eq!(word.load(), GcStatus::NotInGC);
683 }
684
685 #[test]
686 fn set_enabled_is_noop_if_not_disabled() {
687 for status in [
688 GcStatus::Uninitialized,
689 GcStatus::NotInGC,
690 GcStatus::InConcurrentGC,
691 GcStatus::InPause,
692 GcStatus::PauseRequested,
693 ] {
694 let word = GcStatusWord::new(status);
695 assert!(!word.set_enabled());
696 assert_eq!(word.load(), status);
697 }
698 }
699
700 #[test]
701 fn set_in_concurrent_gc_from_in_pause() {
702 let word = GcStatusWord::new(GcStatus::InPause);
703 word.set_in_concurrent_gc();
704 assert_eq!(word.load(), GcStatus::InConcurrentGC);
705 }
706
707 #[test]
708 #[should_panic(expected = "invalid status")]
709 fn set_in_concurrent_gc_panics_if_not_in_pause() {
710 GcStatusWord::new(GcStatus::NotInGC).set_in_concurrent_gc();
711 }
712
713 #[test]
714 fn set_not_in_gc_from_in_pause() {
715 let word = GcStatusWord::new(GcStatus::InPause);
716 word.set_not_in_gc();
717 assert_eq!(word.load(), GcStatus::NotInGC);
718 }
719
720 #[test]
721 #[should_panic(expected = "invalid status")]
722 fn set_not_in_gc_panics_if_not_in_pause() {
723 GcStatusWord::new(GcStatus::InConcurrentGC).set_not_in_gc();
724 }
725
726 #[test]
727 fn is_disabled_reflects_status() {
728 assert!(GcStatusWord::new(GcStatus::Disabled(1)).is_disabled());
729 assert!(!GcStatusWord::new(GcStatus::NotInGC).is_disabled());
730 }
731}
732
733#[derive(Copy, Clone, Debug)]
735pub struct LiveBytesStats {
736 pub live_bytes: usize,
738 pub used_pages: usize,
740 pub used_bytes: usize,
743}