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(
realm,
realm.intrinsics().objects().array_prototype_values(),
realm.intrinsics().objects().array_prototype_values().into(),
)
.callable(Self::values)
.name("values")

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

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

207
boa_engine/src/builtins/mod.rs

@ -97,7 +97,8 @@ use crate::{
js_string,
native_function::{NativeFunction, NativeFunctionPointer},
object::{
FunctionBinding, JsFunction, JsObject, JsPrototype, ObjectData, CONSTRUCTOR, PROTOTYPE,
FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR,
PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
@ -390,6 +391,85 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
// === 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.
struct Constructor {
prototype: JsObject,
@ -434,77 +514,78 @@ struct OrdinaryObject;
/// Applies the pending builder data to the object.
trait ApplyToObject {
fn apply_to(self, object: &JsObject);
fn apply_to(self, object: &mut BuiltInObjectInitializer);
}
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();
prototype.set_prototype(self.inherits);
prototype.insert(
CONSTRUCTOR,
PropertyDescriptor::builder()
.value(object.clone())
.value(object)
.writable(self.attributes.writable())
.enumerable(self.attributes.enumerable())
.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 {
fn apply_to(self, _: &JsObject) {}
fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
}
impl ApplyToObject for OrdinaryFunction {
fn apply_to(self, _: &JsObject) {}
fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
}
impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
fn apply_to(self, object: &JsObject) {
self.kind.apply_to(object);
let function = function::Function::new(
fn apply_to(self, object: &mut BuiltInObjectInitializer) {
let function = ObjectData::function(function::Function::new(
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base),
},
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()
.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);
}
self.kind.apply_to(object);
}
}
impl ApplyToObject for OrdinaryObject {
fn apply_to(self, _: &JsObject) {}
fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
}
/// 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"]
struct BuiltInBuilder<'ctx, Kind> {
realm: &'ctx Realm,
object: JsObject,
object: BuiltInObjectInitializer,
kind: Kind,
prototype: JsObject,
}
@ -524,7 +605,10 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder {
realm,
object: JsObject::with_null_proto(),
object: BuiltInObjectInitializer::Unique {
object: Object::default(),
data: ObjectData::ordinary(),
},
kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(),
}
@ -535,7 +619,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder {
realm,
object: I::get(realm.intrinsics()),
object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
kind: OrdinaryObject,
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> {
BuiltInBuilder {
realm,
object,
object: BuiltInObjectInitializer::Shared(object),
kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(),
}
@ -583,7 +667,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors());
BuiltInBuilder {
realm,
object: constructor.constructor(),
object: BuiltInObjectInitializer::Shared(constructor.constructor()),
kind: Callable {
function: SC::constructor,
name: js_string!(SC::NAME),
@ -617,7 +701,12 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
impl<T> BuiltInBuilder<'_, T> {
/// 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
B: Into<FunctionBinding>,
{
@ -628,7 +717,7 @@ impl<T> BuiltInBuilder<'_, T> {
.length(length)
.build();
self.object.borrow_mut().insert(
self.object.insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
@ -640,7 +729,7 @@ impl<T> BuiltInBuilder<'_, T> {
}
/// 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
K: Into<PropertyKey>,
V: Into<JsValue>,
@ -650,13 +739,13 @@ impl<T> BuiltInBuilder<'_, T> {
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.object.borrow_mut().insert(key, property);
self.object.insert(key, property);
self
}
/// Adds a new static accessor property to the builtin object.
fn static_accessor<K>(
self,
mut self,
key: K,
get: Option<JsFunction>,
set: Option<JsFunction>,
@ -670,7 +759,7 @@ impl<T> BuiltInBuilder<'_, T> {
.maybe_set(set)
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.object.borrow_mut().insert(key, property);
self.object.insert(key, property);
self
}
@ -779,28 +868,22 @@ impl<FnTyp> BuiltInBuilder<'_, Callable<FnTyp>> {
impl BuiltInBuilder<'_, OrdinaryObject> {
/// Build the builtin object.
fn build(self) -> JsObject {
self.kind.apply_to(&self.object);
fn build(mut self) -> JsObject {
self.kind.apply_to(&mut self.object);
{
let mut object = self.object.borrow_mut();
object.set_prototype(self.prototype);
}
self.object.set_prototype(self.prototype);
self.object
self.object.into_shared()
}
}
impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> {
/// Build the builtin callable.
fn build(self) -> JsFunction {
self.kind.apply_to(&self.object);
fn build(mut self) -> JsFunction {
self.kind.apply_to(&mut self.object);
{
let mut object = self.object.borrow_mut();
object.set_prototype(self.prototype);
}
self.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},
error::JsNativeError,
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},
realm::Realm,
string::{utf16, CodePoint},
@ -311,7 +314,9 @@ impl RegExp {
original_source: p,
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).
obj.set(utf16!("lastIndex"), 0, true, context)?;
@ -367,7 +372,7 @@ impl RegExp {
if JsObject::equals(
object,
&context.intrinsics().constructors().regexp().prototype,
&context.intrinsics().constructors().regexp().prototype(),
) {
return Ok(JsValue::undefined());
}

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

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

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

@ -50,10 +50,10 @@ pub struct UriFunctions {
impl Default for UriFunctions {
fn default() -> Self {
Self {
decode_uri: JsFunction::from_object_unchecked(JsObject::default()),
decode_uri_component: JsFunction::from_object_unchecked(JsObject::default()),
encode_uri: JsFunction::from_object_unchecked(JsObject::default()),
encode_uri_component: JsFunction::from_object_unchecked(JsObject::default()),
decode_uri: JsFunction::empty_intrinsic_function(false),
decode_uri_component: JsFunction::empty_intrinsic_function(false),
encode_uri: JsFunction::empty_intrinsic_function(false),
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.
#[derive(Debug, Trace, Finalize)]
pub struct StandardConstructor {
pub(crate) constructor: JsObject,
pub(crate) prototype: JsObject,
constructor: JsFunction,
prototype: JsObject,
}
impl Default for StandardConstructor {
fn default() -> Self {
Self {
constructor: JsObject::with_null_proto(),
constructor: JsFunction::empty_intrinsic_function(true),
prototype: JsObject::with_null_proto(),
}
}
@ -53,7 +53,7 @@ impl StandardConstructor {
/// Build a constructor with a defined prototype.
fn with_prototype(prototype: JsObject) -> Self {
Self {
constructor: JsObject::with_null_proto(),
constructor: JsFunction::empty_intrinsic_function(true),
prototype,
}
}
@ -71,7 +71,7 @@ impl StandardConstructor {
/// This is the same as `Object`, `Array`, etc.
#[inline]
pub fn constructor(&self) -> JsObject {
self.constructor.clone()
self.constructor.clone().into()
}
}
@ -138,7 +138,10 @@ impl Default for StandardConstructors {
object: StandardConstructor::default(),
proxy: 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(),
generator_function: StandardConstructor::default(),
array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(
@ -740,7 +743,7 @@ pub struct IntrinsicObjects {
throw_type_error: JsFunction,
/// [`%Array.prototype.values%`](https://tc39.es/ecma262/#sec-array.prototype.values)
array_prototype_values: JsObject,
array_prototype_values: JsFunction,
/// Cached iterator prototypes.
iterator_prototypes: IteratorPrototypes,
@ -788,21 +791,21 @@ impl Default for IntrinsicObjects {
reflect: JsObject::default(),
math: JsObject::default(),
json: JsObject::default(),
throw_type_error: JsFunction::from_object_unchecked(JsObject::default()),
array_prototype_values: JsObject::default(),
throw_type_error: JsFunction::empty_intrinsic_function(false),
array_prototype_values: JsFunction::empty_intrinsic_function(false),
iterator_prototypes: IteratorPrototypes::default(),
generator: JsObject::default(),
async_generator: JsObject::default(),
eval: JsFunction::from_object_unchecked(JsObject::default()),
eval: JsFunction::empty_intrinsic_function(false),
uri_functions: UriFunctions::default(),
is_finite: JsFunction::from_object_unchecked(JsObject::default()),
is_nan: JsFunction::from_object_unchecked(JsObject::default()),
parse_float: JsFunction::from_object_unchecked(JsObject::default()),
parse_int: JsFunction::from_object_unchecked(JsObject::default()),
is_finite: JsFunction::empty_intrinsic_function(false),
is_nan: JsFunction::empty_intrinsic_function(false),
parse_float: JsFunction::empty_intrinsic_function(false),
parse_int: JsFunction::empty_intrinsic_function(false),
#[cfg(feature = "annex-b")]
escape: JsFunction::from_object_unchecked(JsObject::default()),
escape: JsFunction::empty_intrinsic_function(false),
#[cfg(feature = "annex-b")]
unescape: JsFunction::from_object_unchecked(JsObject::default()),
unescape: JsFunction::empty_intrinsic_function(false),
#[cfg(feature = "intl")]
intl: JsObject::default(),
}
@ -822,7 +825,7 @@ impl IntrinsicObjects {
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
#[inline]
pub fn array_prototype_values(&self) -> JsObject {
pub fn array_prototype_values(&self) -> JsFunction {
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> {
/// 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]
pub fn new() -> Self {
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
use crate::{
object::{JsObject, JsObjectType},
object::{
internal_methods::function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS},
JsObject, JsObjectType, Object,
},
value::TryFromJs,
Context, JsNativeError, JsResult, JsValue,
};
@ -14,11 +17,30 @@ pub struct 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 {
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.
#[inline]

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

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

93
boa_engine/src/object/jsobject.rs

@ -2,7 +2,9 @@
//!
//! The `JsObject` is a garbage collected Object.
use super::{JsPrototype, NativeObject, Object, PropertyMap};
use super::{
internal_methods::InternalObjectMethods, JsPrototype, NativeObject, Object, PropertyMap,
};
use crate::{
context::intrinsics::Intrinsics,
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>;
/// Garbage collected `Object`.
#[derive(Trace, Finalize, Clone, Default)]
#[derive(Trace, Finalize, Clone)]
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 {
/// 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.
///
/// This is equivalent to calling the specification's abstract operation
@ -71,13 +104,16 @@ impl JsObject {
/// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
Self {
inner: Gc::new(GcRefCell::new(Object {
data,
prototype: prototype.into(),
extensible: true,
properties: PropertyMap::default(),
private_elements: ThinVec::new(),
})),
inner: Gc::new(VTableObject {
object: GcRefCell::new(Object {
kind: data.kind,
properties: PropertyMap::default(),
prototype: prototype.into(),
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).
#[inline]
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.
@ -127,7 +163,10 @@ impl JsObject {
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
#[inline]
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.
@ -852,7 +891,7 @@ Cannot both specify accessors and a value or writable attribute",
#[inline]
#[track_caller]
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.
@ -864,21 +903,19 @@ Cannot both specify accessors and a value or writable attribute",
#[inline]
#[track_caller]
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
pub fn is_global(&self) -> bool {
matches!(
self.borrow().data,
ObjectData {
kind: ObjectKind::Global,
..
}
)
matches!(self.inner.object.borrow().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
}
}
@ -886,13 +923,13 @@ Cannot both specify accessors and a value or writable attribute",
impl AsRef<GcRefCell<Object>> for JsObject {
#[inline]
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]
fn from(inner: Gc<GcRefCell<Object>>) -> Self {
fn from(inner: Gc<VTableObject>) -> Self {
Self { inner }
}
}
@ -1014,6 +1051,7 @@ impl RecursionLimiter {
impl Debug for JsObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
let ptr: *const _ = self.vtable();
let limiter = RecursionLimiter::new(self);
// 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
// at most once, hopefully making things a bit clearer.
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 {
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 {
JsValue::Object(proto) => Some(proto.clone()),
JsValue::Null => None,
JsValue::Undefined => Some(
context
.intrinsics()
.constructors()
.object()
.prototype
.clone(),
),
JsValue::Undefined => Some(context.intrinsics().constructors().object().prototype()),
_ => unreachable!(),
};

Loading…
Cancel
Save