diff --git a/.vscode/tasks.json b/.vscode/tasks.json index dcb961c709c..fdba309a472 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -37,7 +37,7 @@ "type": "process", "label": "Run JS file", "command": "cargo", - "args": ["run", "--bin", "boa", "${file}"], + "args": ["run", "--bin", "boa", "${workspaceFolder}/debug/script.js"], "group": { "kind": "build", "isDefault": true diff --git a/CHANGELOG.md b/CHANGELOG.md index 74306691937..3d75c8e7bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## What's Changed -# [0.20.0 (2024-09-11)](https://github.com/boa-dev/boa/compare/v0.19.1...v0.20.0) +# [0.20.0 (2024-12-5)](https://github.com/boa-dev/boa/compare/v0.19.1...v0.20.0) ### Feature Enhancements @@ -61,6 +61,7 @@ - Update night build's rename binary step by @nekevss in https://github.com/boa-dev/boa/pull/4032 - Use upload-rust-binary-action for nightly release by @nekevss in https://github.com/boa-dev/boa/pull/4040 - Fix `ref` value in nightly and add target to nightly release by @nekevss in https://github.com/boa-dev/boa/pull/4042 +- Reduce environment allocations by @raskad in https://github.com/boa-dev/boa/pull/4002 ### Other Changes diff --git a/Cargo.lock b/Cargo.lock index fb353c822b1..2de2a9dfadc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "boa_ast" -version = "0.19.0" +version = "0.20.0" dependencies = [ "arbitrary", "bitflags 2.6.0", @@ -329,7 +329,7 @@ dependencies = [ [[package]] name = "boa_cli" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_engine", "boa_gc", @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "boa_engine" -version = "0.19.0" +version = "0.20.0" dependencies = [ "arrayvec", "bitflags 2.6.0", @@ -419,7 +419,7 @@ dependencies = [ [[package]] name = "boa_examples" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_ast", "boa_engine", @@ -435,7 +435,7 @@ dependencies = [ [[package]] name = "boa_gc" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_macros", "boa_profiler", @@ -447,7 +447,7 @@ dependencies = [ [[package]] name = "boa_icu_provider" -version = "0.19.0" +version = "0.20.0" dependencies = [ "icu_casemap", "icu_collator", @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "boa_interner" -version = "0.19.0" +version = "0.20.0" dependencies = [ "arbitrary", "boa_gc", @@ -483,7 +483,7 @@ dependencies = [ [[package]] name = "boa_interop" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_engine", "boa_gc", @@ -493,7 +493,7 @@ dependencies = [ [[package]] name = "boa_macros" -version = "0.19.0" +version = "0.20.0" dependencies = [ "proc-macro2", "quote", @@ -503,7 +503,7 @@ dependencies = [ [[package]] name = "boa_macros_tests" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_engine", "trybuild", @@ -511,7 +511,7 @@ dependencies = [ [[package]] name = "boa_parser" -version = "0.19.0" +version = "0.20.0" dependencies = [ "bitflags 2.6.0", "boa_ast", @@ -528,7 +528,7 @@ dependencies = [ [[package]] name = "boa_profiler" -version = "0.19.0" +version = "0.20.0" dependencies = [ "measureme", "once_cell", @@ -537,7 +537,7 @@ dependencies = [ [[package]] name = "boa_runtime" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_engine", "boa_gc", @@ -550,7 +550,7 @@ dependencies = [ [[package]] name = "boa_string" -version = "0.19.0" +version = "0.20.0" dependencies = [ "fast-float2", "paste", @@ -561,7 +561,7 @@ dependencies = [ [[package]] name = "boa_tester" -version = "0.19.0" +version = "0.20.0" dependencies = [ "bitflags 2.6.0", "boa_engine", @@ -584,7 +584,7 @@ dependencies = [ [[package]] name = "boa_wasm" -version = "0.19.0" +version = "0.20.0" dependencies = [ "boa_engine", "console_error_panic_hook", @@ -1386,7 +1386,7 @@ dependencies = [ [[package]] name = "gen-icu4x-data" -version = "0.19.0" +version = "0.20.0" dependencies = [ "icu_casemap", "icu_collator", diff --git a/Cargo.toml b/Cargo.toml index 21a34ab6dc3..73ad8aa8c53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ exclude = [ [workspace.package] edition = "2021" -version = "0.19.0" +version = "0.20.0" rust-version = "1.82.0" authors = ["boa-dev"] repository = "https://github.com/boa-dev/boa" @@ -33,17 +33,17 @@ description = "Boa is a Javascript lexer, parser and compiler written in Rust. C [workspace.dependencies] # Repo Crates -boa_ast = { version = "~0.19.0", path = "core/ast" } -boa_engine = { version = "~0.19.0", path = "core/engine" } -boa_gc = { version = "~0.19.0", path = "core/gc" } -boa_icu_provider = { version = "~0.19.0", path = "core/icu_provider" } -boa_interner = { version = "~0.19.0", path = "core/interner" } -boa_interop = { version = "~0.19.0", path = "core/interop" } -boa_macros = { version = "~0.19.0", path = "core/macros" } -boa_parser = { version = "~0.19.0", path = "core/parser" } -boa_profiler = { version = "~0.19.0", path = "core/profiler" } -boa_runtime = { version = "~0.19.0", path = "core/runtime" } -boa_string = { version = "~0.19.0", path = "core/string" } +boa_ast = { version = "~0.20.0", path = "core/ast" } +boa_engine = { version = "~0.20.0", path = "core/engine" } +boa_gc = { version = "~0.20.0", path = "core/gc" } +boa_icu_provider = { version = "~0.20.0", path = "core/icu_provider" } +boa_interner = { version = "~0.20.0", path = "core/interner" } +boa_interop = { version = "~0.20.0", path = "core/interop" } +boa_macros = { version = "~0.20.0", path = "core/macros" } +boa_parser = { version = "~0.20.0", path = "core/parser" } +boa_profiler = { version = "~0.20.0", path = "core/profiler" } +boa_runtime = { version = "~0.20.0", path = "core/runtime" } +boa_string = { version = "~0.20.0", path = "core/string" } # Shared deps arbitrary = "1" diff --git a/core/engine/src/builtins/builder.rs b/core/engine/src/builtins/builder.rs index 4533190da65..0a21b2eb6a7 100644 --- a/core/engine/src/builtins/builder.rs +++ b/core/engine/src/builtins/builder.rs @@ -3,7 +3,7 @@ use crate::{ native_function::{NativeFunctionObject, NativeFunctionPointer}, object::{ shape::{property_table::PropertyTableInner, slot::SlotAttributes}, - FunctionBinding, JsFunction, JsPrototype, CONSTRUCTOR, PROTOTYPE, + FunctionBinding, JsFunction, JsPrototype, LazyBuiltIn, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -392,12 +392,25 @@ impl BuiltInConstructorWithPrototype<'_> { } let mut object = self.object.borrow_mut(); - let function = object - .downcast_mut::() - .expect("Builtin must be a function object"); - function.f = NativeFunction::from_fn_ptr(self.function); - function.constructor = Some(ConstructorKind::Base); - function.realm = Some(self.realm.clone()); + if object.is::() { + let function = object + .downcast_mut::() + .expect("Builtin must be a function object"); + function.f = NativeFunction::from_fn_ptr(self.function); + function.constructor = Some(ConstructorKind::Base); + function.realm = Some(self.realm.clone()); + } else if object.is::() { + let lazy = object + .downcast_mut::() + .expect("Builtin must be a lazy object"); + lazy.set_constructor( + NativeFunction::from_fn_ptr(self.function), + self.realm.clone(), + ); + } else { + unreachable!("The object must be a function or a lazy object"); + } + object .properties_mut() .shape diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 94751379289..8d1b2c90b1c 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -26,7 +26,7 @@ use crate::{ get_prototype_from_constructor, CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }, - JsData, JsFunction, JsObject, PrivateElement, PrivateName, + JsData, JsFunction, JsObject, LazyBuiltIn, PrivateElement, PrivateName, }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -845,8 +845,7 @@ impl BuiltInFunctionObject { return Err(JsNativeError::typ().with_message("not a function").into()); }; - let object_borrow = object.borrow(); - if object_borrow.is::() { + if object.is::() || object.is::() { let name = { // Is there a case here where if there is no name field on a value // name should default to None? Do all functions have names set? @@ -860,10 +859,11 @@ impl BuiltInFunctionObject { return Ok( js_string!(js_str!("function "), &name, js_str!("() { [native code] }")).into(), ); - } else if object_borrow.is::() || object_borrow.is::() { + } else if object.is::() || object.is::() { return Ok(js_string!("function () { [native code] }").into()); } + let object_borrow = object.borrow(); let function = object_borrow .downcast_ref::() .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; diff --git a/core/engine/src/builtins/mod.rs b/core/engine/src/builtins/mod.rs index be52220e254..565345ebda7 100644 --- a/core/engine/src/builtins/mod.rs +++ b/core/engine/src/builtins/mod.rs @@ -208,7 +208,6 @@ impl Realm { ForInIterator::init(self); Math::init(self); Json::init(self); - Array::init(self); ArrayIterator::init(self); Proxy::init(self); ArrayBuffer::init(self); diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index b1056f71bb1..26e6e674702 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -1,18 +1,22 @@ //! Data structures that contain intrinsic objects and constructors. -use boa_gc::{Finalize, Trace}; - use crate::{ - builtins::{iterable::IteratorPrototypes, uri::UriFunctions, Array, OrdinaryObject}, + builtins::{ + function::ConstructorKind, iterable::IteratorPrototypes, uri::UriFunctions, Array, + IntrinsicObject, OrdinaryObject, + }, js_string, + native_function::NativeFunctionObject, object::{ internal_methods::immutable_prototype::IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, shape::{shared_shape::template::ObjectTemplate, RootShape}, - JsFunction, JsObject, Object, CONSTRUCTOR, PROTOTYPE, + BuiltinKind, JsFunction, JsObject, LazyBuiltIn, Object, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyKey}, - JsSymbol, + realm::{Realm, RealmInner}, + JsSymbol, JsValue, NativeFunction, }; +use boa_gc::{Finalize, Trace, WeakGc}; #[cfg(feature = "intl")] use crate::builtins::intl::Intl; @@ -39,8 +43,8 @@ impl Intrinsics { /// To initialize all the intrinsics with their spec properties, see [`Realm::initialize`]. /// /// [`Realm::initialize`]: crate::realm::Realm::initialize - pub(crate) fn uninit(root_shape: &RootShape) -> Option { - let constructors = StandardConstructors::default(); + pub(crate) fn uninit(root_shape: &RootShape, realm_inner: &WeakGc) -> Option { + let constructors = StandardConstructors::new(realm_inner); let templates = ObjectTemplates::new(root_shape, &constructors); Some(Self { @@ -94,6 +98,46 @@ impl StandardConstructor { } } + /// Build a constructor that is lazily initialized. + /// Both the constructor and the prototype are lazily initialized. + /// + /// For example: Both the initiation of the `Array` and accessing its prototype will fire `init` once. + /// This means that the actual creation of the array and the retrieval of its + /// prototype are deferred until they are actually needed. + /// + /// ## Example + /// + /// ```javascript + /// // Lazy initiation of the Array + /// let array = new Array(10); // Creates an array with 10 empty slots + /// + /// // Lazy access to the prototype + /// let prototype = Object.getPrototypeOf(array); + /// + /// // Accessing an index in the array + /// array[0] = 42; // Sets the first element to 42 + /// console.log(array[0]); // Logs 42 + /// ``` + fn lazy_array(init: fn(&Realm) -> (), realm_inner: &WeakGc) -> Self { + let obj: JsObject = JsObject::new_unique( + None, + LazyBuiltIn { + init_and_realm: Some((init, realm_inner.clone())), + kind: BuiltinKind::Function(NativeFunctionObject { + f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())), + constructor: Some(ConstructorKind::Base), + realm: None, + }), + }, + ); + let constructor = JsFunction::from_object_unchecked(obj.clone().upcast()); + + Self { + constructor: constructor.clone(), + prototype: JsObject::lazy_array_prototype(obj), + } + } + /// Build a constructor with a defined prototype. fn with_prototype(prototype: JsObject) -> Self { Self { @@ -202,8 +246,8 @@ pub struct StandardConstructors { calendar: StandardConstructor, } -impl Default for StandardConstructors { - fn default() -> Self { +impl StandardConstructors { + fn new(realm_inner: &WeakGc) -> Self { Self { object: StandardConstructor::with_prototype(JsObject::from_object_and_vtable( Object::::default(), @@ -218,7 +262,7 @@ impl Default for StandardConstructors { }, async_function: StandardConstructor::default(), generator_function: StandardConstructor::default(), - array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, Array)), + array: StandardConstructor::lazy_array(Array::init, realm_inner), bigint: StandardConstructor::default(), number: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, 0.0)), boolean: StandardConstructor::with_prototype(JsObject::from_proto_and_data( diff --git a/core/engine/src/native_function.rs b/core/engine/src/native_function.rs index 721f53291fd..38858533753 100644 --- a/core/engine/src/native_function.rs +++ b/core/engine/src/native_function.rs @@ -342,6 +342,29 @@ pub(crate) fn native_function_call( obj: &JsObject, argument_count: usize, context: &mut Context, +) -> JsResult { + let native_function = &obj + .clone() + .downcast::() + .expect("the object should be a native function object") + .borrow_mut() + .data + .clone(); + native_function_call_inner(obj, native_function, argument_count, context) +} + +/// Call this object. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +#[inline] +pub(crate) fn native_function_call_inner( + obj: &JsObject, + native_function: &NativeFunctionObject, + argument_count: usize, + context: &mut Context, ) -> JsResult { let args = context.vm.pop_n_values(argument_count); let _func = context.vm.pop(); @@ -356,10 +379,7 @@ pub(crate) fn native_function_call( f: function, constructor, realm, - } = obj - .downcast_ref::() - .expect("the object should be a native function object") - .clone(); + } = native_function.clone(); let mut realm = realm.unwrap_or_else(|| context.realm().clone()); @@ -380,7 +400,6 @@ pub(crate) fn native_function_call( Ok(CallValue::Complete) } - /// Construct an instance of this object with the specified arguments. /// /// # Panics @@ -391,20 +410,33 @@ fn native_function_construct( obj: &JsObject, argument_count: usize, context: &mut Context, +) -> JsResult { + native_function_construct_inner( + &obj.downcast_ref::() + .expect("the object should be a native function object") + .clone(), + obj.clone(), + argument_count, + context, + ) +} + +#[inline] +pub(crate) fn native_function_construct_inner( + native_function: &NativeFunctionObject, + this_function_object: JsObject, + argument_count: usize, + context: &mut Context, ) -> JsResult { // We technically don't need this since native functions don't push any new frames to the // vm, but we'll eventually have to combine the native stack with the vm stack. context.check_runtime_limits()?; - let this_function_object = obj.clone(); let NativeFunctionObject { f: function, constructor, realm, - } = obj - .downcast_ref::() - .expect("the object should be a native function object") - .clone(); + } = native_function.clone(); let mut realm = realm.unwrap_or_else(|| context.realm().clone()); diff --git a/core/engine/src/object/builtins/lazy_array_prototype.rs b/core/engine/src/object/builtins/lazy_array_prototype.rs new file mode 100644 index 00000000000..2db1e2dd937 --- /dev/null +++ b/core/engine/src/object/builtins/lazy_array_prototype.rs @@ -0,0 +1,226 @@ +use crate::{ + object::{ + internal_methods::{ + non_existant_call, non_existant_construct, ordinary_define_own_property, + ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_get_prototype_of, + ordinary_has_property, ordinary_is_extensible, ordinary_own_property_keys, + ordinary_prevent_extensions, ordinary_set, ordinary_set_prototype_of, ordinary_try_get, + InternalMethodContext, InternalObjectMethods, + }, + JsPrototype, + }, + property::{PropertyDescriptor, PropertyKey}, + Context, JsData, JsObject, JsResult, JsValue, +}; +use boa_gc::{Finalize, Trace}; + +use super::LazyBuiltIn; + +/// The `LazyArrayPrototype` struct is responsible for ensuring that the prototype +/// of a constructor is lazily initialized. In JavaScript, constructors have a +/// `prototype` property that points to an object which is used as the prototype +/// for instances created by that constructor. +/// +/// The `LazyArrayPrototype` struct is used within `JsObject` (e.g., `JsObject`) +/// to defer the creation of the prototype object until it is actually needed. +/// This lazy initialization helps improve performance by avoiding the creation +/// of the prototype object until it is necessary. +/// +/// Each `LazyArrayPrototype` instance points to the constructor's `lazyBuiltin` (via its +/// object) and triggers the initialization of the prototype if any methods on +/// the prototype are called. + +#[derive(Clone, Trace, Finalize, Debug)] +#[allow(clippy::type_complexity)] +pub struct LazyArrayPrototype { + pub(crate) constructor: JsObject, +} + +// Implement the trait for JsData by overriding all internal_methods by calling init on the LazyBuiltIn associated with this prototype +impl JsData for LazyArrayPrototype { + fn internal_methods(&self) -> &'static InternalObjectMethods { + &LAZY_INTERNAL_METHODS + } +} + +pub(crate) static LAZY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __get_prototype_of__: lazy_get_prototype_of, + __set_prototype_of__: lazy_set_prototype_of, + __is_extensible__: lazy_is_extensible, + __prevent_extensions__: lazy_prevent_extensions, + __get_own_property__: lazy_get_own_property, + __define_own_property__: lazy_define_own_property, + __has_property__: lazy_has_property, + __try_get__: lazy_try_get, + __get__: lazy_get, + __set__: lazy_set, + __delete__: lazy_delete, + __own_property_keys__: lazy_own_property_keys, + __call__: non_existant_call, + __construct__: non_existant_construct, +}; + +pub(crate) fn lazy_get_prototype_of( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_get_prototype_of(obj, context) +} + +pub(crate) fn lazy_set_prototype_of( + obj: &JsObject, + prototype: JsPrototype, + context: &mut Context, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_set_prototype_of(obj, prototype, context) +} +pub(crate) fn lazy_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_is_extensible(obj, context) +} + +pub(crate) fn lazy_prevent_extensions(obj: &JsObject, context: &mut Context) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_prevent_extensions(obj, context) +} + +pub(crate) fn lazy_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_get_own_property(obj, key, context) +} + +pub(crate) fn lazy_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + + ordinary_define_own_property(obj, key, desc, context) +} + +pub(crate) fn lazy_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_has_property(obj, key, context) +} + +pub(crate) fn lazy_try_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + + ordinary_try_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_set(obj, key, value, receiver, context) +} + +pub(crate) fn lazy_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_delete(obj, key, context) +} + +pub(crate) fn lazy_own_property_keys( + obj: &JsObject, + context: &mut Context, +) -> JsResult> { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + + ordinary_own_property_keys(obj, context) +} diff --git a/core/engine/src/object/builtins/lazy_builtin.rs b/core/engine/src/object/builtins/lazy_builtin.rs new file mode 100644 index 00000000000..75b0bb95afb --- /dev/null +++ b/core/engine/src/object/builtins/lazy_builtin.rs @@ -0,0 +1,259 @@ +use crate::{ + builtins::function::ConstructorKind, + gc::custom_trace, + native_function::{ + native_function_call_inner, native_function_construct_inner, NativeFunctionObject, + }, + object::{ + internal_methods::{ + non_existant_call, non_existant_construct, ordinary_define_own_property, + ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_get_prototype_of, + ordinary_has_property, ordinary_is_extensible, ordinary_own_property_keys, + ordinary_prevent_extensions, ordinary_set, ordinary_set_prototype_of, ordinary_try_get, + CallValue, InternalMethodContext, InternalObjectMethods, + }, + JsPrototype, + }, + property::{PropertyDescriptor, PropertyKey}, + realm::{Realm, RealmInner}, + Context, JsData, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, +}; +use boa_gc::{Finalize, Trace, WeakGc}; + +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) enum BuiltinKind { + Function(NativeFunctionObject), + #[allow(dead_code)] + Ordinary, +} + +/// A Lazy Built-in data structure. Used for lazy initialization of builtins. +#[derive(Clone, Finalize, Debug)] +#[allow(clippy::type_complexity)] +pub struct LazyBuiltIn { + pub(crate) init_and_realm: Option<(fn(&Realm), WeakGc)>, + pub(crate) kind: BuiltinKind, +} + +impl LazyBuiltIn { + pub(crate) fn set_constructor(&mut self, function: NativeFunction, realm: Realm) { + if let BuiltinKind::Function(ref mut native_function) = self.kind { + native_function.f = function; + native_function.constructor = Some(ConstructorKind::Base); + native_function.realm = Some(realm); + } else { + panic!("Expected BuiltinKind::Function"); + } + } + + pub(crate) fn ensure_init(built_in: &JsObject) { + let borrowed_built_in = built_in.borrow_mut().data.init_and_realm.take(); + if let Some((init, realm_inner)) = borrowed_built_in { + let realm = &Realm { + inner: realm_inner.upgrade().expect("realm_inner not set"), + }; + init(realm); + } + } +} + +// SAFETY: Temporary, TODO move back to derived Trace when possible +unsafe impl Trace for LazyBuiltIn { + custom_trace!(this, mark, { + mark(&this.kind); + }); +} + +// Implement the trait for JsData by overriding all internal_methods by calling init before calling into the underlying internel_method +impl JsData for LazyBuiltIn { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static FUNCTION: InternalObjectMethods = InternalObjectMethods { + __construct__: lazy_construct, + __call__: lazy_call, + ..LAZY_INTERNAL_METHODS + }; + + if let BuiltinKind::Function(_) = self.kind { + return &FUNCTION; + } + + &LAZY_INTERNAL_METHODS + } +} + +pub(crate) static LAZY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __get_prototype_of__: lazy_get_prototype_of, + __set_prototype_of__: lazy_set_prototype_of, + __is_extensible__: lazy_is_extensible, + __prevent_extensions__: lazy_prevent_extensions, + __get_own_property__: lazy_get_own_property, + __define_own_property__: lazy_define_own_property, + __has_property__: lazy_has_property, + __try_get__: lazy_try_get, + __get__: lazy_get, + __set__: lazy_set, + __delete__: lazy_delete, + __own_property_keys__: lazy_own_property_keys, + __call__: non_existant_call, + __construct__: non_existant_construct, +}; + +pub(crate) fn lazy_get_prototype_of( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_get_prototype_of(obj, context) +} + +pub(crate) fn lazy_set_prototype_of( + obj: &JsObject, + prototype: JsPrototype, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_set_prototype_of(obj, prototype, context) +} +pub(crate) fn lazy_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_is_extensible(obj, context) +} + +pub(crate) fn lazy_prevent_extensions(obj: &JsObject, context: &mut Context) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_prevent_extensions(obj, context) +} + +pub(crate) fn lazy_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_get_own_property(obj, key, context) +} + +pub(crate) fn lazy_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + + ordinary_define_own_property(obj, key, desc, context) +} + +pub(crate) fn lazy_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_has_property(obj, key, context) +} + +pub(crate) fn lazy_try_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + + ordinary_try_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_set(obj, key, value, receiver, context) +} + +pub(crate) fn lazy_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + ordinary_delete(obj, key, context) +} + +pub(crate) fn lazy_own_property_keys( + obj: &JsObject, + context: &mut Context, +) -> JsResult> { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + + ordinary_own_property_keys(obj, context) +} + +pub(crate) fn lazy_construct( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + let kind = &builtin.borrow().data.kind.clone(); + + match kind { + BuiltinKind::Ordinary => Err(JsNativeError::typ() + .with_message("not a constructor") + .with_realm(context.realm().clone()) + .into()), + BuiltinKind::Function(cons) => { + // builtin needs to be dropped before calling the constructor to avoid a double borrow + drop(builtin); + native_function_construct_inner(cons, obj.clone(), argument_count, context) + } + } +} + +pub(crate) fn lazy_call( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let builtin: JsObject = obj.clone().downcast().expect("obj is not a Builtin"); + LazyBuiltIn::ensure_init(&builtin); + let kind = &builtin.borrow().data.kind.clone(); + match kind { + BuiltinKind::Ordinary => Err(JsNativeError::typ() + .with_message("not a constructor") + .with_realm(context.realm().clone()) + .into()), + BuiltinKind::Function(function) => { + // builtin needs to be dropped before calling the constructor to avoid a double borrow + drop(builtin); + native_function_call_inner(obj, function, argument_count, context) + } + } +} diff --git a/core/engine/src/object/builtins/lazy_prototype.rs b/core/engine/src/object/builtins/lazy_prototype.rs new file mode 100644 index 00000000000..f3b6040653f --- /dev/null +++ b/core/engine/src/object/builtins/lazy_prototype.rs @@ -0,0 +1,226 @@ +use crate::{ + object::{ + internal_methods::{ + non_existant_call, non_existant_construct, ordinary_define_own_property, + ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_get_prototype_of, + ordinary_has_property, ordinary_is_extensible, ordinary_own_property_keys, + ordinary_prevent_extensions, ordinary_set, ordinary_set_prototype_of, ordinary_try_get, + InternalMethodContext, InternalObjectMethods, + }, + JsPrototype, + }, + property::{PropertyDescriptor, PropertyKey}, + Context, JsData, JsObject, JsResult, JsValue, +}; +use boa_gc::{Finalize, Trace}; + +use super::LazyBuiltIn; + +/// The `LazyPrototype` struct is responsible for ensuring that the prototype +/// of a constructor is lazily initialized. In JavaScript, constructors have a +/// `prototype` property that points to an object which is used as the prototype +/// for instances created by that constructor. +/// +/// The `LazyPrototype` struct is used within `JsObject` (e.g., `JsObject`) +/// to defer the creation of the prototype object until it is actually needed. +/// This lazy initialization helps improve performance by avoiding the creation +/// of the prototype object until it is necessary. +/// +/// Each `LazyPrototype` instance points to the constructor's `lazyBuiltin` (via its +/// object) and triggers the initialization of the prototype if any methods on +/// the prototype are called. + +#[derive(Clone, Trace, Finalize, Debug)] +#[allow(clippy::type_complexity)] +pub struct LazyPrototype { + pub(crate) constructor: JsObject, +} + +// Implement the trait for JsData by overriding all internal_methods by calling init on the LazyBuiltIn associated with this prototype +impl JsData for LazyPrototype { + fn internal_methods(&self) -> &'static InternalObjectMethods { + &LAZY_INTERNAL_METHODS + } +} + +pub(crate) static LAZY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __get_prototype_of__: lazy_get_prototype_of, + __set_prototype_of__: lazy_set_prototype_of, + __is_extensible__: lazy_is_extensible, + __prevent_extensions__: lazy_prevent_extensions, + __get_own_property__: lazy_get_own_property, + __define_own_property__: lazy_define_own_property, + __has_property__: lazy_has_property, + __try_get__: lazy_try_get, + __get__: lazy_get, + __set__: lazy_set, + __delete__: lazy_delete, + __own_property_keys__: lazy_own_property_keys, + __call__: non_existant_call, + __construct__: non_existant_construct, +}; + +pub(crate) fn lazy_get_prototype_of( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_get_prototype_of(obj, context) +} + +pub(crate) fn lazy_set_prototype_of( + obj: &JsObject, + prototype: JsPrototype, + context: &mut Context, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_set_prototype_of(obj, prototype, context) +} +pub(crate) fn lazy_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_is_extensible(obj, context) +} + +pub(crate) fn lazy_prevent_extensions(obj: &JsObject, context: &mut Context) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_prevent_extensions(obj, context) +} + +pub(crate) fn lazy_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_get_own_property(obj, key, context) +} + +pub(crate) fn lazy_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + + ordinary_define_own_property(obj, key, desc, context) +} + +pub(crate) fn lazy_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_has_property(obj, key, context) +} + +pub(crate) fn lazy_try_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + + ordinary_try_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_get(obj, key, receiver, context) +} + +pub(crate) fn lazy_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_set(obj, key, value, receiver, context) +} + +pub(crate) fn lazy_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + ordinary_delete(obj, key, context) +} + +pub(crate) fn lazy_own_property_keys( + obj: &JsObject, + context: &mut Context, +) -> JsResult> { + let lazy_prototype: JsObject = obj + .clone() + .downcast::() + .expect("obj is not a Builtin"); + let lazy_built_in = &lazy_prototype.borrow_mut().data.constructor.clone(); + LazyBuiltIn::ensure_init(lazy_built_in); + + ordinary_own_property_keys(obj, context) +} diff --git a/core/engine/src/object/builtins/mod.rs b/core/engine/src/object/builtins/mod.rs index 923f5ad9fb1..5dded7e2f42 100644 --- a/core/engine/src/object/builtins/mod.rs +++ b/core/engine/src/object/builtins/mod.rs @@ -17,6 +17,9 @@ mod jsset; mod jsset_iterator; mod jssharedarraybuffer; mod jstypedarray; +mod lazy_array_prototype; +mod lazy_builtin; +mod lazy_prototype; pub use jsarray::*; pub use jsarraybuffer::*; @@ -33,3 +36,6 @@ pub use jsset::*; pub use jsset_iterator::*; pub use jssharedarraybuffer::*; pub use jstypedarray::*; +pub use lazy_array_prototype::*; +pub use lazy_builtin::*; +pub use lazy_prototype::*; diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index ac671752f3c..3cdff643c19 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -1117,7 +1117,7 @@ where Ok(default(realm.intrinsics().constructors()).prototype()) } -fn non_existant_call( +pub(crate) fn non_existant_call( _obj: &JsObject, _argument_count: usize, context: &mut Context, @@ -1128,7 +1128,7 @@ fn non_existant_call( .into()) } -fn non_existant_construct( +pub(crate) fn non_existant_construct( _obj: &JsObject, _argument_count: usize, context: &mut Context, diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index db02acd3c37..6b4fd55c69e 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -5,7 +5,7 @@ use super::{ internal_methods::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, shape::RootShape, - JsPrototype, NativeObject, Object, PrivateName, PropertyMap, + JsPrototype, LazyArrayPrototype, LazyBuiltIn, NativeObject, Object, PrivateName, PropertyMap, }; use crate::{ builtins::{ @@ -92,6 +92,12 @@ impl JsObject { } } + /// Creates a new lazy array prototype object from its inner object and its vtable. + /// This is used for built-in objects that are prototypes of Constructors. + pub(crate) fn lazy_array_prototype(constructor: JsObject) -> Self { + Self::from_proto_and_data(None, LazyArrayPrototype { constructor }) + } + /// Creates a new ordinary object with its prototype set to the `Object` prototype. /// /// This is equivalent to calling the specification's abstract operation @@ -280,6 +286,12 @@ impl JsObject { #[must_use] #[track_caller] pub fn is_array(&self) -> bool { + // The prototype of an array should be an exotic Array object. + // If its the lazyArrayPrototype we know its an array. + if self.is::() { + return true; + } + std::ptr::eq(self.vtable(), &ARRAY_EXOTIC_INTERNAL_METHODS) } @@ -928,6 +940,7 @@ impl Debug for JsObject { if !limiter.visited && !limiter.live { let ptr: *const _ = self.as_ref(); let ptr = ptr.cast::<()>(); + let borrow_flag = self.inner.object.flags.get(); let obj = self.borrow(); let kind = obj.data.type_name_of_value(); if self.is_callable() { @@ -943,10 +956,16 @@ impl Debug for JsObject { .unwrap_or_default(), }; - return f.write_fmt(format_args!("({:?}) {:?} 0x{:X}", kind, name, ptr as usize)); + return f.write_fmt(format_args!( + "({:?}) {:?} 0x{:X} {:?}", + kind, name, ptr as usize, borrow_flag + )); } - f.write_fmt(format_args!("({:?}) 0x{:X}", kind, ptr as usize)) + f.write_fmt(format_args!( + "({:?}) 0x{:X} {:?}", + kind, ptr as usize, borrow_flag + )) } else { f.write_str("{ ... }") } diff --git a/core/engine/src/realm.rs b/core/engine/src/realm.rs index b034420c2a9..50f65104b18 100644 --- a/core/engine/src/realm.rs +++ b/core/engine/src/realm.rs @@ -20,7 +20,7 @@ use crate::{ environments::DeclarativeEnvironment, module::Module, object::shape::RootShape, - HostDefined, JsNativeError, JsObject, JsResult, JsString, + HostDefined, JsObject, JsResult, JsString, }; use boa_gc::{Finalize, Gc, GcRef, GcRefCell, GcRefMut, Trace}; use boa_profiler::Profiler; @@ -30,7 +30,9 @@ use boa_profiler::Profiler; /// In the specification these are called Realm Records. #[derive(Clone, Trace, Finalize)] pub struct Realm { - inner: Gc, + /// The inner data of the realm, which includes the intrinsics, environment, + /// global object, and other realm-specific information. + pub inner: Gc, } impl Eq for Realm {} @@ -53,7 +55,12 @@ impl std::fmt::Debug for Realm { } #[derive(Trace, Finalize)] -struct Inner { + +/// The inner data of a Realm. +/// +/// This struct contains all the realm-specific information, including the intrinsics, +/// environment, global object, and other necessary data for the execution context. +pub struct RealmInner { intrinsics: Intrinsics, /// The global declarative environment of this realm. @@ -70,29 +77,43 @@ struct Inner { template_map: GcRefCell>, loaded_modules: GcRefCell>, host_classes: GcRefCell>, - host_defined: GcRefCell, } +#[allow(clippy::missing_fields_in_debug)] +impl std::fmt::Debug for RealmInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RealmInner") + .field("intrinsics", &self.intrinsics) + .field("environment", &self.environment) + .field("global_object", &self.global_object) + .field("global_this", &self.global_this) + .field("template_map", &self.template_map) + .field("loaded_modules", &self.loaded_modules) + .field("host_classes", &self.host_classes) + .finish() + } +} impl Realm { /// Create a new [`Realm`]. #[inline] pub fn create(hooks: &dyn HostHooks, root_shape: &RootShape) -> JsResult { let _timer = Profiler::global().start_event("Realm::create", "realm"); - let intrinsics = Intrinsics::uninit(root_shape).ok_or_else(|| { - JsNativeError::typ().with_message("failed to create the realm intrinsics") - })?; + // Use Gc::new_cyclic to create the Realm with a cyclic reference + let inner = Gc::new_cyclic(|weak_realm| { + // Initialize intrinsics with a reference to the weak_realm + let intrinsics = Intrinsics::uninit(root_shape, weak_realm) + .expect("failed to create the realm intrinsics"); - let global_object = hooks.create_global_object(&intrinsics); - let global_this = hooks - .create_global_this(&intrinsics) - .unwrap_or_else(|| global_object.clone()); - let environment = Gc::new(DeclarativeEnvironment::global()); - let scope = Scope::new_global(); + let global_object = hooks.create_global_object(&intrinsics); + let global_this = hooks + .create_global_this(&intrinsics) + .unwrap_or_else(|| global_object.clone()); + let environment = Gc::new(DeclarativeEnvironment::global()); + let scope = Scope::new_global(); - let realm = Self { - inner: Gc::new(Inner { + RealmInner { intrinsics, environment, scope, @@ -102,8 +123,10 @@ impl Realm { loaded_modules: GcRefCell::default(), host_classes: GcRefCell::default(), host_defined: GcRefCell::default(), - }), - }; + } + }); + + let realm = Self { inner }; realm.initialize(); diff --git a/core/gc/src/cell.rs b/core/gc/src/cell.rs index 3666d88d973..1140c764cd3 100644 --- a/core/gc/src/cell.rs +++ b/core/gc/src/cell.rs @@ -16,7 +16,7 @@ use std::{ /// `BorrowFlag` represent the internal state of a `GcCell` and /// keeps track of the amount of current borrows. #[derive(Copy, Clone)] -struct BorrowFlag(usize); +pub struct BorrowFlag(usize); /// `BorrowState` represents the various states of a `BorrowFlag` /// @@ -91,7 +91,8 @@ impl Debug for BorrowFlag { /// /// This object is a `RefCell` that can be used inside of a `Gc`. pub struct GcRefCell { - flags: Cell, + /// The current state of the `GcCell`. + pub flags: Cell, cell: UnsafeCell, }