Skip to content

Commit fb83620

Browse files
authored
feat(engine): Implement interleaved garbage collection (#443)
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.
1 parent ab64d9c commit fb83620

File tree

60 files changed

+4374
-3478
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+4374
-3478
lines changed

nova_vm/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ wtf8 = { workspace = true }
2525

2626
[features]
2727
default = ["math", "json", "date", "array-buffer", "shared-array-buffer", "weak-refs", "atomics"]
28-
math = []
29-
json = ["sonic-rs"]
30-
date = []
3128
array-buffer = []
32-
shared-array-buffer = []
33-
weak-refs = []
3429
atomics = ["array-buffer", "shared-array-buffer"]
30+
date = []
31+
interleaved-gc = []
32+
json = ["sonic-rs"]
33+
math = []
34+
shared-array-buffer = []
3535
typescript = []
36+
weak-refs = []
3637

3738
[build-dependencies]
3839
small_string = { path = "../small_string" }

nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,12 +451,22 @@ pub(crate) fn iterator_to_list(
451451

452452
impl HeapMarkAndSweep for IteratorRecord {
453453
fn mark_values(&self, queues: &mut WorkQueues) {
454-
self.iterator.mark_values(queues);
455-
self.next_method.mark_values(queues);
454+
let Self {
455+
iterator,
456+
next_method,
457+
done: _,
458+
} = self;
459+
iterator.mark_values(queues);
460+
next_method.mark_values(queues);
456461
}
457462

458463
fn sweep_values(&mut self, compactions: &CompactionLists) {
459-
self.iterator.sweep_values(compactions);
460-
self.next_method.sweep_values(compactions);
464+
let Self {
465+
iterator,
466+
next_method,
467+
done: _,
468+
} = self;
469+
iterator.sweep_values(compactions);
470+
next_method.sweep_values(compactions);
461471
}
462472
}

nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,6 @@ pub(crate) fn initialize_instance_elements(
907907
// To do this, we need a new execution context that points to a new
908908
// Function environment. The function environment should be lexically a
909909
// child of the class constructor's creating environment.
910-
let bytecode = unsafe { bytecode.as_ref() };
911910
let f = constructor.into_function();
912911
let outer_env = constructor_data.environment;
913912
let outer_priv_env = constructor_data.private_environment;

nova_vm/src/ecmascript/builtins/array/data.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,27 +127,33 @@ pub struct ArrayHeapData {
127127

128128
impl HeapMarkAndSweep for SealableElementsVector {
129129
fn mark_values(&self, queues: &mut WorkQueues) {
130-
let item = *self;
131-
let elements: ElementsVector = item.into();
130+
let elements: ElementsVector = (*self).into();
132131
elements.mark_values(queues)
133132
}
134133

135134
fn sweep_values(&mut self, compactions: &CompactionLists) {
136-
let item = *self;
137-
let mut elements: ElementsVector = item.into();
135+
let mut elements: ElementsVector = (*self).into();
138136
elements.sweep_values(compactions);
139137
self.elements_index = elements.elements_index;
140138
}
141139
}
142140

143141
impl HeapMarkAndSweep for ArrayHeapData {
144142
fn mark_values(&self, queues: &mut WorkQueues) {
145-
self.object_index.mark_values(queues);
146-
self.elements.mark_values(queues);
143+
let Self {
144+
object_index,
145+
elements,
146+
} = self;
147+
object_index.mark_values(queues);
148+
elements.mark_values(queues);
147149
}
148150

149151
fn sweep_values(&mut self, compactions: &CompactionLists) {
150-
self.object_index.sweep_values(compactions);
151-
self.elements.sweep_values(compactions);
152+
let Self {
153+
object_index,
154+
elements,
155+
} = self;
156+
object_index.sweep_values(compactions);
157+
elements.sweep_values(compactions);
152158
}
153159
}

nova_vm/src/ecmascript/builtins/array_buffer/data.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,18 @@ impl ArrayBufferHeapData {
149149

150150
impl HeapMarkAndSweep for ArrayBufferHeapData {
151151
fn mark_values(&self, queues: &mut WorkQueues) {
152-
self.object_index.mark_values(queues);
152+
let Self {
153+
object_index,
154+
buffer: _,
155+
} = self;
156+
object_index.mark_values(queues);
153157
}
154158

155159
fn sweep_values(&mut self, compactions: &CompactionLists) {
156-
self.object_index.sweep_values(compactions);
160+
let Self {
161+
object_index,
162+
buffer: _,
163+
} = self;
164+
object_index.sweep_values(compactions);
157165
}
158166
}

nova_vm/src/ecmascript/builtins/bound_function.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -321,18 +321,34 @@ impl HeapMarkAndSweep for BoundFunction {
321321

322322
impl HeapMarkAndSweep for BoundFunctionHeapData {
323323
fn mark_values(&self, queues: &mut WorkQueues) {
324-
self.name.mark_values(queues);
325-
self.bound_target_function.mark_values(queues);
326-
self.object_index.mark_values(queues);
327-
self.bound_this.mark_values(queues);
328-
self.bound_arguments.mark_values(queues);
324+
let Self {
325+
object_index,
326+
length: _,
327+
bound_target_function,
328+
bound_this,
329+
bound_arguments,
330+
name,
331+
} = self;
332+
name.mark_values(queues);
333+
bound_target_function.mark_values(queues);
334+
object_index.mark_values(queues);
335+
bound_this.mark_values(queues);
336+
bound_arguments.mark_values(queues);
329337
}
330338

331339
fn sweep_values(&mut self, compactions: &CompactionLists) {
332-
self.name.sweep_values(compactions);
333-
self.bound_target_function.sweep_values(compactions);
334-
self.object_index.sweep_values(compactions);
335-
self.bound_this.sweep_values(compactions);
336-
self.bound_arguments.sweep_values(compactions);
340+
let Self {
341+
object_index,
342+
length: _,
343+
bound_target_function,
344+
bound_this,
345+
bound_arguments,
346+
name,
347+
} = self;
348+
name.sweep_values(compactions);
349+
bound_target_function.sweep_values(compactions);
350+
object_index.sweep_values(compactions);
351+
bound_this.sweep_values(compactions);
352+
bound_arguments.sweep_values(compactions);
337353
}
338354
}

nova_vm/src/ecmascript/builtins/builtin_constructor.rs

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use std::{
6-
ops::{Index, IndexMut},
7-
ptr::NonNull,
8-
};
5+
use std::ops::{Index, IndexMut};
96

107
use oxc_span::Span;
118

@@ -339,7 +336,7 @@ pub(crate) struct BuiltinConstructorArgs {
339336
pub(crate) class_name: String,
340337
pub(crate) prototype: Option<Object>,
341338
pub(crate) prototype_property: Object,
342-
pub(crate) compiled_initializer_bytecode: Option<Box<Executable>>,
339+
pub(crate) compiled_initializer_bytecode: Option<Executable>,
343340
pub(crate) env: EnvironmentIndex,
344341
pub(crate) private_env: Option<PrivateEnvironmentIndex>,
345342
pub(crate) source_code: SourceCode,
@@ -414,17 +411,13 @@ pub(crate) fn create_builtin_constructor(
414411
agent.heap.create_null_object(&entries)
415412
};
416413

417-
let compiled_initializer_bytecode = args
418-
.compiled_initializer_bytecode
419-
.map(|bytecode| NonNull::from(Box::leak(bytecode)));
420-
421414
// 13. Return func.
422415
agent.heap.create(BuiltinConstructorHeapData {
423416
// 10. Perform SetFunctionLength(func, length).
424417
// Skipped as length of builtin constructors is always 0.
425418
// 8. Set func.[[Realm]] to realm.
426419
realm,
427-
compiled_initializer_bytecode,
420+
compiled_initializer_bytecode: args.compiled_initializer_bytecode,
428421
is_derived: args.is_derived,
429422
object_index: Some(backing_object),
430423
environment: args.env,
@@ -453,34 +446,40 @@ impl HeapMarkAndSweep for BuiltinConstructorFunction {
453446

454447
impl HeapMarkAndSweep for BuiltinConstructorHeapData {
455448
fn mark_values(&self, queues: &mut WorkQueues) {
456-
self.realm.mark_values(queues);
457-
self.object_index.mark_values(queues);
458-
self.environment.mark_values(queues);
459-
self.private_environment.mark_values(queues);
460-
self.source_code.mark_values(queues);
461-
if let Some(exe) = &self.compiled_initializer_bytecode {
462-
// SAFETY: This is a valid, non-null pointer to an owned Executable
463-
// that cannot have any live mutable references to it.
464-
unsafe { exe.as_ref() }.mark_values(queues);
465-
}
449+
let Self {
450+
object_index,
451+
realm,
452+
is_derived: _,
453+
compiled_initializer_bytecode,
454+
environment,
455+
private_environment,
456+
source_text: _,
457+
source_code,
458+
} = self;
459+
realm.mark_values(queues);
460+
object_index.mark_values(queues);
461+
environment.mark_values(queues);
462+
private_environment.mark_values(queues);
463+
source_code.mark_values(queues);
464+
compiled_initializer_bytecode.mark_values(queues);
466465
}
467466

468467
fn sweep_values(&mut self, compactions: &CompactionLists) {
469-
self.realm.sweep_values(compactions);
470-
self.object_index.sweep_values(compactions);
471-
self.environment.sweep_values(compactions);
472-
self.private_environment.sweep_values(compactions);
473-
self.source_code.sweep_values(compactions);
474-
if let Some(exe) = &mut self.compiled_initializer_bytecode {
475-
// SAFETY: This is a valid, non-null pointer to an owned Executable
476-
// that cannot have any live references to it.
477-
// References to this Executable are only created above for marking
478-
// and in function_definition for running the function. Both of the
479-
// references only live for the duration of a synchronous call and
480-
// no longer. Sweeping cannot run concurrently with marking or with
481-
// ECMAScript code execution. Hence we can be sure that this is not
482-
// an aliasing violation.
483-
unsafe { exe.as_mut() }.sweep_values(compactions);
484-
}
468+
let Self {
469+
object_index,
470+
realm,
471+
is_derived: _,
472+
compiled_initializer_bytecode,
473+
environment,
474+
private_environment,
475+
source_text: _,
476+
source_code,
477+
} = self;
478+
realm.sweep_values(compactions);
479+
object_index.sweep_values(compactions);
480+
environment.sweep_values(compactions);
481+
private_environment.sweep_values(compactions);
482+
source_code.sweep_values(compactions);
483+
compiled_initializer_bytecode.sweep_values(compactions);
485484
}
486485
}

nova_vm/src/ecmascript/builtins/builtin_function.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -540,14 +540,28 @@ impl HeapMarkAndSweep for BuiltinFunction {
540540

541541
impl HeapMarkAndSweep for BuiltinFunctionHeapData {
542542
fn mark_values(&self, queues: &mut WorkQueues) {
543-
self.realm.mark_values(queues);
544-
self.initial_name.mark_values(queues);
545-
self.object_index.mark_values(queues);
543+
let Self {
544+
object_index,
545+
length: _,
546+
realm,
547+
initial_name,
548+
behaviour: _,
549+
} = self;
550+
realm.mark_values(queues);
551+
initial_name.mark_values(queues);
552+
object_index.mark_values(queues);
546553
}
547554

548555
fn sweep_values(&mut self, compactions: &CompactionLists) {
549-
self.realm.sweep_values(compactions);
550-
self.initial_name.sweep_values(compactions);
551-
self.object_index.sweep_values(compactions);
556+
let Self {
557+
object_index,
558+
length: _,
559+
realm,
560+
initial_name,
561+
behaviour: _,
562+
} = self;
563+
realm.sweep_values(compactions);
564+
initial_name.sweep_values(compactions);
565+
object_index.sweep_values(compactions);
552566
}
553567
}

nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_function_objects/await_reaction.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ impl AwaitReactionIdentifier {
6868
// 5. d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
6969
let vm = agent[self].vm.take().unwrap();
7070
let async_function = agent[self].async_function.unwrap();
71-
// SAFETY: We keep the async function alive.
72-
let executable = unsafe { agent[async_function].compiled_bytecode.unwrap().as_ref() };
71+
let executable = agent[async_function].compiled_bytecode.unwrap();
7372
let execution_result = match reaction_type {
7473
PromiseReactionType::Fulfill => vm.resume(agent, executable, value),
7574
PromiseReactionType::Reject => vm.resume_throw(agent, executable, value),
@@ -179,14 +178,28 @@ impl CreateHeapData<AwaitReaction, AwaitReactionIdentifier> for Heap {
179178

180179
impl HeapMarkAndSweep for AwaitReaction {
181180
fn mark_values(&self, queues: &mut WorkQueues) {
182-
self.vm.mark_values(queues);
183-
self.async_function.mark_values(queues);
184-
self.return_promise_capability.mark_values(queues);
181+
let Self {
182+
vm,
183+
async_function,
184+
execution_context,
185+
return_promise_capability,
186+
} = self;
187+
vm.mark_values(queues);
188+
async_function.mark_values(queues);
189+
execution_context.mark_values(queues);
190+
return_promise_capability.mark_values(queues);
185191
}
186192

187193
fn sweep_values(&mut self, compactions: &CompactionLists) {
188-
self.vm.sweep_values(compactions);
189-
self.async_function.sweep_values(compactions);
190-
self.return_promise_capability.sweep_values(compactions);
194+
let Self {
195+
vm,
196+
async_function,
197+
execution_context,
198+
return_promise_capability,
199+
} = self;
200+
vm.sweep_values(compactions);
201+
async_function.sweep_values(compactions);
202+
execution_context.sweep_values(compactions);
203+
return_promise_capability.sweep_values(compactions);
191204
}
192205
}

0 commit comments

Comments
 (0)