Fix various GC bugs and improve GC performance

This fixes various Immix related bugs and makes some improvements to the
collector's performance.

Bug fixes
---------

The following bugs have been fixed:

1. Evacuating required one or more fragmented blocks, which were set
   based on histogram statistics. These statistics were only populated
   when there were one or more fragmented blocks. This resulted in
   evacuating never happening.

   Evacuating now happens if the previous collection increased the
   allocation threshold.

2. The fragmentation status of a block was never cleared after a
   collection. This could result in an increase of fragmentation if the
   collector statistics deemed evacuating to not be needed.

3. The remembered set using a HashSet meant that updating forward
   pointers in the remembered set would result in duplicate entries, as
   the hash codes would be different.

4. Objects promoted to the mature generation would not be remembered if
   they contained any young pointers not promoted. We now trace mature
   objects to check if they need to be remembered, instead of always
   remembering them; drastically reducing the size of the remembered
   set.

Improvements
------------

1. The index to the eden space has been changed to a u8, allowing us to
   add some additional fields without growing the size of a Bucket.

2. We no longer update line maps of mature blocks when performing a
   young collection. This is not needed in a young collection.

3. We now use a chunked list for the remembered set and an additional
   object bit to prevent duplicate entries. This makes the remembered
   set more memory efficient, and removes the need for hashing. This
   does require the use of a 64-bits architecture, but Inko never really
   officially supported 32-bits anyway.

4. Objects with a single hole are not taken into account when
   calculating fragmentation statuses. This way we don't overestimate
   the number of free lines as much, reducing the amount of evacuating
   objects.

5. Messages are now copied into a process' local heap directly, instead
   of being copied into a separate mailbox heap. This reduces memory
   needed when sending messages, overall memory usage, and quite
   drastically simplifies the mailbox and garbage collection
   implementation. Access to the mailbox is synchronised using a
   spinlock, which is fast enough to be used by a mutator.

6. Various garbage collection timers (e.g. the time spent preparing a
   collection) that weren't very useful have been removed.

7. Parallel tracing performance has been improved by tracing objects in
   parallel, instead of only processing stack frames in parallel. When
   moving of objects is not needed this can significantly reduce garbage
   collection timings.

8. The INKO_CONCURRENCY variable has been replaced with separate
   variables, as greater control is needed over some of the thread pool
   settings.

9. GC timings can now be printed when setting INKO_PRINT_GC_TIMINGS to
   "true". This is an internal option and will only stick around until
   we have a better way of aggregating and presenting VM/GC statistics.

10. Updating of garbage collection statistics at the end of a collection
    has been improved a bit, removing the need for counting the total
    number of blocks separately. The performance improvement of this is
    minor, but it does clean up the code quite a bit.
