Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions gc/mmtk/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use mmtk::util::alloc::BumpPointer;
use mmtk::util::alloc::ImmixAllocator;
use mmtk::util::conversions;
use mmtk::util::options::PlanSelector;
use std::str::FromStr;
use std::sync::atomic::Ordering;
Expand All @@ -13,6 +14,8 @@ use crate::abi::RubyBindingOptions;
use crate::abi::RubyUpcalls;
use crate::binding;
use crate::binding::RubyBinding;
use crate::heap::RubyHeapTriggerConfig;
use crate::heap::RUBY_HEAP_TRIGGER_CONFIG;
use crate::mmtk;
use crate::utils::default_heap_max;
use crate::utils::parse_capacity;
Expand Down Expand Up @@ -79,13 +82,55 @@ fn mmtk_builder_default_parse_heap_max() -> usize {
parse_env_var_with("MMTK_HEAP_MAX", parse_capacity).unwrap_or_else(default_heap_max)
}

fn parse_float_env_var(key: &str, default: f64, min: f64, max: f64) -> f64 {
parse_env_var_with(key, |s| {
let mut float = f64::from_str(s).unwrap_or(default);

if float <= min {
eprintln!(
"{key} has value {float} which must be greater than {min}, using default instead"
);
float = default;
}

if float >= max {
eprintln!(
"{key} has value {float} which must be less than {max}, using default instead"
);
float = default;
}

Some(float)
})
.unwrap_or(default)
}

