Browse Source

Lift `InternalObjectMethods` from `Object` (#2790)

This Pull Request lifts the `InternalObjectMethods` vtable from `Object`, which should technically improve performance, since we now won't need to call `borrow` to use any of the internal methods, but let's see what the benchmarks show.

It changes the following:

- Adds a new `VTableObject` struct, containing the old `GcRefCell<Object>` and the lifted `InternalObjectMethods`.
- Changes the definition of `JsObject` to `Gc<VTableObject>`. Note that this means the `InternalObjectMethods` are accessible through the `Gc` pointer.
- Reestructures intrinsic initialization and initialization APIs to accommodate this change.
pull/2814/head
José Julián Espina 2 years ago
parent
commit
c9759a8dc3
  1. 2
      boa_engine/src/builtins/array/mod.rs
  2. 7
      boa_engine/src/builtins/error/type.rs
  3. 207
      boa_engine/src/builtins/mod.rs
  4. 11
      boa_engine/src/builtins/regexp/mod.rs
  5. 4
      boa_engine/src/builtins/typed_array/mod.rs
  6. 8
      boa_engine/src/builtins/uri/mod.rs
  7. 37
      boa_engine/src/context/intrinsics.rs
  8. 2
      boa_engine/src/context/mod.rs
  9. 26
      boa_engine/src/object/builtins/jsfunction.rs
  10. 48
      boa_engine/src/object/internal_methods/mod.rs
  11. 93
      boa_engine/src/object/jsobject.rs
  12. 835
      boa_engine/src/object/mod.rs
  13. 9
      boa_engine/src/vm/opcode/set/class_prototype.rs

2
boa_engine/src/builtins/array/mod.rs

@ -54,7 +54,7 @@ impl IntrinsicObject for Array {
let values_function = BuiltInBuilder::with_object( let values_function = BuiltInBuilder::with_object(
realm, realm,
realm.intrinsics().objects().array_prototype_values(), realm.intrinsics().objects().array_prototype_values().into(),
) )
.callable(Self::values) .callable(Self::values)
.name("values") .name("values")

7
boa_engine/src/builtins/error/type.rs

@ -22,12 +22,11 @@ use crate::{
}, },
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute, property::Attribute,
realm::Realm, realm::Realm,
string::utf16, string::utf16,
Context, JsArgs, JsResult, JsValue, Context, JsArgs, JsResult, JsValue, NativeFunction,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -134,7 +133,7 @@ impl IntrinsicObject for ThrowTypeError {
let mut obj = obj.borrow_mut(); let mut obj = obj.borrow_mut();
obj.extensible = false; obj.extensible = false;
obj.data = ObjectData::function(Function::new( *obj.kind_mut() = ObjectKind::Function(Function::new(
FunctionKind::Native { FunctionKind::Native {
function: NativeFunction::from_fn_ptr(throw_type_error), function: NativeFunction::from_fn_ptr(throw_type_error),
constructor: None, constructor: None,

207
boa_engine/src/builtins/mod.rs

@ -97,7 +97,8 @@ use crate::{
js_string, js_string,
native_function::{NativeFunction, NativeFunctionPointer}, native_function::{NativeFunction, NativeFunctionPointer},
object::{ object::{
FunctionBinding, JsFunction, JsObject, JsPrototype, ObjectData, CONSTRUCTOR, PROTOTYPE, FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR,
PROTOTYPE,
}, },
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
@ -390,6 +391,85 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
// === Builder typestate === // === Builder typestate ===
#[derive(Debug)]
enum BuiltInObjectInitializer {
Shared(JsObject),
Unique { object: Object, data: ObjectData },
}
impl BuiltInObjectInitializer {
/// Inserts a new property descriptor into the builtin.
fn insert<K, P>(&mut self, key: K, property: P)
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
match self {
BuiltInObjectInitializer::Shared(obj) => obj.borrow_mut().insert(key, property),
BuiltInObjectInitializer::Unique { object, .. } => object.insert(key, property),
};
}
/// Sets the prototype of the builtin
fn set_prototype(&mut self, prototype: JsObject) {
match self {
BuiltInObjectInitializer::Shared(obj) => {
let mut obj = obj.borrow_mut();
obj.set_prototype(prototype);
}
BuiltInObjectInitializer::Unique { object, .. } => {
object.set_prototype(prototype);
}
}
}
/// Sets the `ObjectData` of the builtin.
///
/// # Panics
///
/// Panics if the builtin is a shared builtin and the data's vtable is not the same as the
/// builtin's vtable.
fn set_data(&mut self, new_data: ObjectData) {
match self {
BuiltInObjectInitializer::Shared(obj) => {
assert!(
std::ptr::eq(obj.vtable(), new_data.internal_methods),
"intrinsic object's vtable didn't match with new data"
);
*obj.borrow_mut().kind_mut() = new_data.kind;
}
BuiltInObjectInitializer::Unique { ref mut data, .. } => *data = new_data,
}
}
/// Gets a shared object from the builtin, transitioning its state if it's necessary.
fn as_shared(&mut self) -> JsObject {
match std::mem::replace(
self,
BuiltInObjectInitializer::Unique {
object: Object::default(),
data: ObjectData::ordinary(),
},
) {
BuiltInObjectInitializer::Shared(obj) => {
*self = BuiltInObjectInitializer::Shared(obj.clone());
obj
}
BuiltInObjectInitializer::Unique { mut object, data } => {
*object.kind_mut() = data.kind;
let obj = JsObject::from_object_and_vtable(object, data.internal_methods);
*self = BuiltInObjectInitializer::Shared(obj.clone());
obj
}
}
}
/// Converts the builtin into a shared object.
fn into_shared(mut self) -> JsObject {
self.as_shared()
}
}
/// Marker for a constructor function. /// Marker for a constructor function.
struct Constructor { struct Constructor {
prototype: JsObject, prototype: JsObject,
@ -434,77 +514,78 @@ struct OrdinaryObject;
/// Applies the pending builder data to the object. /// Applies the pending builder data to the object.
trait ApplyToObject { trait ApplyToObject {
fn apply_to(self, object: &JsObject); fn apply_to(self, object: &mut BuiltInObjectInitializer);
} }
impl ApplyToObject for Constructor { impl ApplyToObject for Constructor {
fn apply_to(self, object: &JsObject) { fn apply_to(self, object: &mut BuiltInObjectInitializer) {
object.insert(
PROTOTYPE,
PropertyDescriptor::builder()
.value(self.prototype.clone())
.writable(false)
.enumerable(false)
.configurable(false),
);
let object = object.as_shared();
{ {
let mut prototype = self.prototype.borrow_mut(); let mut prototype = self.prototype.borrow_mut();
prototype.set_prototype(self.inherits); prototype.set_prototype(self.inherits);
prototype.insert( prototype.insert(
CONSTRUCTOR, CONSTRUCTOR,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(object.clone()) .value(object)
.writable(self.attributes.writable()) .writable(self.attributes.writable())
.enumerable(self.attributes.enumerable()) .enumerable(self.attributes.enumerable())
.configurable(self.attributes.configurable()), .configurable(self.attributes.configurable()),
); );
} }
let mut object = object.borrow_mut();
object.insert(
PROTOTYPE,
PropertyDescriptor::builder()
.value(self.prototype)
.writable(false)
.enumerable(false)
.configurable(false),
);
} }
} }
impl ApplyToObject for ConstructorNoProto { impl ApplyToObject for ConstructorNoProto {
fn apply_to(self, _: &JsObject) {} fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
} }
impl ApplyToObject for OrdinaryFunction { impl ApplyToObject for OrdinaryFunction {
fn apply_to(self, _: &JsObject) {} fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
} }
impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> { impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
fn apply_to(self, object: &JsObject) { fn apply_to(self, object: &mut BuiltInObjectInitializer) {
self.kind.apply_to(object); let function = ObjectData::function(function::Function::new(
let function = function::Function::new(
function::FunctionKind::Native { function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function), function: NativeFunction::from_fn_ptr(self.function),
constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base), constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base),
}, },
self.realm, self.realm,
));
object.set_data(function);
object.insert(
utf16!("length"),
PropertyDescriptor::builder()
.value(self.length)
.writable(false)
.enumerable(false)
.configurable(true),
);
object.insert(
utf16!("name"),
PropertyDescriptor::builder()
.value(self.name)
.writable(false)
.enumerable(false)
.configurable(true),
); );
let length = PropertyDescriptor::builder() self.kind.apply_to(object);
.value(self.length)
.writable(false)
.enumerable(false)
.configurable(true);
let name = PropertyDescriptor::builder()
.value(self.name)
.writable(false)
.enumerable(false)
.configurable(true);
{
let mut constructor = object.borrow_mut();
constructor.data = ObjectData::function(function);
constructor.insert(utf16!("length"), length);
constructor.insert(utf16!("name"), name);
}
} }
} }
impl ApplyToObject for OrdinaryObject { impl ApplyToObject for OrdinaryObject {
fn apply_to(self, _: &JsObject) {} fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
} }
/// Builder for creating built-in objects, like `Array`. /// Builder for creating built-in objects, like `Array`.
@ -515,7 +596,7 @@ impl ApplyToObject for OrdinaryObject {
#[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"] #[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"]
struct BuiltInBuilder<'ctx, Kind> { struct BuiltInBuilder<'ctx, Kind> {
realm: &'ctx Realm, realm: &'ctx Realm,
object: JsObject, object: BuiltInObjectInitializer,
kind: Kind, kind: Kind,
prototype: JsObject, prototype: JsObject,
} }
@ -524,7 +605,10 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> { fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object: JsObject::with_null_proto(), object: BuiltInObjectInitializer::Unique {
object: Object::default(),
data: ObjectData::ordinary(),
},
kind: OrdinaryObject, kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(), prototype: realm.intrinsics().constructors().object().prototype(),
} }
@ -535,7 +619,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
) -> BuiltInBuilder<'ctx, OrdinaryObject> { ) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object: I::get(realm.intrinsics()), object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
kind: OrdinaryObject, kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(), prototype: realm.intrinsics().constructors().object().prototype(),
} }
@ -544,7 +628,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
fn with_object(realm: &'ctx Realm, object: JsObject) -> BuiltInBuilder<'ctx, OrdinaryObject> { fn with_object(realm: &'ctx Realm, object: JsObject) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object, object: BuiltInObjectInitializer::Shared(object),
kind: OrdinaryObject, kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(), prototype: realm.intrinsics().constructors().object().prototype(),
} }
@ -583,7 +667,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors()); let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors());
BuiltInBuilder { BuiltInBuilder {
realm, realm,
object: constructor.constructor(), object: BuiltInObjectInitializer::Shared(constructor.constructor()),
kind: Callable { kind: Callable {
function: SC::constructor, function: SC::constructor,
name: js_string!(SC::NAME), name: js_string!(SC::NAME),
@ -617,7 +701,12 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
impl<T> BuiltInBuilder<'_, T> { impl<T> BuiltInBuilder<'_, T> {
/// Adds a new static method to the builtin object. /// Adds a new static method to the builtin object.
fn static_method<B>(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self fn static_method<B>(
mut self,
function: NativeFunctionPointer,
binding: B,
length: usize,
) -> Self
where where
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
@ -628,7 +717,7 @@ impl<T> BuiltInBuilder<'_, T> {
.length(length) .length(length)
.build(); .build();
self.object.borrow_mut().insert( self.object.insert(
binding.binding, binding.binding,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(function) .value(function)
@ -640,7 +729,7 @@ impl<T> BuiltInBuilder<'_, T> {
} }
/// Adds a new static data property to the builtin object. /// Adds a new static data property to the builtin object.
fn static_property<K, V>(self, key: K, value: V, attribute: Attribute) -> Self fn static_property<K, V>(mut self, key: K, value: V, attribute: Attribute) -> Self
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<JsValue>, V: Into<JsValue>,
@ -650,13 +739,13 @@ impl<T> BuiltInBuilder<'_, T> {
.writable(attribute.writable()) .writable(attribute.writable())
.enumerable(attribute.enumerable()) .enumerable(attribute.enumerable())
.configurable(attribute.configurable()); .configurable(attribute.configurable());
self.object.borrow_mut().insert(key, property); self.object.insert(key, property);
self self
} }
/// Adds a new static accessor property to the builtin object. /// Adds a new static accessor property to the builtin object.
fn static_accessor<K>( fn static_accessor<K>(
self, mut self,
key: K, key: K,
get: Option<JsFunction>, get: Option<JsFunction>,
set: Option<JsFunction>, set: Option<JsFunction>,
@ -670,7 +759,7 @@ impl<T> BuiltInBuilder<'_, T> {
.maybe_set(set) .maybe_set(set)
.enumerable(attribute.enumerable()) .enumerable(attribute.enumerable())
.configurable(attribute.configurable()); .configurable(attribute.configurable());
self.object.borrow_mut().insert(key, property); self.object.insert(key, property);
self self
} }
@ -779,28 +868,22 @@ impl<FnTyp> BuiltInBuilder<'_, Callable<FnTyp>> {
impl BuiltInBuilder<'_, OrdinaryObject> { impl BuiltInBuilder<'_, OrdinaryObject> {
/// Build the builtin object. /// Build the builtin object.
fn build(self) -> JsObject { fn build(mut self) -> JsObject {
self.kind.apply_to(&self.object); self.kind.apply_to(&mut self.object);
{ self.object.set_prototype(self.prototype);
let mut object = self.object.borrow_mut();
object.set_prototype(self.prototype);
}
self.object self.object.into_shared()
} }
} }
impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> { impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> {
/// Build the builtin callable. /// Build the builtin callable.
fn build(self) -> JsFunction { fn build(mut self) -> JsFunction {
self.kind.apply_to(&self.object); self.kind.apply_to(&mut self.object);
{ self.object.set_prototype(self.prototype);
let mut object = self.object.borrow_mut();
object.set_prototype(self.prototype);
}
JsFunction::from_object_unchecked(self.object) JsFunction::from_object_unchecked(self.object.into_shared())
} }
} }

11
boa_engine/src/builtins/regexp/mod.rs

@ -14,7 +14,10 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, CONSTRUCTOR}, object::{
internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind,
CONSTRUCTOR,
},
property::{Attribute, PropertyDescriptorBuilder}, property::{Attribute, PropertyDescriptorBuilder},
realm::Realm, realm::Realm,
string::{utf16, CodePoint}, string::{utf16, CodePoint},
@ -311,7 +314,9 @@ impl RegExp {
original_source: p, original_source: p,
original_flags: f, original_flags: f,
}; };
obj.borrow_mut().data = ObjectData::reg_exp(Box::new(regexp));
// Safe to directly initialize since previous assertions ensure `obj` is a `Regexp` object.
*obj.borrow_mut().kind_mut() = ObjectKind::RegExp(Box::new(regexp));
// 16. Perform ? Set(obj, "lastIndex", +0𝔽, true). // 16. Perform ? Set(obj, "lastIndex", +0𝔽, true).
obj.set(utf16!("lastIndex"), 0, true, context)?; obj.set(utf16!("lastIndex"), 0, true, context)?;
@ -367,7 +372,7 @@ impl RegExp {
if JsObject::equals( if JsObject::equals(
object, object,
&context.intrinsics().constructors().regexp().prototype, &context.intrinsics().constructors().regexp().prototype(),
) { ) {
return Ok(JsValue::undefined()); return Ok(JsValue::undefined());
} }

4
boa_engine/src/builtins/typed_array/mod.rs

@ -23,7 +23,7 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind},
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
realm::Realm, realm::Realm,
string::utf16, string::utf16,
@ -3349,7 +3349,7 @@ impl TypedArray {
// 18. Set O.[[ByteOffset]] to 0. // 18. Set O.[[ByteOffset]] to 0.
// 19. Set O.[[ArrayLength]] to elementLength. // 19. Set O.[[ArrayLength]] to elementLength.
drop(o_obj); drop(o_obj);
o.borrow_mut().data = ObjectData::integer_indexed(IntegerIndexed::new( *o.borrow_mut().kind_mut() = ObjectKind::IntegerIndexed(IntegerIndexed::new(
Some(data), Some(data),
constructor_name, constructor_name,
0, 0,

8
boa_engine/src/builtins/uri/mod.rs

@ -50,10 +50,10 @@ pub struct UriFunctions {
impl Default for UriFunctions { impl Default for UriFunctions {
fn default() -> Self { fn default() -> Self {
Self { Self {
decode_uri: JsFunction::from_object_unchecked(JsObject::default()), decode_uri: JsFunction::empty_intrinsic_function(false),
decode_uri_component: JsFunction::from_object_unchecked(JsObject::default()), decode_uri_component: JsFunction::empty_intrinsic_function(false),
encode_uri: JsFunction::from_object_unchecked(JsObject::default()), encode_uri: JsFunction::empty_intrinsic_function(false),
encode_uri_component: JsFunction::from_object_unchecked(JsObject::default()), encode_uri_component: JsFunction::empty_intrinsic_function(false),
} }
} }
} }

37
boa_engine/src/context/intrinsics.rs

@ -36,14 +36,14 @@ impl Intrinsics {
/// Store a builtin constructor (such as `Object`) and its corresponding prototype. /// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub struct StandardConstructor { pub struct StandardConstructor {
pub(crate) constructor: JsObject, constructor: JsFunction,
pub(crate) prototype: JsObject, prototype: JsObject,
} }
impl Default for StandardConstructor { impl Default for StandardConstructor {
fn default() -> Self { fn default() -> Self {
Self { Self {
constructor: JsObject::with_null_proto(), constructor: JsFunction::empty_intrinsic_function(true),
prototype: JsObject::with_null_proto(), prototype: JsObject::with_null_proto(),
} }
} }
@ -53,7 +53,7 @@ impl StandardConstructor {
/// Build a constructor with a defined prototype. /// Build a constructor with a defined prototype.
fn with_prototype(prototype: JsObject) -> Self { fn with_prototype(prototype: JsObject) -> Self {
Self { Self {
constructor: JsObject::with_null_proto(), constructor: JsFunction::empty_intrinsic_function(true),
prototype, prototype,
} }
} }
@ -71,7 +71,7 @@ impl StandardConstructor {
/// This is the same as `Object`, `Array`, etc. /// This is the same as `Object`, `Array`, etc.
#[inline] #[inline]
pub fn constructor(&self) -> JsObject { pub fn constructor(&self) -> JsObject {
self.constructor.clone() self.constructor.clone().into()
} }
} }
@ -138,7 +138,10 @@ impl Default for StandardConstructors {
object: StandardConstructor::default(), object: StandardConstructor::default(),
proxy: StandardConstructor::default(), proxy: StandardConstructor::default(),
date: StandardConstructor::default(), date: StandardConstructor::default(),
function: StandardConstructor::default(), function: StandardConstructor {
constructor: JsFunction::empty_intrinsic_function(true),
prototype: JsFunction::empty_intrinsic_function(false).into(),
},
async_function: StandardConstructor::default(), async_function: StandardConstructor::default(),
generator_function: StandardConstructor::default(), generator_function: StandardConstructor::default(),
array: StandardConstructor::with_prototype(JsObject::from_proto_and_data( array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(
@ -740,7 +743,7 @@ pub struct IntrinsicObjects {
throw_type_error: JsFunction, throw_type_error: JsFunction,
/// [`%Array.prototype.values%`](https://tc39.es/ecma262/#sec-array.prototype.values) /// [`%Array.prototype.values%`](https://tc39.es/ecma262/#sec-array.prototype.values)
array_prototype_values: JsObject, array_prototype_values: JsFunction,
/// Cached iterator prototypes. /// Cached iterator prototypes.
iterator_prototypes: IteratorPrototypes, iterator_prototypes: IteratorPrototypes,
@ -788,21 +791,21 @@ impl Default for IntrinsicObjects {
reflect: JsObject::default(), reflect: JsObject::default(),
math: JsObject::default(), math: JsObject::default(),
json: JsObject::default(), json: JsObject::default(),
throw_type_error: JsFunction::from_object_unchecked(JsObject::default()), throw_type_error: JsFunction::empty_intrinsic_function(false),
array_prototype_values: JsObject::default(), array_prototype_values: JsFunction::empty_intrinsic_function(false),
iterator_prototypes: IteratorPrototypes::default(), iterator_prototypes: IteratorPrototypes::default(),
generator: JsObject::default(), generator: JsObject::default(),
async_generator: JsObject::default(), async_generator: JsObject::default(),
eval: JsFunction::from_object_unchecked(JsObject::default()), eval: JsFunction::empty_intrinsic_function(false),
uri_functions: UriFunctions::default(), uri_functions: UriFunctions::default(),
is_finite: JsFunction::from_object_unchecked(JsObject::default()), is_finite: JsFunction::empty_intrinsic_function(false),
is_nan: JsFunction::from_object_unchecked(JsObject::default()), is_nan: JsFunction::empty_intrinsic_function(false),
parse_float: JsFunction::from_object_unchecked(JsObject::default()), parse_float: JsFunction::empty_intrinsic_function(false),
parse_int: JsFunction::from_object_unchecked(JsObject::default()), parse_int: JsFunction::empty_intrinsic_function(false),
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
escape: JsFunction::from_object_unchecked(JsObject::default()), escape: JsFunction::empty_intrinsic_function(false),
#[cfg(feature = "annex-b")] #[cfg(feature = "annex-b")]
unescape: JsFunction::from_object_unchecked(JsObject::default()), unescape: JsFunction::empty_intrinsic_function(false),
#[cfg(feature = "intl")] #[cfg(feature = "intl")]
intl: JsObject::default(), intl: JsObject::default(),
} }
@ -822,7 +825,7 @@ impl IntrinsicObjects {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
#[inline] #[inline]
pub fn array_prototype_values(&self) -> JsObject { pub fn array_prototype_values(&self) -> JsFunction {
self.array_prototype_values.clone() self.array_prototype_values.clone()
} }

2
boa_engine/src/context/mod.rs

@ -587,7 +587,7 @@ impl std::fmt::Debug for ContextBuilder<'_, '_, '_> {
impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> { impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> {
/// Creates a new [`ContextBuilder`] with a default empty [`Interner`] /// Creates a new [`ContextBuilder`] with a default empty [`Interner`]
/// and a default [`BoaProvider`] if the `intl` feature is enabled. /// and a default `BoaProvider` if the `intl` feature is enabled.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()

26
boa_engine/src/object/builtins/jsfunction.rs

@ -1,6 +1,9 @@
//! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object
use crate::{ use crate::{
object::{JsObject, JsObjectType}, object::{
internal_methods::function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS},
JsObject, JsObjectType, Object,
},
value::TryFromJs, value::TryFromJs,
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue,
}; };
@ -14,11 +17,30 @@ pub struct JsFunction {
} }
impl JsFunction { impl JsFunction {
/// Creates a new `JsFunction` from an object, without checking if the object is callable.
pub(crate) fn from_object_unchecked(object: JsObject) -> Self { pub(crate) fn from_object_unchecked(object: JsObject) -> Self {
Self { inner: object } Self { inner: object }
} }
/// Create a [`JsFunction`] from a [`JsObject`], or return `None` if the object is not a function. /// Creates a new, empty intrinsic function object with only its function internal methods set.
///
/// Mainly used to initialize objects before a [`Context`] is available to do so.
///
/// [`Context`]: crate::Context
pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self {
Self {
inner: JsObject::from_object_and_vtable(
Object::default(),
if constructor {
&CONSTRUCTOR_INTERNAL_METHODS
} else {
&FUNCTION_INTERNAL_METHODS
},
),
}
}
/// Creates a [`JsFunction`] from a [`JsObject`], or returns `None` if the object is not a function.
/// ///
/// This does not clone the fields of the function, it only does a shallow clone of the object. /// This does not clone the fields of the function, it only does a shallow clone of the object.
#[inline] #[inline]

48
boa_engine/src/object/internal_methods/mod.rs

@ -35,8 +35,7 @@ impl JsObject {
#[track_caller] #[track_caller]
pub(crate) fn __get_prototype_of__(&self, context: &mut Context<'_>) -> JsResult<JsPrototype> { pub(crate) fn __get_prototype_of__(&self, context: &mut Context<'_>) -> JsResult<JsPrototype> {
let _timer = Profiler::global().start_event("Object::__get_prototype_of__", "object"); let _timer = Profiler::global().start_event("Object::__get_prototype_of__", "object");
let func = self.borrow().data.internal_methods.__get_prototype_of__; (self.vtable().__get_prototype_of__)(self, context)
func(self, context)
} }
/// Internal method `[[SetPrototypeOf]]` /// Internal method `[[SetPrototypeOf]]`
@ -53,8 +52,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<bool> { ) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__set_prototype_of__", "object"); let _timer = Profiler::global().start_event("Object::__set_prototype_of__", "object");
let func = self.borrow().data.internal_methods.__set_prototype_of__; (self.vtable().__set_prototype_of__)(self, val, context)
func(self, val, context)
} }
/// Internal method `[[IsExtensible]]` /// Internal method `[[IsExtensible]]`
@ -67,8 +65,7 @@ impl JsObject {
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
pub(crate) fn __is_extensible__(&self, context: &mut Context<'_>) -> JsResult<bool> { pub(crate) fn __is_extensible__(&self, context: &mut Context<'_>) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__is_extensible__", "object"); let _timer = Profiler::global().start_event("Object::__is_extensible__", "object");
let func = self.borrow().data.internal_methods.__is_extensible__; (self.vtable().__is_extensible__)(self, context)
func(self, context)
} }
/// Internal method `[[PreventExtensions]]` /// Internal method `[[PreventExtensions]]`
@ -81,8 +78,7 @@ impl JsObject {
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
pub(crate) fn __prevent_extensions__(&self, context: &mut Context<'_>) -> JsResult<bool> { pub(crate) fn __prevent_extensions__(&self, context: &mut Context<'_>) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__prevent_extensions__", "object"); let _timer = Profiler::global().start_event("Object::__prevent_extensions__", "object");
let func = self.borrow().data.internal_methods.__prevent_extensions__; (self.vtable().__prevent_extensions__)(self, context)
func(self, context)
} }
/// Internal method `[[GetOwnProperty]]` /// Internal method `[[GetOwnProperty]]`
@ -99,8 +95,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Option<PropertyDescriptor>> { ) -> JsResult<Option<PropertyDescriptor>> {
let _timer = Profiler::global().start_event("Object::__get_own_property__", "object"); let _timer = Profiler::global().start_event("Object::__get_own_property__", "object");
let func = self.borrow().data.internal_methods.__get_own_property__; (self.vtable().__get_own_property__)(self, key, context)
func(self, key, context)
} }
/// Internal method `[[DefineOwnProperty]]` /// Internal method `[[DefineOwnProperty]]`
@ -118,8 +113,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<bool> { ) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__define_own_property__", "object"); let _timer = Profiler::global().start_event("Object::__define_own_property__", "object");
let func = self.borrow().data.internal_methods.__define_own_property__; (self.vtable().__define_own_property__)(self, key, desc, context)
func(self, key, desc, context)
} }
/// Internal method `[[hasProperty]]`. /// Internal method `[[hasProperty]]`.
@ -136,8 +130,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<bool> { ) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__has_property__", "object"); let _timer = Profiler::global().start_event("Object::__has_property__", "object");
let func = self.borrow().data.internal_methods.__has_property__; (self.vtable().__has_property__)(self, key, context)
func(self, key, context)
} }
/// Internal method `[[Get]]` /// Internal method `[[Get]]`
@ -155,8 +148,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__get__", "object"); let _timer = Profiler::global().start_event("Object::__get__", "object");
let func = self.borrow().data.internal_methods.__get__; (self.vtable().__get__)(self, key, receiver, context)
func(self, key, receiver, context)
} }
/// Internal method `[[Set]]` /// Internal method `[[Set]]`
@ -175,8 +167,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<bool> { ) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__set__", "object"); let _timer = Profiler::global().start_event("Object::__set__", "object");
let func = self.borrow().data.internal_methods.__set__; (self.vtable().__set__)(self, key, value, receiver, context)
func(self, key, value, receiver, context)
} }
/// Internal method `[[Delete]]` /// Internal method `[[Delete]]`
@ -193,8 +184,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<bool> { ) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__delete__", "object"); let _timer = Profiler::global().start_event("Object::__delete__", "object");
let func = self.borrow().data.internal_methods.__delete__; (self.vtable().__delete__)(self, key, context)
func(self, key, context)
} }
/// Internal method `[[OwnPropertyKeys]]` /// Internal method `[[OwnPropertyKeys]]`
@ -211,8 +201,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Vec<PropertyKey>> { ) -> JsResult<Vec<PropertyKey>> {
let _timer = Profiler::global().start_event("Object::__own_property_keys__", "object"); let _timer = Profiler::global().start_event("Object::__own_property_keys__", "object");
let func = self.borrow().data.internal_methods.__own_property_keys__; (self.vtable().__own_property_keys__)(self, context)
func(self, context)
} }
/// Internal method `[[Call]]` /// Internal method `[[Call]]`
@ -231,9 +220,9 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__call__", "object"); let _timer = Profiler::global().start_event("Object::__call__", "object");
self.vtable()
let func = self.borrow().data.internal_methods.__call__; .__call__
func.expect("called `[[Call]]` for object without a `[[Call]]` internal method")( .expect("called `[[Call]]` for object without a `[[Call]]` internal method")(
self, this, args, context, self, this, args, context,
) )
} }
@ -254,9 +243,9 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Self> { ) -> JsResult<Self> {
let _timer = Profiler::global().start_event("Object::__construct__", "object"); let _timer = Profiler::global().start_event("Object::__construct__", "object");
self.vtable()
let func = self.borrow().data.internal_methods.__construct__; .__construct__
func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")( .expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
self, args, new_target, context, self, args, new_target, context,
) )
} }
@ -379,8 +368,7 @@ pub(crate) fn ordinary_set_prototype_of(
// c. Else, // c. Else,
// i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined // i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined
// in 10.1.1, set done to true. // in 10.1.1, set done to true.
else if proto.borrow().data.internal_methods.__get_prototype_of__ as usize else if proto.vtable().__get_prototype_of__ as usize != ordinary_get_prototype_of as usize
!= ordinary_get_prototype_of as usize
{ {
break; break;
} }

93
boa_engine/src/object/jsobject.rs

@ -2,7 +2,9 @@
//! //!
//! The `JsObject` is a garbage collected Object. //! The `JsObject` is a garbage collected Object.
use super::{JsPrototype, NativeObject, Object, PropertyMap}; use super::{
internal_methods::InternalObjectMethods, JsPrototype, NativeObject, Object, PropertyMap,
};
use crate::{ use crate::{
context::intrinsics::Intrinsics, context::intrinsics::Intrinsics,
error::JsNativeError, error::JsNativeError,
@ -29,12 +31,43 @@ pub type Ref<'a, T> = boa_gc::GcRef<'a, T>;
pub type RefMut<'a, T, U> = boa_gc::GcRefMut<'a, T, U>; pub type RefMut<'a, T, U> = boa_gc::GcRefMut<'a, T, U>;
/// Garbage collected `Object`. /// Garbage collected `Object`.
#[derive(Trace, Finalize, Clone, Default)] #[derive(Trace, Finalize, Clone)]
pub struct JsObject { pub struct JsObject {
inner: Gc<GcRefCell<Object>>, inner: Gc<VTableObject>,
}
/// An `Object` that has an additional `vtable` with its internal methods.
// We have to skip implementing `Debug` for this because not using the
// implementation of `Debug` for `JsObject` could easily cause stack overflows,
// so we have to force our users to debug the `JsObject` instead.
#[allow(missing_debug_implementations)]
#[derive(Trace, Finalize)]
pub struct VTableObject {
object: GcRefCell<Object>,
#[unsafe_ignore_trace]
vtable: &'static InternalObjectMethods,
}
impl Default for JsObject {
fn default() -> Self {
let data = ObjectData::ordinary();
Self::from_object_and_vtable(Object::default(), data.internal_methods)
}
} }
impl JsObject { impl JsObject {
/// Creates a new `JsObject` from its inner object and its vtable.
pub(crate) fn from_object_and_vtable(
object: Object,
vtable: &'static InternalObjectMethods,
) -> Self {
Self {
inner: Gc::new(VTableObject {
object: GcRefCell::new(object),
vtable,
}),
}
}
/// Creates a new ordinary object with its prototype set to the `Object` prototype. /// Creates a new ordinary object with its prototype set to the `Object` prototype.
/// ///
/// This is equivalent to calling the specification's abstract operation /// This is equivalent to calling the specification's abstract operation
@ -71,13 +104,16 @@ impl JsObject {
/// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self { pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
Self { Self {
inner: Gc::new(GcRefCell::new(Object { inner: Gc::new(VTableObject {
data, object: GcRefCell::new(Object {
prototype: prototype.into(), kind: data.kind,
extensible: true, properties: PropertyMap::default(),
properties: PropertyMap::default(), prototype: prototype.into(),
private_elements: ThinVec::new(), extensible: true,
})), private_elements: ThinVec::new(),
}),
vtable: data.internal_methods,
}),
} }
} }
@ -116,7 +152,7 @@ impl JsObject {
/// This is the non-panicking variant of [`borrow`](#method.borrow). /// This is the non-panicking variant of [`borrow`](#method.borrow).
#[inline] #[inline]
pub fn try_borrow(&self) -> StdResult<Ref<'_, Object>, BorrowError> { pub fn try_borrow(&self) -> StdResult<Ref<'_, Object>, BorrowError> {
self.inner.try_borrow().map_err(|_| BorrowError) self.inner.object.try_borrow().map_err(|_| BorrowError)
} }
/// Mutably borrows the object, returning an error if the value is currently borrowed. /// Mutably borrows the object, returning an error if the value is currently borrowed.
@ -127,7 +163,10 @@ impl JsObject {
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
#[inline] #[inline]
pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object, Object>, BorrowMutError> { pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object, Object>, BorrowMutError> {
self.inner.try_borrow_mut().map_err(|_| BorrowMutError) self.inner
.object
.try_borrow_mut()
.map_err(|_| BorrowMutError)
} }
/// Checks if the garbage collected memory is the same. /// Checks if the garbage collected memory is the same.
@ -852,7 +891,7 @@ Cannot both specify accessors and a value or writable attribute",
#[inline] #[inline]
#[track_caller] #[track_caller]
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
self.borrow().data.internal_methods.__call__.is_some() self.inner.vtable.__call__.is_some()
} }
/// It determines if Object is a function object with a `[[Construct]]` internal method. /// It determines if Object is a function object with a `[[Construct]]` internal method.
@ -864,21 +903,19 @@ Cannot both specify accessors and a value or writable attribute",
#[inline] #[inline]
#[track_caller] #[track_caller]
pub fn is_constructor(&self) -> bool { pub fn is_constructor(&self) -> bool {
self.borrow().data.internal_methods.__construct__.is_some() self.inner.vtable.__construct__.is_some()
} }
/// Returns true if the `JsObject` is the global for a Realm /// Returns true if the `JsObject` is the global for a Realm
pub fn is_global(&self) -> bool { pub fn is_global(&self) -> bool {
matches!( matches!(self.inner.object.borrow().kind, ObjectKind::Global)
self.borrow().data,
ObjectData {
kind: ObjectKind::Global,
..
}
)
} }
pub(crate) const fn inner(&self) -> &Gc<GcRefCell<Object>> { pub(crate) fn vtable(&self) -> &'static InternalObjectMethods {
self.inner.vtable
}
pub(crate) const fn inner(&self) -> &Gc<VTableObject> {
&self.inner &self.inner
} }
} }
@ -886,13 +923,13 @@ Cannot both specify accessors and a value or writable attribute",
impl AsRef<GcRefCell<Object>> for JsObject { impl AsRef<GcRefCell<Object>> for JsObject {
#[inline] #[inline]
fn as_ref(&self) -> &GcRefCell<Object> { fn as_ref(&self) -> &GcRefCell<Object> {
&self.inner &self.inner.object
} }
} }
impl From<Gc<GcRefCell<Object>>> for JsObject { impl From<Gc<VTableObject>> for JsObject {
#[inline] #[inline]
fn from(inner: Gc<GcRefCell<Object>>) -> Self { fn from(inner: Gc<VTableObject>) -> Self {
Self { inner } Self { inner }
} }
} }
@ -1014,6 +1051,7 @@ impl RecursionLimiter {
impl Debug for JsObject { impl Debug for JsObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
let ptr: *const _ = self.vtable();
let limiter = RecursionLimiter::new(self); let limiter = RecursionLimiter::new(self);
// Typically, using `!limiter.live` would be good enough here. // Typically, using `!limiter.live` would be good enough here.
@ -1023,7 +1061,10 @@ impl Debug for JsObject {
// Instead, we check if the object has appeared before in the entire graph. This means that objects will appear // Instead, we check if the object has appeared before in the entire graph. This means that objects will appear
// at most once, hopefully making things a bit clearer. // at most once, hopefully making things a bit clearer.
if !limiter.visited && !limiter.live { if !limiter.visited && !limiter.live {
f.debug_tuple("JsObject").field(&self.inner).finish() f.debug_struct("JsObject")
.field("vtable", &ptr)
.field("object", &self.inner.object)
.finish()
} else { } else {
f.write_str("{ ... }") f.write_str("{ ... }")
} }

835
boa_engine/src/object/mod.rs

File diff suppressed because it is too large Load Diff

9
boa_engine/src/vm/opcode/set/class_prototype.rs

@ -21,14 +21,7 @@ impl Operation for SetClassPrototype {
let prototype = match &prototype_value { let prototype = match &prototype_value {
JsValue::Object(proto) => Some(proto.clone()), JsValue::Object(proto) => Some(proto.clone()),
JsValue::Null => None, JsValue::Null => None,
JsValue::Undefined => Some( JsValue::Undefined => Some(context.intrinsics().constructors().object().prototype()),
context
.intrinsics()
.constructors()
.object()
.prototype
.clone(),
),
_ => unreachable!(), _ => unreachable!(),
}; };

Loading…
Cancel
Save