Skip to content

feat(gc): Value lifetime #562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c26ffc7
[WIP] feat(gc): Value lifetime
aapoalas Feb 2, 2025
120aece
cont
aapoalas Feb 6, 2025
47d0b08
wrk wrk
aapoalas Feb 6, 2025
29c99b6
asdwrk
aapoalas Feb 6, 2025
38eea28
vm.rs basically done
aapoalas Feb 8, 2025
bac35b5
Finish constructor and prototype method issues
aapoalas Feb 8, 2025
464ff7f
wrk wrk, 903 errors remaining
aapoalas Feb 8, 2025
07c7d29
warnings
aapoalas Feb 8, 2025
8b907a5
wrk
aapoalas Feb 9, 2025
3a34de6
wrk
aapoalas Feb 12, 2025
01835be
fix 1/3 of Array.prototype methods
aapoalas Feb 13, 2025
471e8f0
More Array.prototype methods, 799 errors to go
aapoalas Feb 13, 2025
da4fd87
array_prototype.rs done, 776 errors to go
aapoalas Feb 20, 2025
b93a8c9
global_environment.rs, 769 errors to go
aapoalas Feb 21, 2025
857a0ac
Most of string_prototype
aapoalas Feb 21, 2025
93a413d
fixed some gc lifetime stuff
load1n9 Feb 23, 2025
a876875
fixed lifetime issues for json_object
load1n9 Feb 23, 2025
21a30e3
some regexp + array lifetime issues fixed
load1n9 Feb 23, 2025
2c2910e
string_prototype done: 610 errors left
aapoalas Feb 23, 2025
8cd4196
most of string constructor, 598 errors
aapoalas Feb 23, 2025
cb240ec
WIP fixed gc lifetime issues
load1n9 Feb 24, 2025
abc27be
WIP fixed some lifetime issues
load1n9 Feb 24, 2025
3d14ad7
wip lifetime issues
load1n9 Feb 24, 2025
636282f
wip value lifetime fixes
load1n9 Feb 24, 2025
d9126ae
value lifetime stuff
load1n9 Feb 26, 2025
00711de
fmt
load1n9 Feb 26, 2025
2ad5e88
Rest of string_constructor
aapoalas Feb 26, 2025
b359000
Number protoype and constructor
aapoalas Feb 26, 2025
a8817b1
Proxy methods, 209
aapoalas Feb 26, 2025
0c4cd5b
mooore work; 176 errors left
aapoalas Feb 26, 2025
d1057c9
Add Bindable trait, impl for a couple of tricksters; 195 errors
aapoalas Feb 27, 2025
40299e7
Mooore work; 155
aapoalas Feb 27, 2025
13bd7bb
Slightly broken stuff; up to 154
aapoalas Feb 27, 2025
60e87ff
various
aapoalas Feb 28, 2025
09f1dc6
0
aapoalas Feb 28, 2025
eb170f0
Fix call and new, cli microtask queue
aapoalas Feb 28, 2025
47ad7f5
fixes
aapoalas Feb 28, 2025
4172c40
chore(test262): Update expectations
aapoalas Feb 28, 2025
930c40c
Comment tests
aapoalas Feb 28, 2025
bb058f9
lint
aapoalas Feb 28, 2025
64be9fd
argh
aapoalas Feb 28, 2025
4229aa7
fiiix
aapoalas Feb 28, 2025
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ this is not a trivial piece of work. Right now a function in the engine might
look like this:

