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,
232 NotInGC,
234 InConcurrentGC,
237 InPause,
240 PauseRequested,
243}
244
245#[derive(Debug, PartialEq, Eq)]
247pub(crate) enum PauseRequestOutcome {
248 Uninitialized,
251 AlreadyRequested,
254 Requested,
257}
258
259pub(crate) struct GcStatusWord(AtomicUsize);
271
272impl GcStatusWord {
273 const TAG_BITS: u32 = 3;
276 const TAG_MASK: usize = (1 << Self::TAG_BITS) - 1;
277
278 fn encode(status: GcStatus) -> usize {
279 match status {
280 GcStatus::Uninitialized => 0,
281 GcStatus::NotInGC => 1,
282 GcStatus::InConcurrentGC => 2,
283 GcStatus::InPause => 3,
284 GcStatus::PauseRequested => 4,
285 }
286 }
287
288 fn decode(bits: usize) -> GcStatus {
289 match bits & Self::TAG_MASK {
290 0 => GcStatus::Uninitialized,
291 1 => GcStatus::NotInGC,
292 2 => GcStatus::InConcurrentGC,
293 3 => GcStatus::InPause,
294 4 => GcStatus::PauseRequested,
295 _ => unreachable!("invalid encoded GcStatus tag"),
296 }
297 }
298
299 pub(crate) fn new(status: GcStatus) -> Self {
300 GcStatusWord(AtomicUsize::new(Self::encode(status)))
301 }
302
303 pub(crate) fn load(&self) -> GcStatus {
305 Self::decode(self.0.load(Ordering::SeqCst))
306 }
307
308 fn transition<F: FnMut(GcStatus) -> GcStatus>(&self, mut f: F) -> GcStatus {
318 let old_bits = self
319 .0
320 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
321 Some(Self::encode(f(Self::decode(bits))))
322 })
323 .unwrap(); Self::decode(old_bits)
325 }
326
327 pub(crate) fn is_initialized(&self) -> bool {
328 self.load() != GcStatus::Uninitialized
329 }
330
331 pub(crate) fn set_initialized(&self) {
333 self.transition(|status| {
334 assert!(
335 status == GcStatus::Uninitialized,
336 "Trying to set initialized GC status when it is not uninitialized"
337 );
338 GcStatus::NotInGC
339 });
340 }
341
342 pub(crate) fn set_uninitialized(&self) {
344 self.transition(|status| {
345 assert!(
346 status != GcStatus::Uninitialized,
347 "Trying to set uninitialized GC status when it is already uninitialized"
348 );
349 GcStatus::Uninitialized
350 });
351 }
352
353 pub(crate) fn set_in_pause(&self) {
355 self.transition(|status| {
356 assert!(
357 status == GcStatus::PauseRequested,
358 "Trying to set in-pause GC status in invalid status: {:?}",
359 status
360 );
361 GcStatus::InPause
362 });
363 }
364
365 pub(crate) fn set_in_concurrent_gc(&self) {
368 self.transition(|status| {
369 assert!(
370 status == GcStatus::InPause,
371 "Trying to set in-concurrent-gc GC status in invalid status: {:?}",
372 status
373 );
374 GcStatus::InConcurrentGC
375 });
376 }
377
378 pub(crate) fn set_not_in_gc(&self) {
380 self.transition(|status| {
381 assert!(
382 status == GcStatus::InPause,
383 "Trying to set not-in-gc GC status in invalid status: {:?}",
384 status
385 );
386 GcStatus::NotInGC
387 });
388 }
389
390 pub(crate) fn try_request_pause(&self) -> PauseRequestOutcome {
393 match self
397 .0
398 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |bits| {
399 let status = Self::decode(bits);
400 if matches!(status, GcStatus::Uninitialized | GcStatus::PauseRequested) {
401 return None;
402 }
403 assert!(
404 matches!(status, GcStatus::NotInGC | GcStatus::InConcurrentGC),
405 "Trying to request a GC pause in invalid status: {:?}",
406 status
407 );
408 Some(Self::encode(GcStatus::PauseRequested))
409 }) {
410 Ok(_) => PauseRequestOutcome::Requested,
411 Err(bits) => match Self::decode(bits) {
412 GcStatus::Uninitialized => PauseRequestOutcome::Uninitialized,
413 GcStatus::PauseRequested => PauseRequestOutcome::AlreadyRequested,
414 status => unreachable!(
415 "fetch_update aborted the transition for an unexpected status: {:?}",
416 status
417 ),
418 },
419 }
420 }
421}
422
423#[cfg(test)]
424mod gc_status_tests {
425 use super::{GcStatus, GcStatusWord, PauseRequestOutcome};
426
427 #[test]
428 fn encode_decode_roundtrip() {
429 let statuses = [
430 GcStatus::Uninitialized,
431 GcStatus::NotInGC,
432 GcStatus::InConcurrentGC,
433 GcStatus::InPause,
434 GcStatus::PauseRequested,
435 ];
436 for status in statuses {
437 assert_eq!(GcStatusWord::decode(GcStatusWord::encode(status)), status);
438 }
439 }
440
441 #[test]
442 fn new_and_load_roundtrip() {
443 let statuses = [
444 GcStatus::Uninitialized,
445 GcStatus::NotInGC,
446 GcStatus::InConcurrentGC,
447 GcStatus::InPause,
448 GcStatus::PauseRequested,
449 ];
450 for status in statuses {
451 assert_eq!(GcStatusWord::new(status).load(), status);
452 }
453 }
454
455 #[test]
456 fn set_initialized_from_uninitialized() {
457 let word = GcStatusWord::new(GcStatus::Uninitialized);
458 assert!(!word.is_initialized());
459 word.set_initialized();
460 assert_eq!(word.load(), GcStatus::NotInGC);
461 assert!(word.is_initialized());
462 }
463
464 #[test]
465 #[should_panic(expected = "not uninitialized")]
466 fn set_initialized_panics_if_already_initialized() {
467 GcStatusWord::new(GcStatus::NotInGC).set_initialized();
468 }
469
470 #[test]
471 fn set_uninitialized_from_not_in_gc() {
472 let word = GcStatusWord::new(GcStatus::NotInGC);
473 word.set_uninitialized();
474 assert_eq!(word.load(), GcStatus::Uninitialized);
475 }
476
477 #[test]
478 #[should_panic(expected = "already uninitialized")]
479 fn set_uninitialized_panics_if_already_uninitialized() {
480 GcStatusWord::new(GcStatus::Uninitialized).set_uninitialized();
481 }
482
483 #[test]
484 fn try_request_pause_from_not_in_gc() {
485 let word = GcStatusWord::new(GcStatus::NotInGC);
486 assert_eq!(word.try_request_pause(), PauseRequestOutcome::Requested);
487 assert_eq!(word.load(), GcStatus::PauseRequested);
488 }
489
490 #[test]
491 fn try_request_pause_from_in_concurrent_gc() {
492 let word = GcStatusWord::new(GcStatus::InConcurrentGC);
493 assert_eq!(word.try_request_pause(), PauseRequestOutcome::Requested);
494 assert_eq!(word.load(), GcStatus::PauseRequested);
495 }
496
497 #[test]
498 fn try_request_pause_when_already_requested() {
499 let word = GcStatusWord::new(GcStatus::PauseRequested);
500 assert_eq!(
501 word.try_request_pause(),
502 PauseRequestOutcome::AlreadyRequested
503 );
504 assert_eq!(word.load(), GcStatus::PauseRequested);
506 }
507
508 #[test]
513 #[should_panic(expected = "invalid status")]
514 fn try_request_pause_panics_when_already_in_pause() {
515 GcStatusWord::new(GcStatus::InPause).try_request_pause();
516 }
517
518 #[test]
524 fn try_request_pause_when_uninitialized() {
525 let word = GcStatusWord::new(GcStatus::Uninitialized);
526 assert_eq!(word.try_request_pause(), PauseRequestOutcome::Uninitialized);
527 assert_eq!(word.load(), GcStatus::Uninitialized);
528 }
529
530 #[test]
531 fn set_in_pause_from_pause_requested() {
532 let word = GcStatusWord::new(GcStatus::PauseRequested);
533 word.set_in_pause();
534 assert_eq!(word.load(), GcStatus::InPause);
535 }
536
537 #[test]
538 #[should_panic(expected = "invalid status")]
539 fn set_in_pause_panics_if_not_requested() {
540 GcStatusWord::new(GcStatus::NotInGC).set_in_pause();
541 }
542
543 #[test]
544 fn set_in_concurrent_gc_from_in_pause() {
545 let word = GcStatusWord::new(GcStatus::InPause);
546 word.set_in_concurrent_gc();
547 assert_eq!(word.load(), GcStatus::InConcurrentGC);
548 }
549
550 #[test]
551 #[should_panic(expected = "invalid status")]
552 fn set_in_concurrent_gc_panics_if_not_in_pause() {
553 GcStatusWord::new(GcStatus::NotInGC).set_in_concurrent_gc();
554 }
555
556 #[test]
557 fn set_not_in_gc_from_in_pause() {
558 let word = GcStatusWord::new(GcStatus::InPause);
559 word.set_not_in_gc();
560 assert_eq!(word.load(), GcStatus::NotInGC);
561 }
562
563 #[test]
564 #[should_panic(expected = "invalid status")]
565 fn set_not_in_gc_panics_if_not_in_pause() {
566 GcStatusWord::new(GcStatus::InConcurrentGC).set_not_in_gc();
567 }
568}
569
570#[derive(Copy, Clone, Debug)]
572pub struct LiveBytesStats {
573 pub live_bytes: usize,
575 pub used_pages: usize,
577 pub used_bytes: usize,
580}