Skip to content

Commit

Permalink
feat(engine): Implement interleaved garbage collection (#443)
Browse files Browse the repository at this point in the history
An interleaved, exact, stop-the-world GC implementation.

At present the GC is (mostly?) memory safe but not very reference-correct or useful. The collection is just "after every 256 instructions in the same bytecode" and the rest of the engine has no lifetime hints about when it's safe or unsafe to retain JS Values.
  • Loading branch information
aapoalas authored Oct 18, 2024
1 parent ab64d9c commit fb83620
Show file tree
Hide file tree
Showing 60 changed files with 4,374 additions and 3,478 deletions.
11 changes: 6 additions & 5 deletions nova_vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ wtf8 = { workspace = true }

[features]
default = ["math", "json", "date", "array-buffer", "shared-array-buffer", "weak-refs", "atomics"]
math = []
json = ["sonic-rs"]
date = []
array-buffer = []
shared-array-buffer = []
weak-refs = []
atomics = ["array-buffer", "shared-array-buffer"]
date = []
interleaved-gc = []
json = ["sonic-rs"]
math = []
shared-array-buffer = []
typescript = []
weak-refs = []

[build-dependencies]
small_string = { path = "../small_string" }
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,22 @@ pub(crate) fn iterator_to_list(

impl HeapMarkAndSweep for IteratorRecord {
fn mark_values(&self, queues: &mut WorkQueues) {
self.iterator.mark_values(queues);
self.next_method.mark_values(queues);
let Self {
iterator,
next_method,
done: _,
} = self;
iterator.mark_values(queues);
next_method.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.iterator.sweep_values(compactions);
self.next_method.sweep_values(compactions);
let Self {
iterator,
next_method,
done: _,
} = self;
iterator.sweep_values(compactions);
next_method.sweep_values(compactions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,6 @@ pub(crate) fn initialize_instance_elements(
// To do this, we need a new execution context that points to a new
// Function environment. The function environment should be lexically a
// child of the class constructor's creating environment.
let bytecode = unsafe { bytecode.as_ref() };
let f = constructor.into_function();
let outer_env = constructor_data.environment;
let outer_priv_env = constructor_data.private_environment;
Expand Down
22 changes: 14 additions & 8 deletions nova_vm/src/ecmascript/builtins/array/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,27 +127,33 @@ pub struct ArrayHeapData {

impl HeapMarkAndSweep for SealableElementsVector {
fn mark_values(&self, queues: &mut WorkQueues) {
let item = *self;
let elements: ElementsVector = item.into();
let elements: ElementsVector = (*self).into();
elements.mark_values(queues)
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
let item = *self;
let mut elements: ElementsVector = item.into();
let mut elements: ElementsVector = (*self).into();
elements.sweep_values(compactions);
self.elements_index = elements.elements_index;
}
}

impl HeapMarkAndSweep for ArrayHeapData {
fn mark_values(&self, queues: &mut WorkQueues) {
self.object_index.mark_values(queues);
self.elements.mark_values(queues);
let Self {
object_index,
elements,
} = self;
object_index.mark_values(queues);
elements.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.object_index.sweep_values(compactions);
self.elements.sweep_values(compactions);
let Self {
object_index,
elements,
} = self;
object_index.sweep_values(compactions);
elements.sweep_values(compactions);
}
}
12 changes: 10 additions & 2 deletions nova_vm/src/ecmascript/builtins/array_buffer/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,18 @@ impl ArrayBufferHeapData {

impl HeapMarkAndSweep for ArrayBufferHeapData {
fn mark_values(&self, queues: &mut WorkQueues) {
self.object_index.mark_values(queues);
let Self {
object_index,
buffer: _,
} = self;
object_index.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.object_index.sweep_values(compactions);
let Self {
object_index,
buffer: _,
} = self;
object_index.sweep_values(compactions);
}
}
36 changes: 26 additions & 10 deletions nova_vm/src/ecmascript/builtins/bound_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,34 @@ impl HeapMarkAndSweep for BoundFunction {

impl HeapMarkAndSweep for BoundFunctionHeapData {
fn mark_values(&self, queues: &mut WorkQueues) {
self.name.mark_values(queues);
self.bound_target_function.mark_values(queues);
self.object_index.mark_values(queues);
self.bound_this.mark_values(queues);
self.bound_arguments.mark_values(queues);
let Self {
object_index,
length: _,
bound_target_function,
bound_this,
bound_arguments,
name,
} = self;
name.mark_values(queues);
bound_target_function.mark_values(queues);
object_index.mark_values(queues);
bound_this.mark_values(queues);
bound_arguments.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.name.sweep_values(compactions);
self.bound_target_function.sweep_values(compactions);
self.object_index.sweep_values(compactions);
self.bound_this.sweep_values(compactions);
self.bound_arguments.sweep_values(compactions);
let Self {
object_index,
length: _,
bound_target_function,
bound_this,
bound_arguments,
name,
} = self;
name.sweep_values(compactions);
bound_target_function.sweep_values(compactions);
object_index.sweep_values(compactions);
bound_this.sweep_values(compactions);
bound_arguments.sweep_values(compactions);
}
}
71 changes: 35 additions & 36 deletions nova_vm/src/ecmascript/builtins/builtin_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::{
ops::{Index, IndexMut},
ptr::NonNull,
};
use std::ops::{Index, IndexMut};

use oxc_span::Span;

Expand Down Expand Up @@ -339,7 +336,7 @@ pub(crate) struct BuiltinConstructorArgs {
pub(crate) class_name: String,
pub(crate) prototype: Option<Object>,
pub(crate) prototype_property: Object,
pub(crate) compiled_initializer_bytecode: Option<Box<Executable>>,
pub(crate) compiled_initializer_bytecode: Option<Executable>,
pub(crate) env: EnvironmentIndex,
pub(crate) private_env: Option<PrivateEnvironmentIndex>,
pub(crate) source_code: SourceCode,
Expand Down Expand Up @@ -414,17 +411,13 @@ pub(crate) fn create_builtin_constructor(
agent.heap.create_null_object(&entries)
};

let compiled_initializer_bytecode = args
.compiled_initializer_bytecode
.map(|bytecode| NonNull::from(Box::leak(bytecode)));

// 13. Return func.
agent.heap.create(BuiltinConstructorHeapData {
// 10. Perform SetFunctionLength(func, length).
// Skipped as length of builtin constructors is always 0.
// 8. Set func.[[Realm]] to realm.
realm,
compiled_initializer_bytecode,
compiled_initializer_bytecode: args.compiled_initializer_bytecode,
is_derived: args.is_derived,
object_index: Some(backing_object),
environment: args.env,
Expand Down Expand Up @@ -453,34 +446,40 @@ impl HeapMarkAndSweep for BuiltinConstructorFunction {

impl HeapMarkAndSweep for BuiltinConstructorHeapData {
fn mark_values(&self, queues: &mut WorkQueues) {
self.realm.mark_values(queues);
self.object_index.mark_values(queues);
self.environment.mark_values(queues);
self.private_environment.mark_values(queues);
self.source_code.mark_values(queues);
if let Some(exe) = &self.compiled_initializer_bytecode {
// SAFETY: This is a valid, non-null pointer to an owned Executable
// that cannot have any live mutable references to it.
unsafe { exe.as_ref() }.mark_values(queues);
}
let Self {
object_index,
realm,
is_derived: _,
compiled_initializer_bytecode,
environment,
private_environment,
source_text: _,
source_code,
} = self;
realm.mark_values(queues);
object_index.mark_values(queues);
environment.mark_values(queues);
private_environment.mark_values(queues);
source_code.mark_values(queues);
compiled_initializer_bytecode.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.realm.sweep_values(compactions);
self.object_index.sweep_values(compactions);
self.environment.sweep_values(compactions);
self.private_environment.sweep_values(compactions);
self.source_code.sweep_values(compactions);
if let Some(exe) = &mut self.compiled_initializer_bytecode {
// SAFETY: This is a valid, non-null pointer to an owned Executable
// that cannot have any live references to it.
// References to this Executable are only created above for marking
// and in function_definition for running the function. Both of the
// references only live for the duration of a synchronous call and
// no longer. Sweeping cannot run concurrently with marking or with
// ECMAScript code execution. Hence we can be sure that this is not
// an aliasing violation.
unsafe { exe.as_mut() }.sweep_values(compactions);
}
let Self {
object_index,
realm,
is_derived: _,
compiled_initializer_bytecode,
environment,
private_environment,
source_text: _,
source_code,
} = self;
realm.sweep_values(compactions);
object_index.sweep_values(compactions);
environment.sweep_values(compactions);
private_environment.sweep_values(compactions);
source_code.sweep_values(compactions);
compiled_initializer_bytecode.sweep_values(compactions);
}
}
26 changes: 20 additions & 6 deletions nova_vm/src/ecmascript/builtins/builtin_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,14 +540,28 @@ impl HeapMarkAndSweep for BuiltinFunction {

impl HeapMarkAndSweep for BuiltinFunctionHeapData {
fn mark_values(&self, queues: &mut WorkQueues) {
self.realm.mark_values(queues);
self.initial_name.mark_values(queues);
self.object_index.mark_values(queues);
let Self {
object_index,
length: _,
realm,
initial_name,
behaviour: _,
} = self;
realm.mark_values(queues);
initial_name.mark_values(queues);
object_index.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.realm.sweep_values(compactions);
self.initial_name.sweep_values(compactions);
self.object_index.sweep_values(compactions);
let Self {
object_index,
length: _,
realm,
initial_name,
behaviour: _,
} = self;
realm.sweep_values(compactions);
initial_name.sweep_values(compactions);
object_index.sweep_values(compactions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ impl AwaitReactionIdentifier {
// 5. d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
let vm = agent[self].vm.take().unwrap();
let async_function = agent[self].async_function.unwrap();
// SAFETY: We keep the async function alive.
let executable = unsafe { agent[async_function].compiled_bytecode.unwrap().as_ref() };
let executable = agent[async_function].compiled_bytecode.unwrap();
let execution_result = match reaction_type {
PromiseReactionType::Fulfill => vm.resume(agent, executable, value),
PromiseReactionType::Reject => vm.resume_throw(agent, executable, value),
Expand Down Expand Up @@ -179,14 +178,28 @@ impl CreateHeapData<AwaitReaction, AwaitReactionIdentifier> for Heap {

impl HeapMarkAndSweep for AwaitReaction {
fn mark_values(&self, queues: &mut WorkQueues) {
self.vm.mark_values(queues);
self.async_function.mark_values(queues);
self.return_promise_capability.mark_values(queues);
let Self {
vm,
async_function,
execution_context,
return_promise_capability,
} = self;
vm.mark_values(queues);
async_function.mark_values(queues);
execution_context.mark_values(queues);
return_promise_capability.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.vm.sweep_values(compactions);
self.async_function.sweep_values(compactions);
self.return_promise_capability.sweep_values(compactions);
let Self {
vm,
async_function,
execution_context,
return_promise_capability,
} = self;
vm.sweep_values(compactions);
async_function.sweep_values(compactions);
execution_context.sweep_values(compactions);
return_promise_capability.sweep_values(compactions);
}
}
Loading

0 comments on commit fb83620

Please sign in to comment.