```rs
fn call(agent: &mut Agent, obj: Value, mut gc: Gc) -> JsResult<Value> {
fn call<'gc>(agent: &mut Agent, obj: Value, mut gc: GcScope<'gc, '_>) -> JsResult<Value<'gc>> {
if !obj.is_object() {
return Err(agent.throw_error(agent, "Not object", gc));
}
Expand Down Expand Up @@ -411,7 +411,7 @@ But what about when we call some mutable function and need to keep a reference
to a stack value past that call? This is how that would look:

```rs
fn call(agent: &mut Agent, value: Value, mut gc: Gc) -> JsResult<Value> {
fn call<'gc>(agent: &mut Agent, value: Value, mut gc: GcScope<'gc, '_>) -> JsResult<Value<'gc>> {
let value = unsafe { value.bind(agent) };
let kept_value: Global<Value<'static>> = value.make_global(value);
other_call(agent, gc.reborrow(), value.into_register())?;
Expand Down
60 changes: 37 additions & 23 deletions nova_cli/src/helper.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
use nova_vm::ecmascript::{
builtins::{create_builtin_function, ArgumentsList, Behaviour, BuiltinFunctionArgs},
execution::{agent::ExceptionType, Agent, JsResult},
types::{
InternalMethods, IntoValue, Object, OrdinaryObject, PropertyDescriptor, PropertyKey,
String, Value,
// This Source Code Form is subject to the terms of the Mozilla Public
// 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 nova_vm::{
ecmascript::{
builtins::{create_builtin_function, ArgumentsList, Behaviour, BuiltinFunctionArgs},
execution::{agent::ExceptionType, Agent, JsResult},
types::{
InternalMethods, IntoValue, Object, OrdinaryObject, PropertyDescriptor, PropertyKey,
String, Value,
},
},
engine::context::{Bindable, GcScope},
};
use nova_vm::engine::context::GcScope;
use oxc_diagnostics::OxcDiagnostic;

/// Initialize the global object with the built-in functions.
pub fn initialize_global_object(agent: &mut Agent, global: Object, mut gc: GcScope) {
let global = global.scope(agent, gc.nogc());
// `print` function
fn print(agent: &mut Agent, _this: Value, args: ArgumentsList, gc: GcScope) -> JsResult<Value> {
fn print<'gc>(
agent: &mut Agent,
_this: Value,
args: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<Value<'gc>> {
if args.len() == 0 {
println!();
} else {
Expand All @@ -22,12 +34,12 @@ pub fn initialize_global_object(agent: &mut Agent, global: Object, mut gc: GcSco
}

// 'readTextFile' function
fn read_text_file(
fn read_text_file<'gc>(
agent: &mut Agent,
_: Value,
args: ArgumentsList,
gc: GcScope,
) -> JsResult<Value> {
gc: GcScope<'gc, '_>,
) -> JsResult<Value<'gc>> {
if args.len() != 1 {
return Err(agent.throw_exception_with_static_message(
ExceptionType::Error,
Expand All @@ -45,7 +57,7 @@ pub fn initialize_global_object(agent: &mut Agent, global: Object, mut gc: GcSco

let file = std::fs::read_to_string(path.as_str(agent))
.map_err(|e| agent.throw_exception(ExceptionType::Error, e.to_string(), gc.nogc()))?;
Ok(String::from_string(agent, file, gc.nogc()).into_value())
Ok(String::from_string(agent, file, gc.into_nogc()).into_value())
}

let function = create_builtin_function(
Expand All @@ -56,11 +68,12 @@ pub fn initialize_global_object(agent: &mut Agent, global: Object, mut gc: GcSco
);
let property_key = PropertyKey::from_static_str(agent, "print", gc.nogc()).unbind();
global
.get(agent)
.internal_define_own_property(
agent,
property_key,
PropertyDescriptor {
value: Some(function.into_value()),
value: Some(function.into_value().unbind()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(true),
Expand All @@ -78,11 +91,12 @@ pub fn initialize_global_object(agent: &mut Agent, global: Object, mut gc: GcSco
);
let property_key = PropertyKey::from_static_str(agent, "readTextFile", gc.nogc()).unbind();
global
.get(agent)
.internal_define_own_property(
agent,
property_key,
PropertyDescriptor {
value: Some(function.into_value()),
value: Some(function.into_value().unbind()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(true),
Expand All @@ -95,12 +109,12 @@ pub fn initialize_global_object(agent: &mut Agent, global: Object, mut gc: GcSco

pub fn initialize_global_object_with_internals(agent: &mut Agent, global: Object, mut gc: GcScope) {
// `detachArrayBuffer` function
fn detach_array_buffer(
fn detach_array_buffer<'gc>(
agent: &mut Agent,
_this: Value,
args: ArgumentsList,
gc: GcScope,
) -> JsResult<Value> {
gc: GcScope<'gc, '_>,
) -> JsResult<Value<'gc>> {
let Value::ArrayBuffer(array_buffer) = args.get(0) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::Error,
Expand All @@ -112,12 +126,12 @@ pub fn initialize_global_object_with_internals(agent: &mut Agent, global: Object
Ok(Value::Undefined)
}

fn create_realm(
fn create_realm<'gc>(
agent: &mut Agent,
_this: Value,
_args: ArgumentsList,
_gc: GcScope,
) -> JsResult<Value> {
_gc: GcScope<'gc, '_>,
) -> JsResult<Value<'gc>> {
let create_global_object: Option<for<'a> fn(&mut Agent, GcScope<'a, '_>) -> Object<'a>> =
None;
let create_global_this_value: Option<
Expand All @@ -128,7 +142,7 @@ pub fn initialize_global_object_with_internals(agent: &mut Agent, global: Object
create_global_this_value,
Some(initialize_global_object_with_internals),
);
Ok(realm.global_object(agent).into_value())
Ok(realm.global_object(agent).into_value().unbind())
}

initialize_global_object(agent, global, gc.reborrow());
Expand Down Expand Up @@ -163,7 +177,7 @@ pub fn initialize_global_object_with_internals(agent: &mut Agent, global: Object
agent,
property_key,
PropertyDescriptor {
value: Some(function.into_value()),
value: Some(function.into_value().unbind()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(true),
Expand All @@ -186,7 +200,7 @@ pub fn initialize_global_object_with_internals(agent: &mut Agent, global: Object
agent,
property_key,
PropertyDescriptor {
value: Some(function.into_value()),
value: Some(function.into_value().unbind()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(true),
Expand Down
39 changes: 30 additions & 9 deletions nova_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ use nova_vm::{
ecmascript::{
execution::{
agent::{GcAgent, HostHooks, Job, Options},
Agent,
Agent, JsResult,
},
scripts_and_modules::script::{parse_script, script_evaluation},
types::{Object, String as JsString},
types::{Object, String as JsString, Value},
},
engine::context::GcScope,
engine::context::{Bindable, GcScope},
};
use oxc_parser::Parser;
use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn};
Expand Down Expand Up @@ -90,6 +90,10 @@ impl Debug for CliHostHooks {
}

impl CliHostHooks {
fn has_promise_jobs(&self) -> bool {
!self.promise_job_queue.borrow().is_empty()
}

fn pop_promise_job(&self) -> Option<Job> {
self.promise_job_queue.borrow_mut().pop_front()
}
Expand Down Expand Up @@ -186,17 +190,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
exit_with_parse_errors(errors, &path, source_text)
}
};
let mut result = script_evaluation(agent, script, gc.reborrow());
let result = script_evaluation(agent, script, gc.reborrow());

if result.is_ok() {
while let Some(job) = host_hooks.pop_promise_job() {
if let Err(err) = job.run(agent, gc.reborrow()) {
result = Err(err);
break;
fn run_microtask_queue<'gc>(
agent: &mut Agent,
host_hooks: &CliHostHooks,
result: JsResult<Value>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<Value<'gc>> {
match result.bind(gc.nogc()) {
Ok(result) => {
let ok_result = result.unbind().scope(agent, gc.nogc());
while let Some(job) = host_hooks.pop_promise_job() {
job.run(agent, gc.reborrow())?;
}
Ok(ok_result.get(agent).bind(gc.into_nogc()))
}
Err(_) => result.bind(gc.into_nogc()),
}
}

let result = if host_hooks.has_promise_jobs() {
run_microtask_queue(agent, host_hooks, result.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc())
} else {
result
};

match result {
Ok(result) => {
if verbose {
Expand Down
Loading