fn mmtk_builder_default_parse_heap_mode(heap_min: usize, heap_max: usize) -> GCTriggerSelector {
let make_fixed = || GCTriggerSelector::FixedHeapSize(heap_max);
let make_dynamic = || GCTriggerSelector::DynamicHeapSize(heap_min, heap_max);

parse_env_var_with("MMTK_HEAP_MODE", |s| match s {
"fixed" => Some(make_fixed()),
"dynamic" => Some(make_dynamic()),
"ruby" => {
let min_ratio = parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO", 0.2, 0.0, 1.0);
let goal_ratio =
parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO", 0.4, min_ratio, 1.0);
let max_ratio =
parse_float_env_var("RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO", 0.65, goal_ratio, 1.0);

crate::heap::RUBY_HEAP_TRIGGER_CONFIG
.set(RubyHeapTriggerConfig {
min_heap_pages: conversions::bytes_to_pages_up(heap_min),
max_heap_pages: conversions::bytes_to_pages_up(heap_max),
heap_pages_min_ratio: min_ratio,
heap_pages_goal_ratio: goal_ratio,
heap_pages_max_ratio: max_ratio,
})
.unwrap_or_else(|_| panic!("RUBY_HEAP_TRIGGER_CONFIG is already set"));

Some(GCTriggerSelector::Delegated)
}
_ => None,
})
.unwrap_or_else(make_dynamic)
Expand Down Expand Up @@ -146,7 +191,7 @@ pub unsafe extern "C" fn mmtk_init_binding(

crate::set_panic_hook();

let builder = unsafe { Box::from_raw(builder) };
let builder: Box<MMTKBuilder> = unsafe { Box::from_raw(builder) };
let binding_options = RubyBindingOptions {
ractor_check_mode: false,
suffix_size: 0,
Expand Down Expand Up @@ -388,11 +433,12 @@ pub extern "C" fn mmtk_plan() -> *const u8 {
pub extern "C" fn mmtk_heap_mode() -> *const u8 {
static FIXED_HEAP: &[u8] = b"fixed\0";
static DYNAMIC_HEAP: &[u8] = b"dynamic\0";
static RUBY_HEAP: &[u8] = b"ruby\0";

match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger {
GCTriggerSelector::FixedHeapSize(_) => FIXED_HEAP.as_ptr(),
GCTriggerSelector::DynamicHeapSize(_, _) => DYNAMIC_HEAP.as_ptr(),
_ => panic!("Unknown heap mode"),
GCTriggerSelector::Delegated => RUBY_HEAP.as_ptr(),
}
}

Expand All @@ -401,7 +447,12 @@ pub extern "C" fn mmtk_heap_min() -> usize {
match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger {
GCTriggerSelector::FixedHeapSize(_) => 0,
GCTriggerSelector::DynamicHeapSize(min_size, _) => min_size,
_ => panic!("Unknown heap mode"),
GCTriggerSelector::Delegated => conversions::pages_to_bytes(
RUBY_HEAP_TRIGGER_CONFIG
.get()
.expect("RUBY_HEAP_TRIGGER_CONFIG not set")
.min_heap_pages,
),
}
}

Expand All @@ -410,7 +461,12 @@ pub extern "C" fn mmtk_heap_max() -> usize {
match *crate::BINDING.get().unwrap().mmtk.get_options().gc_trigger {
GCTriggerSelector::FixedHeapSize(max_size) => max_size,
GCTriggerSelector::DynamicHeapSize(_, max_size) => max_size,
_ => panic!("Unknown heap mode"),
GCTriggerSelector::Delegated => conversions::pages_to_bytes(
RUBY_HEAP_TRIGGER_CONFIG
.get()
.expect("RUBY_HEAP_TRIGGER_CONFIG not set")
.max_heap_pages,
),
}
}

Expand Down
6 changes: 6 additions & 0 deletions gc/mmtk/src/collection.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::abi::GCThreadTLS;

use crate::api::RubyMutator;
use crate::heap::RubyHeapTrigger;
use crate::{mmtk, upcalls, Ruby};
use mmtk::memory_manager;
use mmtk::scheduler::*;
use mmtk::util::heap::GCTriggerPolicy;
use mmtk::util::{VMMutatorThread, VMThread, VMWorkerThread};
use mmtk::vm::{Collection, GCThreadContext};
use std::sync::atomic::Ordering;
Expand Down Expand Up @@ -67,6 +69,10 @@ impl Collection<Ruby> for VMCollection {
fn vm_live_bytes() -> usize {
(upcalls().vm_live_bytes)()
}

fn create_gc_trigger() -> Box<dyn GCTriggerPolicy<Ruby>> {
Box::new(RubyHeapTrigger::default())
}
}

impl VMCollection {
Expand Down
4 changes: 4 additions & 0 deletions gc/mmtk/src/heap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod ruby_heap_trigger;
pub use ruby_heap_trigger::RubyHeapTrigger;
pub use ruby_heap_trigger::RubyHeapTriggerConfig;
pub use ruby_heap_trigger::RUBY_HEAP_TRIGGER_CONFIG;
104 changes: 104 additions & 0 deletions gc/mmtk/src/heap/ruby_heap_trigger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use std::sync::atomic::{AtomicUsize, Ordering};

use mmtk::util::heap::GCTriggerPolicy;
use mmtk::util::heap::SpaceStats;
use mmtk::Plan;
use mmtk::MMTK;
use once_cell::sync::OnceCell;

use crate::Ruby;

pub static RUBY_HEAP_TRIGGER_CONFIG: OnceCell<RubyHeapTriggerConfig> = OnceCell::new();

pub struct RubyHeapTriggerConfig {
/// Min heap size
pub min_heap_pages: usize,
/// Max heap size
pub max_heap_pages: usize,
/// Minimum ratio of empty space after a GC before the heap will grow
pub heap_pages_min_ratio: f64,
/// Ratio the heap will grow by
pub heap_pages_goal_ratio: f64,
/// Maximum ratio of empty space after a GC before the heap will shrink
pub heap_pages_max_ratio: f64,
}

pub struct RubyHeapTrigger {
/// Target number of heap pages
target_heap_pages: AtomicUsize,
}

impl GCTriggerPolicy<Ruby> for RubyHeapTrigger {
fn is_gc_required(
&self,
space_full: bool,
space: Option<SpaceStats<Ruby>>,
plan: &dyn Plan<VM = Ruby>,
) -> bool {
// Let the plan decide
plan.collection_required(space_full, space)
}

fn on_gc_end(&self, mmtk: &'static MMTK<Ruby>) {
if let Some(plan) = mmtk.get_plan().generational() {
if plan.is_current_gc_nursery() {
// Nursery GC
} else {
// Full GC
}

panic!("TODO: support for generational GC not implemented")
} else {
let used_pages = mmtk.get_plan().get_used_pages();

let target_min =
(used_pages as f64 * (1.0 + Self::get_config().heap_pages_min_ratio)) as usize;
let target_max =
(used_pages as f64 * (1.0 + Self::get_config().heap_pages_max_ratio)) as usize;
let new_target =
(((used_pages as f64) * (1.0 + Self::get_config().heap_pages_goal_ratio)) as usize)
.clamp(
Self::get_config().min_heap_pages,
Self::get_config().max_heap_pages,
);

if used_pages < target_min || used_pages > target_max {
self.target_heap_pages.store(new_target, Ordering::Relaxed);
}
}
}

fn is_heap_full(&self, plan: &dyn Plan<VM = Ruby>) -> bool {
plan.get_reserved_pages() > self.target_heap_pages.load(Ordering::Relaxed)
}

fn get_current_heap_size_in_pages(&self) -> usize {
self.target_heap_pages.load(Ordering::Relaxed)
}

fn get_max_heap_size_in_pages(&self) -> usize {
Self::get_config().max_heap_pages
}

fn can_heap_size_grow(&self) -> bool {
self.target_heap_pages.load(Ordering::Relaxed) < Self::get_config().max_heap_pages
}
}

impl Default for RubyHeapTrigger {
fn default() -> Self {
let min_heap_pages = Self::get_config().min_heap_pages;

Self {
target_heap_pages: AtomicUsize::new(min_heap_pages),
}
}
}

impl RubyHeapTrigger {
fn get_config<'b>() -> &'b RubyHeapTriggerConfig {
RUBY_HEAP_TRIGGER_CONFIG
.get()
.expect("Attempt to use RUBY_HEAP_TRIGGER_CONFIG before it is initialized")
}
}
1 change: 1 addition & 0 deletions gc/mmtk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod active_plan;
pub mod api;
pub mod binding;
pub mod collection;
pub mod heap;
pub mod object_model;
pub mod reference_glue;
pub mod scanning;
Expand Down
2 changes: 1 addition & 1 deletion test/mmtk/test_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_MMTK_THREADS
end
end

%w(fixed dynamic).each do |heap|
%w(fixed dynamic ruby).each do |heap|
define_method(:"test_MMTK_HEAP_MODE_#{heap}") do
assert_separately([{ "MMTK_HEAP_MODE" => heap }], <<~RUBY)
assert_equal("#{heap}", GC.config[:mmtk_heap_mode])
Expand Down
Loading