From e2a7fc89d36eedb976f7704593f20a3271a736d6 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Mon, 22 Jul 2024 23:19:19 +0200 Subject: [PATCH 01/13] feat: New type of builtin functions with storage --- Cargo.toml | 1 + nova_vm/Cargo.toml | 1 + .../builders/builtin_function_builder.rs | 4 +- nova_vm/src/ecmascript/builtins.rs | 3 +- .../ecmascript/builtins/builtin_function.rs | 55 ++++++++++++++++++- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ba6761117..e4b3ed874 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,5 @@ oxc_diagnostics = "0.20.0" rand = "0.8.5" ryu-js = "1.0.1" wtf8 = "0.1" +anymap = "0.12.1" diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 859ffab8a..d980f2830 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -17,6 +17,7 @@ rand = { workspace = true } ryu-js = { workspace = true } small_string = { path = "../small_string" } wtf8 = { workspace = true } +anymap = { workspace = true } [features] typescript = [] diff --git a/nova_vm/src/ecmascript/builders/builtin_function_builder.rs b/nova_vm/src/ecmascript/builders/builtin_function_builder.rs index 03ed29535..4e93ec1ee 100644 --- a/nova_vm/src/ecmascript/builders/builtin_function_builder.rs +++ b/nova_vm/src/ecmascript/builders/builtin_function_builder.rs @@ -43,7 +43,7 @@ pub struct CreatorName(String); #[derive(Default, Clone, Copy)] pub struct NoBehaviour; -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct CreatorBehaviour(Behaviour); #[derive(Default, Clone, Copy)] @@ -530,7 +530,7 @@ impl<'agent> length: self.length.0, realm: self.realm, initial_name: Some(self.name.0), - behaviour: self.behaviour.0, + behaviour: self.behaviour.0.clone(), }; let slot = self diff --git a/nova_vm/src/ecmascript/builtins.rs b/nova_vm/src/ecmascript/builtins.rs index 4791edeca..0ba30f5f8 100644 --- a/nova_vm/src/ecmascript/builtins.rs +++ b/nova_vm/src/ecmascript/builtins.rs @@ -51,7 +51,8 @@ pub use array_buffer::ArrayBuffer; pub(crate) use array_buffer::ArrayBufferHeapData; pub use builtin_function::{ create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunction, - BuiltinFunctionArgs, BuiltinGetter, ConstructorFn, RegularFn as JsFunction, RegularFn, + BuiltinFunctionArgs, BuiltinGetter, ConstructorFn, FnStorage, RegularFn as JsFunction, + RegularFn, RegularFnStorage, RegularWithStorageFn, }; pub(crate) use builtin_function::{BuiltinIntrinsic, BuiltinIntrinsicConstructor}; pub(crate) use ecmascript_function::*; diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index 7ea6e2b4f..84bb33d78 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -2,7 +2,13 @@ // 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::{Deref, Index, IndexMut}; +use std::{ + cell::RefCell, + ops::{Deref, Index, IndexMut}, + rc::Rc, +}; + +use anymap::AnyMap; use crate::{ ecmascript::{ @@ -41,13 +47,43 @@ impl ArgumentsList<'_> { } } +#[derive(Debug, Clone)] +pub struct RegularFnStorage(pub Rc>); +impl Default for RegularFnStorage { + fn default() -> Self { + Self(Rc::new(RefCell::new(FnStorage::new()))) + } +} + +impl From for RegularFnStorage { + fn from(value: FnStorage) -> Self { + Self(Rc::new(RefCell::new(value))) + } +} + +impl PartialEq for RegularFnStorage { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} +impl Eq for RegularFnStorage {} +/// Safety: +/// The storage will ways be used in one thread, but, because the +/// garbage collector clears the data in other threads, this is required. +unsafe impl Send for RegularFnStorage {} +unsafe impl Sync for RegularFnStorage {} + +pub type FnStorage = AnyMap; +pub type RegularWithStorageFn = + fn(&mut Agent, Value, ArgumentsList<'_>, &mut FnStorage) -> JsResult; pub type RegularFn = fn(&mut Agent, Value, ArgumentsList<'_>) -> JsResult; pub type ConstructorFn = fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Behaviour { Regular(RegularFn), + RegularWithStorage(RegularWithStorageFn, RegularFnStorage), Constructor(ConstructorFn), } @@ -525,7 +561,7 @@ pub(crate) fn builtin_call_or_construct( // 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to // the specification of F. If thisArgument is uninitialized, the this value is uninitialized; otherwise, // thisArgument provides the this value. argumentsList provides the named parameters. newTarget provides the NewTarget value. - let func = heap_data.behaviour; + let func = heap_data.behaviour.clone(); let result = match func { Behaviour::Regular(func) => { if new_target.is_some() { @@ -538,6 +574,19 @@ pub(crate) fn builtin_call_or_construct( ) } } + Behaviour::RegularWithStorage(func, storage) => { + if new_target.is_some() { + Err(agent.throw_exception(ExceptionType::TypeError, "Not a constructor")) + } else { + let mut storage = storage.0.borrow_mut(); + func( + agent, + this_argument.unwrap_or(Value::Undefined), + arguments_list, + &mut storage, + ) + } + } Behaviour::Constructor(func) => func( agent, this_argument.unwrap_or(Value::Undefined), From 7880f196b41c034ff3e84d459501c5c2f5a9f2b2 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 12:52:54 +0200 Subject: [PATCH 02/13] feat: Move the storage logic as a method in HostHooks --- .../ecmascript/builtins/builtin_function.rs | 51 +------------------ nova_vm/src/ecmascript/execution/agent.rs | 10 +++- .../execution/default_host_hooks.rs | 4 ++ 3 files changed, 14 insertions(+), 51 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index 84bb33d78..f3a34a50e 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -2,13 +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::{ - cell::RefCell, - ops::{Deref, Index, IndexMut}, - rc::Rc, -}; - -use anymap::AnyMap; +use std::ops::{Deref, Index, IndexMut}; use crate::{ ecmascript::{ @@ -47,35 +41,6 @@ impl ArgumentsList<'_> { } } -#[derive(Debug, Clone)] -pub struct RegularFnStorage(pub Rc>); -impl Default for RegularFnStorage { - fn default() -> Self { - Self(Rc::new(RefCell::new(FnStorage::new()))) - } -} - -impl From for RegularFnStorage { - fn from(value: FnStorage) -> Self { - Self(Rc::new(RefCell::new(value))) - } -} - -impl PartialEq for RegularFnStorage { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) - } -} -impl Eq for RegularFnStorage {} -/// Safety: -/// The storage will ways be used in one thread, but, because the -/// garbage collector clears the data in other threads, this is required. -unsafe impl Send for RegularFnStorage {} -unsafe impl Sync for RegularFnStorage {} - -pub type FnStorage = AnyMap; -pub type RegularWithStorageFn = - fn(&mut Agent, Value, ArgumentsList<'_>, &mut FnStorage) -> JsResult; pub type RegularFn = fn(&mut Agent, Value, ArgumentsList<'_>) -> JsResult; pub type ConstructorFn = fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; @@ -83,7 +48,6 @@ pub type ConstructorFn = #[derive(Debug, Clone, PartialEq, Eq)] pub enum Behaviour { Regular(RegularFn), - RegularWithStorage(RegularWithStorageFn, RegularFnStorage), Constructor(ConstructorFn), } @@ -574,19 +538,6 @@ pub(crate) fn builtin_call_or_construct( ) } } - Behaviour::RegularWithStorage(func, storage) => { - if new_target.is_some() { - Err(agent.throw_exception(ExceptionType::TypeError, "Not a constructor")) - } else { - let mut storage = storage.0.borrow_mut(); - func( - agent, - this_argument.unwrap_or(Value::Undefined), - arguments_list, - &mut storage, - ) - } - } Behaviour::Constructor(func) => func( agent, this_argument.unwrap_or(Value::Undefined), diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index d105e2d42..1a3beeb08 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -7,6 +7,8 @@ //! - This is inspired by and/or copied from Kiesel engine: //! Copyright (c) 2023-2024 Linus Groh +use anymap::AnyMap; + use super::{ environments::get_identifier_reference, EnvironmentIndex, ExecutionContext, Realm, RealmIdentifier, @@ -21,7 +23,7 @@ use crate::{ heap::CreateHeapData, Heap, }; -use std::collections::HashMap; +use std::{cell::RefMut, collections::HashMap}; #[derive(Debug, Default)] pub struct Options { @@ -122,6 +124,8 @@ pub trait HostHooks: std::fmt::Debug { ) { // The default implementation of HostPromiseRejectionTracker is to return unused. } + + fn get_storage_handle(&self) -> RefMut; } /// ### [9.7 Agents](https://tc39.es/ecma262/#sec-agents) @@ -189,6 +193,10 @@ impl Agent { pub(crate) fn running_execution_context_mut(&mut self) -> &mut ExecutionContext { self.execution_context_stack.last_mut().unwrap() } + + pub fn get_hoost_hooks(&self) -> &'static dyn HostHooks { + self.host_hooks + } } /// ### [9.4.1 GetActiveScriptOrModule ()](https://tc39.es/ecma262/#sec-getactivescriptormodule) diff --git a/nova_vm/src/ecmascript/execution/default_host_hooks.rs b/nova_vm/src/ecmascript/execution/default_host_hooks.rs index 471dc7b00..4de48a5cf 100644 --- a/nova_vm/src/ecmascript/execution/default_host_hooks.rs +++ b/nova_vm/src/ecmascript/execution/default_host_hooks.rs @@ -29,4 +29,8 @@ impl HostHooks for DefaultHostHooks { fn enqueue_promise_job(&self, _job: Job) { // No-op } + + fn get_storage_handle(&self) -> std::cell::RefMut { + panic!("Storage is not supported.") + } } From 047a92831aa9ffe8a8f6ad60bb57f81a9e60cc60 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 12:56:17 +0200 Subject: [PATCH 03/13] clean up --- nova_cli/Cargo.toml | 1 + nova_cli/src/main.rs | 22 +++++++++++++++++-- .../builders/builtin_function_builder.rs | 4 ++-- nova_vm/src/ecmascript/builtins.rs | 3 +-- .../ecmascript/builtins/builtin_function.rs | 4 ++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/nova_cli/Cargo.toml b/nova_cli/Cargo.toml index 3c2202944..d784b50d7 100644 --- a/nova_cli/Cargo.toml +++ b/nova_cli/Cargo.toml @@ -14,3 +14,4 @@ oxc_ast = { workspace = true } oxc_parser = { workspace = true } oxc_span = { workspace = true } oxc_diagnostics = { workspace = true } +anymap = { workspace = true } \ No newline at end of file diff --git a/nova_cli/src/main.rs b/nova_cli/src/main.rs index d38db5cef..fb5aa426a 100644 --- a/nova_cli/src/main.rs +++ b/nova_cli/src/main.rs @@ -4,8 +4,13 @@ mod helper; mod theme; -use std::{cell::RefCell, collections::VecDeque, fmt::Debug}; +use std::{ + cell::{RefCell, RefMut}, + collections::VecDeque, + fmt::Debug, +}; +use anymap::AnyMap; use clap::{Parser as ClapParser, Subcommand}; use cliclack::{input, intro, set_theme}; use helper::{exit_with_parse_errors, initialize_global_object}; @@ -55,9 +60,18 @@ enum Command { Repl {}, } -#[derive(Default)] struct CliHostHooks { promise_job_queue: RefCell>, + storage: RefCell, +} + +impl Default for CliHostHooks { + fn default() -> Self { + Self { + promise_job_queue: RefCell::default(), + storage: RefCell::new(AnyMap::new()), + } + } } // RefCell doesn't implement Debug @@ -79,6 +93,10 @@ impl HostHooks for CliHostHooks { fn enqueue_promise_job(&self, job: Job) { self.promise_job_queue.borrow_mut().push_back(job); } + + fn get_storage_handle(&self) -> RefMut { + self.storage.borrow_mut() + } } fn main() -> Result<(), Box> { diff --git a/nova_vm/src/ecmascript/builders/builtin_function_builder.rs b/nova_vm/src/ecmascript/builders/builtin_function_builder.rs index 4e93ec1ee..03ed29535 100644 --- a/nova_vm/src/ecmascript/builders/builtin_function_builder.rs +++ b/nova_vm/src/ecmascript/builders/builtin_function_builder.rs @@ -43,7 +43,7 @@ pub struct CreatorName(String); #[derive(Default, Clone, Copy)] pub struct NoBehaviour; -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct CreatorBehaviour(Behaviour); #[derive(Default, Clone, Copy)] @@ -530,7 +530,7 @@ impl<'agent> length: self.length.0, realm: self.realm, initial_name: Some(self.name.0), - behaviour: self.behaviour.0.clone(), + behaviour: self.behaviour.0, }; let slot = self diff --git a/nova_vm/src/ecmascript/builtins.rs b/nova_vm/src/ecmascript/builtins.rs index 0ba30f5f8..4791edeca 100644 --- a/nova_vm/src/ecmascript/builtins.rs +++ b/nova_vm/src/ecmascript/builtins.rs @@ -51,8 +51,7 @@ pub use array_buffer::ArrayBuffer; pub(crate) use array_buffer::ArrayBufferHeapData; pub use builtin_function::{ create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunction, - BuiltinFunctionArgs, BuiltinGetter, ConstructorFn, FnStorage, RegularFn as JsFunction, - RegularFn, RegularFnStorage, RegularWithStorageFn, + BuiltinFunctionArgs, BuiltinGetter, ConstructorFn, RegularFn as JsFunction, RegularFn, }; pub(crate) use builtin_function::{BuiltinIntrinsic, BuiltinIntrinsicConstructor}; pub(crate) use ecmascript_function::*; diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index f3a34a50e..7ea6e2b4f 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -45,7 +45,7 @@ pub type RegularFn = fn(&mut Agent, Value, ArgumentsList<'_>) -> JsResult pub type ConstructorFn = fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Behaviour { Regular(RegularFn), Constructor(ConstructorFn), @@ -525,7 +525,7 @@ pub(crate) fn builtin_call_or_construct( // 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to // the specification of F. If thisArgument is uninitialized, the this value is uninitialized; otherwise, // thisArgument provides the this value. argumentsList provides the named parameters. newTarget provides the NewTarget value. - let func = heap_data.behaviour.clone(); + let func = heap_data.behaviour; let result = match func { Behaviour::Regular(func) => { if new_target.is_some() { From f26ece2701090b77713f256a570dda96457805d4 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 13:02:38 +0200 Subject: [PATCH 04/13] refactor: `get_host_storage` --- nova_vm/src/ecmascript/execution/agent.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 1a3beeb08..ad8a37df4 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -194,8 +194,8 @@ impl Agent { self.execution_context_stack.last_mut().unwrap() } - pub fn get_hoost_hooks(&self) -> &'static dyn HostHooks { - self.host_hooks + pub fn get_host_storage(&self) -> RefMut { + self.host_hooks.get_storage_handle() } } From e4a799260842240df58ebfe9df50ae729351823d Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 13:19:36 +0200 Subject: [PATCH 05/13] chore: More clean up --- Cargo.toml | 1 - nova_cli/Cargo.toml | 3 +-- nova_cli/src/main.rs | 22 ++----------------- nova_vm/Cargo.toml | 1 - nova_vm/src/ecmascript/execution/agent.rs | 12 +++++----- .../execution/default_host_hooks.rs | 4 ---- 6 files changed, 9 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4b3ed874..ba6761117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,4 @@ oxc_diagnostics = "0.20.0" rand = "0.8.5" ryu-js = "1.0.1" wtf8 = "0.1" -anymap = "0.12.1" diff --git a/nova_cli/Cargo.toml b/nova_cli/Cargo.toml index d784b50d7..225d78ade 100644 --- a/nova_cli/Cargo.toml +++ b/nova_cli/Cargo.toml @@ -13,5 +13,4 @@ nova_vm = { path = "../nova_vm" } oxc_ast = { workspace = true } oxc_parser = { workspace = true } oxc_span = { workspace = true } -oxc_diagnostics = { workspace = true } -anymap = { workspace = true } \ No newline at end of file +oxc_diagnostics = { workspace = true } \ No newline at end of file diff --git a/nova_cli/src/main.rs b/nova_cli/src/main.rs index fb5aa426a..d38db5cef 100644 --- a/nova_cli/src/main.rs +++ b/nova_cli/src/main.rs @@ -4,13 +4,8 @@ mod helper; mod theme; -use std::{ - cell::{RefCell, RefMut}, - collections::VecDeque, - fmt::Debug, -}; +use std::{cell::RefCell, collections::VecDeque, fmt::Debug}; -use anymap::AnyMap; use clap::{Parser as ClapParser, Subcommand}; use cliclack::{input, intro, set_theme}; use helper::{exit_with_parse_errors, initialize_global_object}; @@ -60,18 +55,9 @@ enum Command { Repl {}, } +#[derive(Default)] struct CliHostHooks { promise_job_queue: RefCell>, - storage: RefCell, -} - -impl Default for CliHostHooks { - fn default() -> Self { - Self { - promise_job_queue: RefCell::default(), - storage: RefCell::new(AnyMap::new()), - } - } } // RefCell doesn't implement Debug @@ -93,10 +79,6 @@ impl HostHooks for CliHostHooks { fn enqueue_promise_job(&self, job: Job) { self.promise_job_queue.borrow_mut().push_back(job); } - - fn get_storage_handle(&self) -> RefMut { - self.storage.borrow_mut() - } } fn main() -> Result<(), Box> { diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index d980f2830..859ffab8a 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -17,7 +17,6 @@ rand = { workspace = true } ryu-js = { workspace = true } small_string = { path = "../small_string" } wtf8 = { workspace = true } -anymap = { workspace = true } [features] typescript = [] diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index ad8a37df4..c1ff9f229 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -7,8 +7,6 @@ //! - This is inspired by and/or copied from Kiesel engine: //! Copyright (c) 2023-2024 Linus Groh -use anymap::AnyMap; - use super::{ environments::get_identifier_reference, EnvironmentIndex, ExecutionContext, Realm, RealmIdentifier, @@ -23,7 +21,7 @@ use crate::{ heap::CreateHeapData, Heap, }; -use std::{cell::RefMut, collections::HashMap}; +use std::{any::Any, collections::HashMap}; #[derive(Debug, Default)] pub struct Options { @@ -125,7 +123,9 @@ pub trait HostHooks: std::fmt::Debug { // The default implementation of HostPromiseRejectionTracker is to return unused. } - fn get_storage_handle(&self) -> RefMut; + fn get_host_data(&self) -> &dyn Any { + unimplemented!() + } } /// ### [9.7 Agents](https://tc39.es/ecma262/#sec-agents) @@ -194,8 +194,8 @@ impl Agent { self.execution_context_stack.last_mut().unwrap() } - pub fn get_host_storage(&self) -> RefMut { - self.host_hooks.get_storage_handle() + pub fn get_host_data(&self) -> &dyn Any { + self.host_hooks.get_host_data() } } diff --git a/nova_vm/src/ecmascript/execution/default_host_hooks.rs b/nova_vm/src/ecmascript/execution/default_host_hooks.rs index 4de48a5cf..471dc7b00 100644 --- a/nova_vm/src/ecmascript/execution/default_host_hooks.rs +++ b/nova_vm/src/ecmascript/execution/default_host_hooks.rs @@ -29,8 +29,4 @@ impl HostHooks for DefaultHostHooks { fn enqueue_promise_job(&self, _job: Job) { // No-op } - - fn get_storage_handle(&self) -> std::cell::RefMut { - panic!("Storage is not supported.") - } } From 6cb18b9a413b802775ccb4089ca52a5622da5102 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 13:22:41 +0200 Subject: [PATCH 06/13] doc: Doc comment for `HostHooks::get_host_data` --- nova_vm/src/ecmascript/execution/agent.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index c1ff9f229..4c84e9984 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -123,6 +123,9 @@ pub trait HostHooks: std::fmt::Debug { // The default implementation of HostPromiseRejectionTracker is to return unused. } + /// Get access to the Host data, useful to share state between calls of built-in functions. + /// + /// Note: This will panic if not implemented manually. fn get_host_data(&self) -> &dyn Any { unimplemented!() } From 193de2e4af31f636e6998d553566f7b698cf753c Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 13:25:10 +0200 Subject: [PATCH 07/13] doc: Duplicate doc comment --- nova_vm/src/ecmascript/execution/agent.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 4c84e9984..e10231875 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -197,6 +197,9 @@ impl Agent { self.execution_context_stack.last_mut().unwrap() } + /// Get access to the Host data, useful to share state between calls of built-in functions. + /// + /// Note: This will panic if not implemented manually. pub fn get_host_data(&self) -> &dyn Any { self.host_hooks.get_host_data() } From bad614bf107881e788bc5c8e367ceb304125d1de Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 23 Jul 2024 13:31:31 +0200 Subject: [PATCH 08/13] chore: trailing new line --- nova_cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_cli/Cargo.toml b/nova_cli/Cargo.toml index 92ed069f3..8e5f9f591 100644 --- a/nova_cli/Cargo.toml +++ b/nova_cli/Cargo.toml @@ -14,4 +14,4 @@ oxc_ast = { workspace = true } oxc_parser = { workspace = true } oxc_semantic = { workspace = true } oxc_span = { workspace = true } -oxc_diagnostics = { workspace = true } \ No newline at end of file +oxc_diagnostics = { workspace = true } From 295837e02763a142b20376650429ca2fe27f69ae Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 24 Jul 2024 15:44:37 +0200 Subject: [PATCH 09/13] feat: Hold `GlobalValue` across GC calls --- nova_vm/src/ecmascript/builtins.rs | 1 + .../builtins/control_abstraction_objects.rs | 2 +- .../promise_objects.rs | 6 +-- .../promise_abstract_operations.rs | 8 +-- .../promise_capability_records.rs | 8 +-- nova_vm/src/ecmascript/types.rs | 6 +-- nova_vm/src/ecmascript/types/language.rs | 2 + .../ecmascript/types/language/global_value.rs | 54 +++++++++++++++++++ nova_vm/src/heap.rs | 2 +- nova_vm/src/heap/heap_gc.rs | 14 +++-- 10 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 nova_vm/src/ecmascript/types/language/global_value.rs diff --git a/nova_vm/src/ecmascript/builtins.rs b/nova_vm/src/ecmascript/builtins.rs index 4791edeca..5daa34c17 100644 --- a/nova_vm/src/ecmascript/builtins.rs +++ b/nova_vm/src/ecmascript/builtins.rs @@ -54,4 +54,5 @@ pub use builtin_function::{ BuiltinFunctionArgs, BuiltinGetter, ConstructorFn, RegularFn as JsFunction, RegularFn, }; pub(crate) use builtin_function::{BuiltinIntrinsic, BuiltinIntrinsicConstructor}; +pub use control_abstraction_objects::*; pub(crate) use ecmascript_function::*; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects.rs index 6308b216c..863f3d1b0 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects.rs @@ -8,4 +8,4 @@ pub(crate) mod async_generator_objects; pub(crate) mod generator_function_objects; pub(crate) mod generator_objects; pub(crate) mod iteration; -pub(crate) mod promise_objects; +pub mod promise_objects; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs index 838f90253..59b4ff8bd 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs @@ -2,6 +2,6 @@ // 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/. -pub(crate) mod promise_abstract_operations; -pub(crate) mod promise_constructor; -pub(crate) mod promise_prototype; +pub mod promise_abstract_operations; +pub mod promise_constructor; +pub mod promise_prototype; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs index b97e014c3..18ebe0695 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs @@ -2,7 +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/. -pub(crate) mod promise_capability_records; -pub(crate) mod promise_jobs; -pub(crate) mod promise_reaction_records; -pub(crate) mod promise_resolving_functions; +pub mod promise_capability_records; +pub mod promise_jobs; +pub mod promise_reaction_records; +pub mod promise_resolving_functions; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs index 8c022c07c..9a2c20dcc 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs @@ -38,7 +38,7 @@ use super::promise_jobs::new_promise_resolve_thenable_job; /// state is Fulfilled or Rejected. If true, it also counts as already resolved /// if it's Pending but `is_resolved` is set to true. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct PromiseCapability { +pub struct PromiseCapability { promise: Promise, must_be_unresolved: bool, } @@ -47,18 +47,18 @@ impl PromiseCapability { /// [27.2.1.5 NewPromiseCapability ( C )](https://tc39.es/ecma262/#sec-newpromisecapability) /// NOTE: Our implementation doesn't take C as a parameter, since we don't /// yet support promise subclassing. - pub(crate) fn new(agent: &mut Agent) -> Self { + pub fn new(agent: &mut Agent) -> Self { Self::from_promise(agent.heap.create(PromiseHeapData::default()), true) } - pub(crate) fn from_promise(promise: Promise, must_be_unresolved: bool) -> Self { + pub fn from_promise(promise: Promise, must_be_unresolved: bool) -> Self { Self { promise, must_be_unresolved, } } - pub(crate) fn promise(&self) -> Promise { + pub fn promise(&self) -> Promise { self.promise } diff --git a/nova_vm/src/ecmascript/types.rs b/nova_vm/src/ecmascript/types.rs index 4ab2486bc..bba3c6abd 100644 --- a/nova_vm/src/ecmascript/types.rs +++ b/nova_vm/src/ecmascript/types.rs @@ -7,9 +7,9 @@ mod spec; pub(crate) use language::*; pub use language::{ - bigint, BigInt, Function, HeapNumber, HeapString, InternalMethods, InternalSlots, IntoFunction, - IntoNumeric, IntoObject, IntoPrimitive, IntoValue, Number, Numeric, Object, OrdinaryObject, - Primitive, PropertyKey, String, Symbol, Value, + bigint, BigInt, Function, GlobalValue, HeapNumber, HeapString, InternalMethods, InternalSlots, + IntoFunction, IntoNumeric, IntoObject, IntoPrimitive, IntoValue, Number, Numeric, Object, + OrdinaryObject, Primitive, PropertyKey, String, Symbol, Value, }; pub use spec::PropertyDescriptor; pub(crate) use spec::*; diff --git a/nova_vm/src/ecmascript/types/language.rs b/nova_vm/src/ecmascript/types/language.rs index 2eb7541ec..e7112f8e6 100644 --- a/nova_vm/src/ecmascript/types/language.rs +++ b/nova_vm/src/ecmascript/types/language.rs @@ -4,6 +4,7 @@ pub mod bigint; mod function; +mod global_value; mod into_numeric; mod into_primitive; mod into_value; @@ -20,6 +21,7 @@ pub(crate) use function::{ BoundFunctionHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData, }; pub use function::{Function, IntoFunction}; +pub use global_value::GlobalValue; pub use into_numeric::IntoNumeric; pub use into_primitive::IntoPrimitive; pub use into_value::IntoValue; diff --git a/nova_vm/src/ecmascript/types/language/global_value.rs b/nova_vm/src/ecmascript/types/language/global_value.rs new file mode 100644 index 000000000..6fcc79fec --- /dev/null +++ b/nova_vm/src/ecmascript/types/language/global_value.rs @@ -0,0 +1,54 @@ +use crate::ecmascript::execution::Agent; + +use super::Value; + +/// Holds the position of a global value in the heap. +/// The main advantage of this is that the garbage collector will not move the elements +/// of the global valus vector, making it possible to maintain a position of any value across +/// multiple calls of the garbage collector. +/// +/// This might be useful to resolve promises asynchronously for example. +pub struct GlobalValue(usize); + +impl GlobalValue { + /// Register a value as global. + pub fn new(agent: &mut Agent, value: impl Into) -> Self { + let available_index = Self::find_available_index(agent); + agent + .heap + .globals + .insert(available_index, Some(value.into())); + Self(available_index) + } + + /// Unregister this global value. + #[must_use] + pub fn take(&self, agent: &mut Agent) -> Value { + // Leave a `None` in the index and return the value + agent.heap.globals.get_mut(self.0).unwrap().take().unwrap() + } + + /// Find an available index in the global values vector. + fn find_available_index(agent: &mut Agent) -> usize { + let mut available_index = 0; + + loop { + // Index has been freed previously + if let Some(None) = agent.heap.globals.get(available_index) { + break; + } + + // Global values vector is full, the capacity must increase + if available_index == agent.heap.globals.len() { + agent.heap.globals.push(None); + available_index = agent.heap.globals.len() - 1; + break; + } + + // Advance the index + available_index += 1; + } + + available_index + } +} diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index c1bdb8411..00ba46be6 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -85,7 +85,7 @@ pub struct Heap { pub environments: Environments, pub errors: Vec>, pub finalization_registrys: Vec>, - pub globals: Vec, + pub globals: Vec>, pub maps: Vec>, pub modules: Vec>, pub numbers: Vec>, diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 2330e738b..6989f4f81 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -59,9 +59,17 @@ pub fn heap_gc(heap: &mut Heap) { let mut bits = HeapBits::new(heap); let mut queues = WorkQueues::new(heap); - heap.globals.iter().for_each(|&value| { - queues.push_value(value); + let mut last_filled_global_value = None; + heap.globals.iter().enumerate().for_each(|(i, &value)| { + if let Some(value) = value { + queues.push_value(value); + last_filled_global_value = Some(i); + } }); + // Remove as many `None` global values without moving any `Some(Value)` values. + if let Some(last_filled_global_value) = last_filled_global_value { + heap.globals.drain(last_filled_global_value..); + } while !queues.is_empty() { let Heap { @@ -960,7 +968,7 @@ fn test_heap_gc() { assert!(heap.objects.is_empty()); let obj = Value::Object(heap.create_null_object(&[])); println!("Object: {:#?}", obj); - heap.globals.push(obj); + heap.globals.push(Some(obj)); heap_gc(&mut heap); println!("Objects: {:#?}", heap.objects); assert_eq!(heap.objects.len(), 1); From a3b1327dc43f84a6a43333e3d4c86a83e3bf5797 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 24 Jul 2024 15:57:56 +0200 Subject: [PATCH 10/13] fix: Typo --- nova_vm/src/ecmascript/types/language/global_value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/types/language/global_value.rs b/nova_vm/src/ecmascript/types/language/global_value.rs index 6fcc79fec..82c1064db 100644 --- a/nova_vm/src/ecmascript/types/language/global_value.rs +++ b/nova_vm/src/ecmascript/types/language/global_value.rs @@ -4,7 +4,7 @@ use super::Value; /// Holds the position of a global value in the heap. /// The main advantage of this is that the garbage collector will not move the elements -/// of the global valus vector, making it possible to maintain a position of any value across +/// of the global values vector, making it possible to maintain a position of any value across /// multiple calls of the garbage collector. /// /// This might be useful to resolve promises asynchronously for example. From 403b209c6a6c9f5cab01fd33d9757158b364a394 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 24 Jul 2024 16:09:48 +0200 Subject: [PATCH 11/13] fix: Properly clear unused global values slots in the heap gc --- nova_vm/src/heap/heap_gc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 6989f4f81..6d7317348 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -68,7 +68,7 @@ pub fn heap_gc(heap: &mut Heap) { }); // Remove as many `None` global values without moving any `Some(Value)` values. if let Some(last_filled_global_value) = last_filled_global_value { - heap.globals.drain(last_filled_global_value..); + heap.globals.drain(last_filled_global_value + 1..); } while !queues.is_empty() { From 531a26363531daed7e9a3aa1a1bf7045e879d8e3 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Wed, 24 Jul 2024 23:26:53 +0200 Subject: [PATCH 12/13] feat: derive debug and partial eq for GlobalValue --- nova_vm/src/ecmascript/types/language/global_value.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/nova_vm/src/ecmascript/types/language/global_value.rs b/nova_vm/src/ecmascript/types/language/global_value.rs index 82c1064db..2142bf646 100644 --- a/nova_vm/src/ecmascript/types/language/global_value.rs +++ b/nova_vm/src/ecmascript/types/language/global_value.rs @@ -8,6 +8,7 @@ use super::Value; /// multiple calls of the garbage collector. /// /// This might be useful to resolve promises asynchronously for example. +#[derive(Debug, PartialEq)] pub struct GlobalValue(usize); impl GlobalValue { From 36d282662f9c703ec55eab8efc6511baab14b35d Mon Sep 17 00:00:00 2001 From: marc2332 Date: Thu, 25 Jul 2024 16:26:24 +0200 Subject: [PATCH 13/13] feat: Let runtimes handle imports --- nova_vm/src/ecmascript/execution/agent.rs | 11 ++++++++++- nova_vm/src/ecmascript/execution/execution_context.rs | 4 ++-- nova_vm/src/ecmascript/execution/realm.rs | 2 +- nova_vm/src/ecmascript/scripts_and_modules.rs | 2 +- nova_vm/src/ecmascript/scripts_and_modules/script.rs | 4 ++-- .../syntax_directed_operations/scope_analysis.rs | 4 ++-- nova_vm/src/engine/bytecode/executable.rs | 9 ++++++++- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index e10231875..a380f6813 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -7,6 +7,8 @@ //! - This is inspired by and/or copied from Kiesel engine: //! Copyright (c) 2023-2024 Linus Groh +use oxc_ast::ast; + use super::{ environments::get_identifier_reference, EnvironmentIndex, ExecutionContext, Realm, RealmIdentifier, @@ -123,6 +125,13 @@ pub trait HostHooks: std::fmt::Debug { // The default implementation of HostPromiseRejectionTracker is to return unused. } + /// Handle import declarations. + /// + /// Note: This will panic if not implemented manually. + fn import_module(&self, _import: &ast::ImportDeclaration<'_>, _agent: &mut Agent) { + unimplemented!() + } + /// Get access to the Host data, useful to share state between calls of built-in functions. /// /// Note: This will panic if not implemented manually. @@ -189,7 +198,7 @@ impl Agent { JsError(self.create_exception(kind, message)) } - pub(crate) fn running_execution_context(&self) -> &ExecutionContext { + pub fn running_execution_context(&self) -> &ExecutionContext { self.execution_context_stack.last().unwrap() } diff --git a/nova_vm/src/ecmascript/execution/execution_context.rs b/nova_vm/src/ecmascript/execution/execution_context.rs index 6a77d5be8..8275b3e37 100644 --- a/nova_vm/src/ecmascript/execution/execution_context.rs +++ b/nova_vm/src/ecmascript/execution/execution_context.rs @@ -48,12 +48,12 @@ pub(crate) struct ECMAScriptCodeEvaluationState { /// references to the running execution context in this specification denote /// the running execution context of the surrounding agent. #[derive(Debug)] -pub(crate) struct ExecutionContext { +pub struct ExecutionContext { /// ### code evaluation state /// /// Any state needed to perform, suspend, and resume evaluation of the code /// associated with this execution context. - pub ecmascript_code: Option, + pub(crate) ecmascript_code: Option, /// ### Function /// diff --git a/nova_vm/src/ecmascript/execution/realm.rs b/nova_vm/src/ecmascript/execution/realm.rs index e5864b70d..1bebac2a8 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -152,7 +152,7 @@ pub struct Realm { /// /// Field reserved for use by hosts that need to associate additional /// information with a Realm Record. - pub(crate) host_defined: Option<&'static dyn Any>, + pub host_defined: Option<&'static dyn Any>, } unsafe impl Send for Realm {} diff --git a/nova_vm/src/ecmascript/scripts_and_modules.rs b/nova_vm/src/ecmascript/scripts_and_modules.rs index 0ec517862..34f65a4c4 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules.rs @@ -12,7 +12,7 @@ pub mod module; pub mod script; #[derive(Debug, Clone, Copy)] -pub(crate) enum ScriptOrModule { +pub enum ScriptOrModule { Script(ScriptIdentifier), Module(Module), } diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index 4cefebe49..fc59b44d9 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -41,7 +41,7 @@ use std::{ pub type HostDefined = &'static mut dyn Any; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct ScriptIdentifier(u32, PhantomData