parent cb17da33
......@@ -30,7 +30,7 @@ website](https://inko-lang.org/about/).
Inko officially supports Linux, Mac OS, and Windows (when compiled with a MingW
toolchain such as [MSYS2](http://www.msys2.org/)). Other Unix-like platforms
such as the various BSDs should also work, but are not officially supported at
this time.
this time. Inko only supports 64-bits architectures.
## Requirements
......
......@@ -49,7 +49,7 @@ dependencies = [
[[package]]
name = "autocfg"
version = "0.1.5"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
......@@ -176,15 +176,6 @@ dependencies = [
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.7.1"
......@@ -234,11 +225,6 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "env_logger"
version = "0.6.2"
......@@ -346,7 +332,6 @@ dependencies = [
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"siphasher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -488,7 +473,7 @@ name = "num-integer"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -497,7 +482,7 @@ name = "num-traits"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
......@@ -585,7 +570,7 @@ name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -603,7 +588,7 @@ name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -664,7 +649,7 @@ name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -676,28 +661,6 @@ dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rdrand"
version = "0.4.0"
......@@ -979,7 +942,7 @@ dependencies = [
"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)" = "88fb679bc9af8fa639198790a77f52d345fe13656c08b43afa9424c206b731c6"
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
"checksum bindgen 0.49.2 (registry+https://github.com/rust-lang/crates.io-index)" = "846a1fba6535362a01487ef6b10f0275faa12e5c5d835c5c1c627aabc46ccbd6"
......@@ -994,13 +957,11 @@ dependencies = [
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
"checksum crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "05e44b8cf3e1a625844d1750e1f7820da46044ff6d28f4d43e455ba3e5bb2c13"
"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71"
"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
......@@ -1050,8 +1011,6 @@ dependencies = [
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
"checksum rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4b0186e22767d5b9738a05eab7c6ac90b15db17e5b5f9bd87976dd7d89a10a4"
"checksum rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe0df8435ac0c397d467b6cad6d25543d06e8a019ef3f6af3c384597515bd2"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828"
......
......@@ -3,6 +3,7 @@ name = "inko"
version = "0.5.0" # VERSION
authors = ["Yorick Peterse <[email protected]>"]
edition = "2018"
build = "build.rs"
[features]
default = []
......@@ -19,7 +20,6 @@ test = false
[dependencies]
getopts = "^0.2"
num_cpus = "^1.10"
rayon = "^1.0"
parking_lot = "^0.7"
fnv = "^1.0"
time = "^0.1"
......
fn main() {
if !cfg!(target_arch = "x86_64") {
panic!("The Inko virtual machine requires a 64-bits architecture");
}
}
......@@ -4,9 +4,8 @@
use crate::arc_without_weak::ArcWithoutWeak;
use crate::block::Block;
use crate::chunk::Chunk;
use crate::gc::work_list::WorkList;
use crate::immix::copy_object::CopyObject;
use crate::object_pointer::ObjectPointer;
use crate::object_pointer::{ObjectPointer, ObjectPointerPointer};
use std::cell::UnsafeCell;
pub struct Binding {
......@@ -92,18 +91,20 @@ impl Binding {
unsafe { &mut *self.locals.get() }
}
/// Pushes all pointers in this binding into the supplied vector.
pub fn push_pointers(&self, pointers: &mut WorkList) {
pub fn each_pointer<F>(&self, mut callback: F)
where
F: FnMut(ObjectPointerPointer),
{
let mut current = Some(self);
while let Some(binding) = current {
pointers.push(binding.receiver.pointer());
callback(binding.receiver.pointer());
for index in 0..binding.locals().len() {
let local = &binding.locals()[index];
if !local.is_null() {
pointers.push(local.pointer());
callback(local.pointer());
}
}
......@@ -139,34 +140,12 @@ impl Binding {
parent,
})
}
// Moves all pointers in this binding to the given heap.
pub fn move_pointers_to<H: CopyObject>(&mut self, heap: &mut H) {
if let Some(ref mut bind) = self.parent {
bind.move_pointers_to(heap);
}
{
let locals = self.locals_mut();
for index in 0..locals.len() {
let pointer = locals[index];
if !pointer.is_null() {
locals[index] = heap.move_object(pointer);
}
}
}
self.receiver = heap.move_object(self.receiver);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::gc::work_list::WorkList;
use crate::immix::global_allocator::GlobalAllocator;
use crate::immix::local_allocator::LocalAllocator;
use crate::object_pointer::ObjectPointer;
......@@ -295,7 +274,7 @@ mod tests {
}
#[test]
fn test_push_pointers() {
fn test_each_pointer() {
let mut alloc =
LocalAllocator::new(GlobalAllocator::with_rc(), &Config::new());
......@@ -311,27 +290,29 @@ mod tests {
binding2.parent = Some(binding1.clone());
binding2.set_local(0, local2);
let mut pointers = WorkList::new();
let mut pointer_pointers = Vec::new();
binding2.push_pointers(&mut pointers);
binding2.each_pointer(|ptr| pointer_pointers.push(ptr));
assert!(*pointers.pop().unwrap().get() == receiver);
assert!(*pointers.pop().unwrap().get() == local2);
let pointers: Vec<_> =
pointer_pointers.into_iter().map(|x| *x.get()).collect();
assert!(*pointers.pop().unwrap().get() == receiver);
assert!(*pointers.pop().unwrap().get() == local1);
assert_eq!(pointers.iter().filter(|x| **x == receiver).count(), 2);
assert!(pointers.contains(&local2));
assert!(pointers.contains(&local1));
}
#[test]
fn test_push_pointers_and_update() {
fn test_each_pointer_and_update() {
let mut alloc =
LocalAllocator::new(GlobalAllocator::with_rc(), &Config::new());
let mut binding = Binding::with_rc(1, alloc.allocate_empty());
let mut pointers = WorkList::new();
let mut pointers = Vec::new();
binding.set_local(0, alloc.allocate_empty());
binding.push_pointers(&mut pointers);
binding.each_pointer(|ptr| pointers.push(ptr));
while let Some(pointer_pointer) = pointers.pop() {
let pointer = pointer_pointer.get_mut();
......@@ -377,34 +358,6 @@ mod tests {
assert_eq!(bind_copy_parent.receiver.float_value().unwrap(), 8.0);
}
#[test]
fn test_move_pointers_to() {
let galloc = GlobalAllocator::with_rc();
let mut alloc1 = LocalAllocator::new(galloc.clone(), &Config::new());
let mut alloc2 = LocalAllocator::new(galloc, &Config::new());
let ptr1 = alloc1.allocate_without_prototype(object_value::float(5.0));
let ptr2 = alloc1.allocate_without_prototype(object_value::float(2.0));
let ptr3 = alloc1.allocate_without_prototype(object_value::float(8.0));
let mut src_bind1 = Binding::with_rc(1, ptr3);
let mut src_bind2 = Binding::with_rc(1, ptr3);
src_bind2.parent = Some(src_bind1.clone());
src_bind1.set_local(0, ptr1);
src_bind2.set_local(0, ptr2);
src_bind2.move_pointers_to(&mut alloc2);
// The original pointers now point to empty objects.
assert!(ptr1.get().value.is_none());
assert!(ptr2.get().value.is_none());
assert!(ptr3.get().value.is_none());
assert_eq!(src_bind2.get_local(0).float_value().unwrap(), 2.0);
assert_eq!(src_bind1.get_local(0).float_value().unwrap(), 5.0);
assert_eq!(src_bind1.receiver.float_value().unwrap(), 8.0);
}
#[test]
fn test_receiver_with_receiver() {
let pointer = ObjectPointer::integer(5);
......
......@@ -8,6 +8,7 @@
//! of the VM to easily access these configuration details.
use crate::immix::block::BLOCK_SIZE;
use num_cpus;
use std::cmp::max;
use std::env;
use std::path::PathBuf;
......@@ -24,7 +25,6 @@ macro_rules! set_from_env {
const DEFAULT_YOUNG_THRESHOLD: u32 = (8 * 1024 * 1024) / (BLOCK_SIZE as u32);
const DEFAULT_MATURE_THRESHOLD: u32 = (16 * 1024 * 1024) / (BLOCK_SIZE as u32);
const DEFAULT_MAILBOX_THRESHOLD: u32 = 1;
const DEFAULT_GROWTH_FACTOR: f64 = 1.5;
const DEFAULT_GROWTH_THRESHOLD: f64 = 0.9;
const DEFAULT_REDUCTIONS: usize = 1000;
......@@ -35,17 +35,25 @@ pub struct Config {
pub directories: Vec<PathBuf>,
/// The number of primary process threads to run.
///
/// This defaults to the number of CPU cores.
pub primary_threads: usize,
/// The number of blocking process threads to run.
///
/// This defaults to the number of CPU cores.
pub blocking_threads: usize,
/// The number of garbage collector threads to run.
///
/// This defaults to half the number of CPU cores, with a minimum of two.
pub gc_threads: usize,
/// The number of threads to use for various generic parallel tasks such as
/// scanning stack frames during garbage collection.
pub generic_parallel_threads: usize,
/// The number of threads to run for tracing objects during garbage
/// collection.
///
/// This defaults to half the number of CPU cores, with a minimum of two.
pub tracer_threads: usize,
/// The number of reductions a process can perform before being suspended.
/// Defaults to 1000.
......@@ -66,36 +74,34 @@ pub struct Config {
/// should be used before increasing the heap size.
pub heap_growth_threshold: f64,
/// The number of memory blocks that can be allocated before triggering a
/// mailbox collection.
pub mailbox_threshold: u32,
/// The block allocation growth factor for the mailbox heap.
pub mailbox_growth_factor: f64,
/// The percentage of memory in the mailbox heap that should be used before
/// increasing the size.
pub mailbox_growth_threshold: f64,
/// When enabled, GC timings will be printed to STDERR.
pub print_gc_timings: bool,
}
impl Config {
pub fn new() -> Config {
let cpu_count = num_cpus::get();
// GC threads may consume a lot of CPU as they trace through objects. If
// we have N cores and spawn N threads, we would end up with many
// threads competing over CPU time.
//
// To keep this somewhat under control we limit both GC control and
// tracer threads to half the number of CPU cores.
let cpu_half = max(cpu_count / 2, 1);
Config {
directories: Vec::new(),
primary_threads: cpu_count,
gc_threads: cpu_count,
blocking_threads: cpu_count,
generic_parallel_threads: cpu_count,
gc_threads: cpu_half,
tracer_threads: cpu_half,
reductions: DEFAULT_REDUCTIONS,
young_threshold: DEFAULT_YOUNG_THRESHOLD,
mature_threshold: DEFAULT_MATURE_THRESHOLD,
heap_growth_factor: DEFAULT_GROWTH_FACTOR,
heap_growth_threshold: DEFAULT_GROWTH_THRESHOLD,
mailbox_threshold: DEFAULT_MAILBOX_THRESHOLD,
mailbox_growth_factor: DEFAULT_GROWTH_FACTOR,
mailbox_growth_threshold: DEFAULT_GROWTH_THRESHOLD,
print_gc_timings: false,
}
}
......@@ -105,10 +111,10 @@ impl Config {
allow(cyclomatic_complexity, cognitive_complexity)
)]
pub fn populate_from_env(&mut self) {
set_from_env!(self, primary_threads, "CONCURRENCY", usize);
set_from_env!(self, blocking_threads, "CONCURRENCY", usize);
set_from_env!(self, gc_threads, "CONCURRENCY", usize);
set_from_env!(self, generic_parallel_threads, "CONCURRENCY", usize);
set_from_env!(self, primary_threads, "PRIMARY_THREADS", usize);
set_from_env!(self, blocking_threads, "BLOCKING_THREADS", usize);
set_from_env!(self, gc_threads, "GC_THREADS", usize);
set_from_env!(self, tracer_threads, "TRACER_THREADS", usize);
set_from_env!(self, reductions, "REDUCTIONS", usize);
......@@ -123,21 +129,7 @@ impl Config {
f64
);
set_from_env!(self, mailbox_threshold, "MAILBOX_THRESHOLD", u32);
set_from_env!(
self,
mailbox_growth_factor,
"MAILBOX_GROWTH_FACTOR",
f64
);
set_from_env!(
self,
mailbox_growth_threshold,
"MAILBOX_GROWTH_THRESHOLD",
f64
);
set_from_env!(self, print_gc_timings, "PRINT_GC_TIMINGS", bool);
}
pub fn add_directory(&mut self, path: String) {
......@@ -162,7 +154,7 @@ mod tests {
#[test]
fn test_populate_from_env() {
env::set_var("INKO_CONCURRENCY", "42");
env::set_var("INKO_PRIMARY_THREADS", "42");
env::set_var("INKO_HEAP_GROWTH_FACTOR", "4.2");
let mut config = Config::new();
......@@ -170,7 +162,6 @@ mod tests {
config.populate_from_env();
// Unset before any assertions may fail.
env::remove_var("INKO_CONCURRENCY");
env::remove_var("INKO_HEAP_GROWTH_FACTOR");
assert_eq!(config.primary_threads, 42);
......
......@@ -5,9 +5,8 @@
use crate::binding::{Binding, RcBinding};
use crate::block::Block;
use crate::compiled_code::CompiledCodePointer;
use crate::gc::work_list::WorkList;
use crate::global_scope::GlobalScopePointer;
use crate::object_pointer::ObjectPointer;
use crate::object_pointer::{ObjectPointer, ObjectPointerPointer};
use crate::process::RcProcess;
use crate::register::Register;
......@@ -168,24 +167,16 @@ impl ExecutionContext {
}
}
/// Returns pointers to all pointers stored in this context.
pub fn pointers(&self) -> WorkList {
let mut pointers = WorkList::new();
// We don't use chain() here since it may perform worse than separate
// for loops, and we want the garbage collector (which calls this
// method) to be as fast as possible.
//
// See https://github.com/rust-lang/rust/issues/38038 for more
// information.
self.binding.push_pointers(&mut pointers);
self.register.push_pointers(&mut pointers);
pub fn each_pointer<F>(&self, mut callback: F)
where
F: FnMut(ObjectPointerPointer),
{
self.binding.each_pointer(|ptr| callback(ptr));
self.register.each_pointer(|ptr| callback(ptr));
for pointer in &self.deferred_blocks {
pointers.push(pointer.pointer());
callback(pointer.pointer());
}
pointers
}
/// Returns the top-most parent binding of the current binding.
......@@ -376,7 +367,7 @@ mod tests {
}
#[test]
fn test_pointers() {
fn test_each_pointer() {
let (_machine, block, _) = setup();
let mut context = ExecutionContext::from_block(&block, None);
let pointer = ObjectPointer::new(0x1 as RawObjectPointer);
......@@ -386,11 +377,18 @@ mod tests {
context.binding.set_local(0, pointer);
context.add_defer(deferred);
let mut pointers = context.pointers();
let mut pointer_pointers = Vec::new();
context.each_pointer(|ptr| pointer_pointers.push(ptr));
let pointers: Vec<_> =
pointer_pointers.into_iter().map(|x| *x.get()).collect();
assert_eq!(pointers.len(), 4);
assert_eq!(pointers.iter().filter(|x| **x == pointer).count(), 2);
assert!(pointers.pop().is_some());
assert!(pointers.pop().is_some());
assert!(pointers.pop().is_some());
assert!(pointers.contains(&context.binding.receiver));
assert!(pointers.contains(&deferred));
}
#[test]
......
//! Functions and macros for performing garbage collection.
use crate::gc::trace_result::TraceResult;
use crate::gc::work_list::WorkList;
use crate::object::ObjectStatus;
use crate::object_pointer::ObjectPointer;
use crate::process::RcProcess;
/// Macro that returns true if the pointer can be skipped during tracing.
macro_rules! can_skip_pointer {
($pointer:expr, $mature:expr) => {
$pointer.is_marked() || !$mature && $pointer.is_mature()
};
}
/// Promotes an object to the mature generation.
///
/// The pointer to promote is updated to point to the new location.
pub fn promote_mature(process: &RcProcess, pointer: &mut ObjectPointer) {
{
let local_data = process.local_data_mut();
let old_obj = pointer.get_mut();
let new_pointer = local_data.allocator.allocate_mature(old_obj.take());
old_obj.forward_to(new_pointer);
}
pointer.resolve_forwarding_pointer();
}
// Evacuates a pointer.
//
// The pointer to evacuate is updated to point to the new location.
pub fn evacuate(process: &RcProcess, pointer: &mut ObjectPointer) {
{
// When evacuating an object we must ensure we evacuate the object into
// the same bucket.
let local_data = process.local_data_mut();
let bucket = pointer.block_mut().bucket_mut().unwrap();
let old_obj = pointer.get_mut();
let new_obj = old_obj.take();
let (_, new_pointer) =
bucket.allocate(&local_data.allocator.global_allocator, new_obj);
old_obj.forward_to(new_pointer);
}
pointer.resolve_forwarding_pointer();
}
/// Traces through the given pointers, and potentially moves objects around.
pub fn trace_pointers_with_moving(
process: &RcProcess,
mut objects: WorkList,
mature: bool,
) -> TraceResult {
let mut marked = 0;
let mut evacuated = 0;
let mut promoted = 0;
while let Some(pointer_pointer) = objects.pop() {
let pointer = pointer_pointer.get_mut();
if can_skip_pointer!(pointer, mature) {
continue;
}
match pointer.status() {
ObjectStatus::Resolve => pointer.resolve_forwarding_pointer(),
ObjectStatus::Promote => {
promote_mature(process, pointer);
promoted += 1;
}
ObjectStatus::Evacuate => {
evacuate(process, pointer);
evacuated += 1;
}
ObjectStatus::PendingMove => {
objects.push(pointer_pointer.clone());
continue;
}
_ => {}
}
pointer.mark();
marked += 1;
pointer.get().push_pointers(&mut objects);
}
TraceResult::with(marked, evacuated, promoted)
}
/// Traces through the roots and all their child pointers, without moving
/// objects around.
pub fn trace_pointers_without_moving(
mut objects: WorkList,
mature: bool,
) -> TraceResult {
let mut marked = 0;
while let Some(pointer_pointer) = objects.pop() {
let pointer = pointer_pointer.get();
if can_skip_pointer!(pointer, mature) {
continue;
}
pointer.mark();
marked += 1;
pointer.get().push_pointers(&mut objects);