Skip to content

Commit

Permalink
Move some states from BasePlan to GlobalState (#949)
Browse files Browse the repository at this point in the history
This PR moves some states from `BasePlan` to a new struct `GlobalState`.
Before this change, the states can only be accessed through `Plan`.
Whoever needs to access those states need to acquire or store a
reference to `Plan`, which is unnecessary. With the changes in the PR,
`GlobalState` is stored as `Arc<GlobalState>`. It can be directly
accessed through the `MMTK` instance, and can also be stored in some
components, such as allocators. This change reduces the dependency on
`Plan`.
  • Loading branch information
qinsoon authored Oct 24, 2023
1 parent eaa203e commit 57af17f
Show file tree
Hide file tree
Showing 53 changed files with 862 additions and 736 deletions.
3 changes: 0 additions & 3 deletions docs/header/mmtk.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ extern bool mmtk_is_mapped_object(void* ref);
// Return if the address pointed to by `addr` is in mapped memory
extern bool mmtk_is_mapped_address(void* addr);

// Check if a GC is in progress and if the object `ref` is movable
extern void mmtk_modify_check(void* ref);

// Return if object pointed to by `object` will never move
extern bool mmtk_will_never_move(void* object);

Expand Down
12 changes: 6 additions & 6 deletions docs/userguide/src/tutorial/code/mygc_semispace/mutator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// ANCHOR: imports
use super::MyGC; // Add
use crate::Plan;
use crate::MMTK;
use crate::plan::barriers::NoBarrier;
use crate::plan::mutator_context::Mutator;
use crate::plan::mutator_context::MutatorConfig;
Expand Down Expand Up @@ -67,17 +67,17 @@ lazy_static! {

pub fn create_mygc_mutator<VM: VMBinding>(
mutator_tls: VMMutatorThread,
plan: &'static dyn Plan<VM = VM>,
mmtk: &'static MMTK<VM>,
) -> Mutator<VM> {
// ANCHOR: plan_downcast
let mygc = plan.downcast_ref::<MyGC<VM>>().unwrap();
let mygc = mmtk.get_plan().downcast_ref::<MyGC<VM>>().unwrap();
// ANCHOR_END: plan_downcast
let config = MutatorConfig {
allocator_mapping: &*ALLOCATOR_MAPPING,
// Modify
// ANCHOR: space_mapping
space_mapping: Box::new({
let mut vec = create_space_mapping(RESERVED_ALLOCATORS, true, plan);
let mut vec = create_space_mapping(RESERVED_ALLOCATORS, true, mygc);
vec.push((AllocatorSelector::BumpPointer(0), mygc.tospace()));
vec
}),
Expand All @@ -87,10 +87,10 @@ pub fn create_mygc_mutator<VM: VMBinding>(
};

Mutator {
allocators: Allocators::<VM>::new(mutator_tls, plan, &config.space_mapping),
allocators: Allocators::<VM>::new(mutator_tls, mmtk, &config.space_mapping),
barrier: Box::new(NoBarrier),
mutator_tls,
config,
plan,
plan: mygc,
}
}
230 changes: 230 additions & 0 deletions src/global_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Mutex;

/// This stores some global states for an MMTK instance.
/// Some MMTK components like plans and allocators may keep an reference to the struct, and can access it.
// This used to be a part of the `BasePlan`. In that case, any component that accesses
// the states needs a reference to the plan. It makes it harder for us to reason about the access pattern
// for the plan, as many components hold references to the plan. Besides, the states
// actually are not related with a plan, they are just global states for MMTK. So we refactored
// those fields to this separate struct. For components that access the state, they just need
// a reference to the struct, and are no longer dependent on the plan.
// We may consider further break down the fields into smaller structs.
pub struct GlobalState {
/// Whether MMTk is now ready for collection. This is set to true when initialize_collection() is called.
pub(crate) initialized: AtomicBool,
/// Should we trigger a GC when the heap is full? It seems this should always be true. However, we allow
/// bindings to temporarily disable GC, at which point, we do not trigger GC even if the heap is full.
pub(crate) trigger_gc_when_heap_is_full: AtomicBool,
/// The current GC status.
pub(crate) gc_status: Mutex<GcStatus>,
/// Is the current GC an emergency collection? Emergency means we may run out of memory soon, and we should
/// attempt to collect as much as we can.
pub(crate) emergency_collection: AtomicBool,
/// Is the current GC triggered by the user?
pub(crate) user_triggered_collection: AtomicBool,
/// Is the current GC triggered internally by MMTK? This is unused for now. We may have internally triggered GC
/// for a concurrent plan.
pub(crate) internal_triggered_collection: AtomicBool,
/// Is the last GC internally triggered?
pub(crate) last_internal_triggered_collection: AtomicBool,
// Has an allocation succeeded since the emergency collection?
pub(crate) allocation_success: AtomicBool,
// Maximum number of failed attempts by a single thread
pub(crate) max_collection_attempts: AtomicUsize,
// Current collection attempt
pub(crate) cur_collection_attempts: AtomicUsize,
/// A counter for per-mutator stack scanning
pub(crate) scanned_stacks: AtomicUsize,
/// Have we scanned all the stacks?
pub(crate) stacks_prepared: AtomicBool,
/// A counter that keeps tracks of the number of bytes allocated since last stress test
pub(crate) allocation_bytes: AtomicUsize,
/// A counteer that keeps tracks of the number of bytes allocated by malloc
#[cfg(feature = "malloc_counted_size")]
pub(crate) malloc_bytes: AtomicUsize,
/// This stores the size in bytes for all the live objects in last GC. This counter is only updated in the GC release phase.
#[cfg(feature = "count_live_bytes_in_gc")]
pub(crate) live_bytes_in_last_gc: AtomicUsize,
}

impl GlobalState {
/// Is MMTk initialized?
pub fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::SeqCst)
}

/// Should MMTK trigger GC when heap is full? If GC is disabled, we wont trigger GC even if the heap is full.
pub fn should_trigger_gc_when_heap_is_full(&self) -> bool {
self.trigger_gc_when_heap_is_full.load(Ordering::SeqCst)
}

/// Set the collection kind for the current GC. This is called before
/// scheduling collection to determin what kind of collection it will be.
pub fn set_collection_kind(
&self,
last_collection_was_exhaustive: bool,
heap_can_grow: bool,
) -> bool {
self.cur_collection_attempts.store(
if self.user_triggered_collection.load(Ordering::Relaxed) {
1
} else {
self.determine_collection_attempts()
},
Ordering::Relaxed,
);

let emergency_collection = !self.is_internal_triggered_collection()
&& last_collection_was_exhaustive
&& self.cur_collection_attempts.load(Ordering::Relaxed) > 1
&& !heap_can_grow;
self.emergency_collection
.store(emergency_collection, Ordering::Relaxed);

emergency_collection
}

fn determine_collection_attempts(&self) -> usize {
if !self.allocation_success.load(Ordering::Relaxed) {
self.max_collection_attempts.fetch_add(1, Ordering::Relaxed);
} else {
self.allocation_success.store(false, Ordering::Relaxed);
self.max_collection_attempts.store(1, Ordering::Relaxed);
}

self.max_collection_attempts.load(Ordering::Relaxed)
}

fn is_internal_triggered_collection(&self) -> bool {
let is_internal_triggered = self
.last_internal_triggered_collection
.load(Ordering::SeqCst);
// Remove this assertion when we have concurrent GC.
assert!(
!is_internal_triggered,
"We have no concurrent GC implemented. We should not have internally triggered GC"
);
is_internal_triggered
}

pub fn is_emergency_collection(&self) -> bool {
self.emergency_collection.load(Ordering::Relaxed)
}

/// Return true if this collection was triggered by application code.
pub fn is_user_triggered_collection(&self) -> bool {
self.user_triggered_collection.load(Ordering::Relaxed)
}

/// Reset collection state information.
pub fn reset_collection_trigger(&self) {
self.last_internal_triggered_collection.store(
self.internal_triggered_collection.load(Ordering::SeqCst),
Ordering::Relaxed,
);
self.internal_triggered_collection
.store(false, Ordering::SeqCst);
self.user_triggered_collection
.store(false, Ordering::Relaxed);
}

/// Are the stacks scanned?
pub fn stacks_prepared(&self) -> bool {
self.stacks_prepared.load(Ordering::SeqCst)
}

/// Prepare for stack scanning. This is usually used with `inform_stack_scanned()`.
/// This should be called before doing stack scanning.
pub fn prepare_for_stack_scanning(&self) {
self.scanned_stacks.store(0, Ordering::SeqCst);
self.stacks_prepared.store(false, Ordering::SeqCst);
}

/// Inform that 1 stack has been scanned. The argument `n_mutators` indicates the
/// total stacks we should scan. This method returns true if the number of scanned
/// stacks equals the total mutator count. Otherwise it returns false. This method
/// is thread safe and we guarantee only one thread will return true.
pub fn inform_stack_scanned(&self, n_mutators: usize) -> bool {
let old = self.scanned_stacks.fetch_add(1, Ordering::SeqCst);
debug_assert!(
old < n_mutators,
"The number of scanned stacks ({}) is more than the number of mutators ({})",
old,
n_mutators
);
let scanning_done = old + 1 == n_mutators;
if scanning_done {
self.stacks_prepared.store(true, Ordering::SeqCst);
}
scanning_done
}

/// Increase the allocation bytes and return the current allocation bytes after increasing
pub fn increase_allocation_bytes_by(&self, size: usize) -> usize {
let old_allocation_bytes = self.allocation_bytes.fetch_add(size, Ordering::SeqCst);
trace!(
"Stress GC: old_allocation_bytes = {}, size = {}, allocation_bytes = {}",
old_allocation_bytes,
size,
self.allocation_bytes.load(Ordering::Relaxed),
);
old_allocation_bytes + size
}

#[cfg(feature = "malloc_counted_size")]
pub fn get_malloc_bytes_in_pages(&self) -> usize {
crate::util::conversions::bytes_to_pages_up(self.malloc_bytes.load(Ordering::Relaxed))
}

#[cfg(feature = "malloc_counted_size")]
pub(crate) fn increase_malloc_bytes_by(&self, size: usize) {
self.malloc_bytes.fetch_add(size, Ordering::SeqCst);
}

#[cfg(feature = "malloc_counted_size")]
pub(crate) fn decrease_malloc_bytes_by(&self, size: usize) {
self.malloc_bytes.fetch_sub(size, Ordering::SeqCst);
}

#[cfg(feature = "count_live_bytes_in_gc")]
pub fn get_live_bytes_in_last_gc(&self) -> usize {
self.live_bytes_in_last_gc.load(Ordering::SeqCst)
}

#[cfg(feature = "count_live_bytes_in_gc")]
pub fn set_live_bytes_in_last_gc(&self, size: usize) {
self.live_bytes_in_last_gc.store(size, Ordering::SeqCst);
}
}

impl Default for GlobalState {
fn default() -> Self {
Self {
initialized: AtomicBool::new(false),
trigger_gc_when_heap_is_full: AtomicBool::new(true),
gc_status: Mutex::new(GcStatus::NotInGC),
stacks_prepared: AtomicBool::new(false),
emergency_collection: AtomicBool::new(false),
user_triggered_collection: AtomicBool::new(false),
internal_triggered_collection: AtomicBool::new(false),
last_internal_triggered_collection: AtomicBool::new(false),
allocation_success: AtomicBool::new(false),
max_collection_attempts: AtomicUsize::new(0),
cur_collection_attempts: AtomicUsize::new(0),
scanned_stacks: AtomicUsize::new(0),
allocation_bytes: AtomicUsize::new(0),
#[cfg(feature = "malloc_counted_size")]
malloc_bytes: AtomicUsize::new(0),
#[cfg(feature = "count_live_bytes_in_gc")]
live_bytes_in_last_gc: AtomicUsize::new(0),
}
}
}

#[derive(PartialEq)]
pub enum GcStatus {
NotInGC,
GcPrepare,
GcProper,
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub use mmtk::MMTKBuilder;
pub(crate) use mmtk::MMAPPER;
pub use mmtk::MMTK;

mod global_state;

mod policy;

pub mod build_info;
Expand Down
Loading

0 comments on commit 57af17f

Please sign in to comment.