From 254241600a4fc52c7aa5b725d955c4fa3b9fb10a Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Wed, 22 Mar 2023 01:11:58 +0100 Subject: [PATCH 01/20] Implementation of object shapes - Make forward reference weak gc - Add shape property table cache - Implement unique shape - Implement unique shape property deleting delete - Move property desciptor flags to shape - Remove unneeded Option in property_table - Implement shared property table - Remove unneeded return of insert and remove - Implement prototype transitions - Implement varying width property storage - Convert shape to unique if shape has exceeded transition max - Add documentation - Add `shape.md` documentation - Use FxHashMap for lookup and vec for keys - Direct initialization of arrays and objects with shape - Make functions share the same shape - Apply shared shapes to builtins - Add forward reference check for attribute change - Direct initialization of arrays --- .../src/builtins/array/array_iterator.rs | 3 +- boa_engine/src/builtins/array/mod.rs | 53 ++- boa_engine/src/builtins/array_buffer/mod.rs | 3 +- boa_engine/src/builtins/boolean/mod.rs | 6 +- boa_engine/src/builtins/dataview/mod.rs | 3 +- boa_engine/src/builtins/date/mod.rs | 6 +- boa_engine/src/builtins/error/aggregate.rs | 6 +- boa_engine/src/builtins/error/eval.rs | 6 +- boa_engine/src/builtins/error/mod.rs | 6 +- boa_engine/src/builtins/error/range.rs | 6 +- boa_engine/src/builtins/error/reference.rs | 6 +- boa_engine/src/builtins/error/syntax.rs | 6 +- boa_engine/src/builtins/error/type.rs | 6 +- boa_engine/src/builtins/error/uri.rs | 6 +- boa_engine/src/builtins/function/arguments.rs | 164 ++----- boa_engine/src/builtins/intl/collator/mod.rs | 3 +- boa_engine/src/builtins/iterable/mod.rs | 13 +- boa_engine/src/builtins/map/mod.rs | 6 +- boa_engine/src/builtins/number/mod.rs | 6 +- boa_engine/src/builtins/object/mod.rs | 6 +- boa_engine/src/builtins/promise/mod.rs | 3 +- boa_engine/src/builtins/proxy/mod.rs | 3 +- boa_engine/src/builtins/regexp/mod.rs | 6 +- boa_engine/src/builtins/set/mod.rs | 6 +- boa_engine/src/builtins/set/set_iterator.rs | 3 +- boa_engine/src/builtins/string/mod.rs | 6 +- boa_engine/src/builtins/typed_array/mod.rs | 6 +- boa_engine/src/builtins/weak/weak_ref.rs | 5 +- boa_engine/src/builtins/weak_map/mod.rs | 3 +- boa_engine/src/builtins/weak_set/mod.rs | 3 +- boa_engine/src/context/mod.rs | 12 +- boa_engine/src/environments/compile.rs | 14 +- boa_engine/src/error.rs | 6 +- boa_engine/src/object/builtins/jsdataview.rs | 3 +- boa_engine/src/object/builtins/jsdate.rs | 12 +- boa_engine/src/object/builtins/jsgenerator.rs | 3 +- boa_engine/src/object/builtins/jsmap.rs | 6 +- boa_engine/src/object/builtins/jspromise.rs | 3 +- boa_engine/src/object/builtins/jsproxy.rs | 3 +- .../src/object/internal_methods/global.rs | 25 +- .../internal_methods/integer_indexed.rs | 23 +- boa_engine/src/object/internal_methods/mod.rs | 35 +- .../src/object/internal_methods/string.rs | 21 +- boa_engine/src/object/jsobject.rs | 54 ++- boa_engine/src/object/mod.rs | 45 +- boa_engine/src/object/property_map.rs | 337 +++++++------- boa_engine/src/object/shape/mod.rs | 188 ++++++++ boa_engine/src/object/shape/property_table.rs | 144 ++++++ .../shape/shared_shape/forward_transition.rs | 58 +++ .../src/object/shape/shared_shape/mod.rs | 423 ++++++++++++++++++ .../src/object/shape/shared_shape/template.rs | 135 ++++++ boa_engine/src/object/shape/slot.rs | 80 ++++ boa_engine/src/object/shape/unique_shape.rs | 203 +++++++++ boa_engine/src/property/attribute/mod.rs | 2 +- boa_engine/src/property/mod.rs | 17 +- boa_engine/src/realm.rs | 160 ++++++- .../src/value/conversions/serde_json.rs | 11 +- boa_engine/src/value/display.rs | 26 +- boa_engine/src/value/hash.rs | 2 +- boa_engine/src/value/mod.rs | 77 ++-- boa_engine/src/value/tests.rs | 1 + boa_engine/src/vm/code_block.rs | 72 +++ boa_engine/src/vm/opcode/define/mod.rs | 23 +- boa_engine/src/vm/opcode/get/function.rs | 10 +- boa_engine/src/vm/opcode/get/name.rs | 9 +- boa_engine/src/vm/opcode/push/array.rs | 9 +- boa_engine/src/vm/opcode/push/object.rs | 7 +- .../src/vm/opcode/set/class_prototype.rs | 6 +- boa_engine/src/vm/opcode/set/name.rs | 6 +- docs/shapes.md | 220 +++++++++ 70 files changed, 2289 insertions(+), 566 deletions(-) create mode 100644 boa_engine/src/object/shape/mod.rs create mode 100644 boa_engine/src/object/shape/property_table.rs create mode 100644 boa_engine/src/object/shape/shared_shape/forward_transition.rs create mode 100644 boa_engine/src/object/shape/shared_shape/mod.rs create mode 100644 boa_engine/src/object/shape/shared_shape/template.rs create mode 100644 boa_engine/src/object/shape/slot.rs create mode 100644 boa_engine/src/object/shape/unique_shape.rs create mode 100644 docs/shapes.md diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index c625478858c..d96220dce4a 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -77,7 +77,8 @@ impl ArrayIterator { kind: PropertyNameKind, context: &Context<'_>, ) -> JsValue { - let array_iterator = JsObject::from_proto_and_data( + let array_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), context.intrinsics().objects().iterator_prototypes().array(), ObjectData::array_iterator(Self::new(array, kind)), ); diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 793d4d8d72b..77552468091 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -203,17 +203,18 @@ impl BuiltInConstructor for Array { // b. Let array be ? ArrayCreate(numberOfArgs, proto). let array = Self::array_create(number_of_args as u64, Some(prototype), context)?; + // c. Let k be 0. // d. Repeat, while k < numberOfArgs, - for (i, item) in args.iter().cloned().enumerate() { - // i. Let Pk be ! ToString(𝔽(k)). - // ii. Let itemK be values[k]. - // iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK). - array - .create_data_property_or_throw(i, item, context) - .expect("this CreateDataPropertyOrThrow must not fail"); - // iv. Set k to k + 1. - } + // i. Let Pk be ! ToString(𝔽(k)). + // ii. Let itemK be values[k]. + // iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK). + // iv. Set k to k + 1. + array + .borrow_mut() + .properties_mut() + .override_indexed_properties(args.iter().cloned().collect()); + // e. Assert: The mathematical value of array's "length" property is numberOfArgs. // f. Return array. Ok(array.into()) @@ -239,6 +240,15 @@ impl Array { .with_message("array exceeded max size") .into()); } + + // Fast path: + if prototype.is_none() || prototype == context.realm.array_object_template.prototype() { + return Ok(context + .realm + .array_object_template + .create(ObjectData::array(), vec![JsValue::new(length)])); + } + // 7. Return A. // 2. If proto is not present, set proto to %Array.prototype%. // 3. Let A be ! MakeBasicObject(« [[Prototype]], [[Extensible]] »). @@ -246,7 +256,12 @@ impl Array { // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1. let prototype = prototype.unwrap_or_else(|| context.intrinsics().constructors().array().prototype()); - let array = JsObject::from_proto_and_data(prototype, ObjectData::array()); + + let array = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::array(), + ); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). crate::object::internal_methods::ordinary_define_own_property( @@ -276,27 +291,19 @@ impl Array { { // 1. Assert: elements is a List whose elements are all ECMAScript language values. // 2. Let array be ! ArrayCreate(0). - let array = Self::array_create(0, None, context) - .expect("creating an empty array with the default prototype must not fail"); - // 3. Let n be 0. // 4. For each element e of elements, do // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(n)), e). // b. Set n to n + 1. - // + // 5. Return array. // NOTE: This deviates from the spec, but it should have the same behaviour. let elements: ThinVec<_> = elements.into_iter().collect(); let length = elements.len(); - array - .borrow_mut() - .properties_mut() - .override_indexed_properties(elements); - array - .set(utf16!("length"), length, true, context) - .expect("Should not fail"); - // 5. Return array. - array + context + .realm + .array_object_template + .create_with_index_properties(ObjectData::array(), vec![JsValue::new(length)], elements) } /// Utility function for concatenating array objects. diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index be5b99fedfb..7dc414d3fea 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -354,7 +354,8 @@ impl ArrayBuffer { // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. - let obj = JsObject::from_proto_and_data( + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), prototype, ObjectData::array_buffer(Self { array_buffer_data: Some(block), diff --git a/boa_engine/src/builtins/boolean/mod.rs b/boa_engine/src/builtins/boolean/mod.rs index 18cdea1750d..1ed97311afd 100644 --- a/boa_engine/src/builtins/boolean/mod.rs +++ b/boa_engine/src/builtins/boolean/mod.rs @@ -67,7 +67,11 @@ impl BuiltInConstructor for Boolean { } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?; - let boolean = JsObject::from_proto_and_data(prototype, ObjectData::boolean(data)); + let boolean = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::boolean(data), + ); Ok(boolean.into()) } diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 0999c5a1c9b..72d14c4f6cc 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -193,7 +193,8 @@ impl BuiltInConstructor for DataView { .into()); } - let obj = JsObject::from_proto_and_data( + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), prototype, ObjectData::data_view(Self { // 11. Set O.[[ViewedArrayBuffer]] to buffer. diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index aed1cbce60f..d44e9227409 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -267,7 +267,11 @@ impl BuiltInConstructor for Date { get_prototype_from_constructor(new_target, StandardConstructors::date, context)?; // 7. Set O.[[DateValue]] to dv. - let obj = JsObject::from_proto_and_data(prototype, ObjectData::date(dv)); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::date(dv), + ); // 8. Return O. Ok(obj.into()) diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index becc22f0154..1df777ef24a 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -66,7 +66,11 @@ impl BuiltInConstructor for AggregateError { StandardConstructors::aggregate_error, context, )?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Aggregate)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Aggregate), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(1); diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index cd1581609d7..a9d57b95338 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -65,7 +65,11 @@ impl BuiltInConstructor for EvalError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Eval)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Eval), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index 6c11588e384..8a2f17f0957 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -166,7 +166,11 @@ impl BuiltInConstructor for Error { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Error)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Error), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index e6618cf37be..6a796343336 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -63,7 +63,11 @@ impl BuiltInConstructor for RangeError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Range)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Range), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index 710983db048..659d8164053 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -65,7 +65,11 @@ impl BuiltInConstructor for ReferenceError { StandardConstructors::reference_error, context, )?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Reference)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Reference), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index 70811c4fcae..eb18c1a4d5b 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -68,7 +68,11 @@ impl BuiltInConstructor for SyntaxError { StandardConstructors::syntax_error, context, )?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Syntax)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Syntax), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 6a82717bf24..43bee8ba2f1 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -73,7 +73,11 @@ impl BuiltInConstructor for TypeError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Type)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Type), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index 180a368d35f..d455a43873e 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -64,7 +64,11 @@ impl BuiltInConstructor for UriError { // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(ErrorKind::Uri)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(ErrorKind::Uri), + ); // 3. If message is not undefined, then let message = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 40b4c97acd2..7fe20aee5bf 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -1,9 +1,6 @@ use crate::{ environments::DeclarativeEnvironment, object::{JsObject, ObjectData}, - property::PropertyDescriptor, - string::utf16, - symbol::{self, JsSymbol}, Context, JsValue, }; use boa_ast::{function::FormalParameterList, operations::bound_names}; @@ -77,68 +74,38 @@ impl Arguments { // 1. Let len be the number of elements in argumentsList. let len = arguments_list.len(); + let values_function = context.intrinsics().objects().array_prototype_values(); + let throw_type_error = context.intrinsics().objects().throw_type_error(); + // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). // 3. Set obj.[[ParameterMap]] to undefined. // skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]` - let obj = JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), + let obj = context.realm.unmapped_arguments_object_templete.create( ObjectData::arguments(Self::Unmapped), + vec![ + // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + len.into(), + // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // [[Configurable]]: true }). + values_function.into(), + // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, + // [[Configurable]]: false }). + throw_type_error.clone().into(), // get + throw_type_error.into(), // set + ], ); - // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), - // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - obj.define_property_or_throw( - utf16!("length"), - PropertyDescriptor::builder() - .value(len) - .writable(true) - .enumerable(false) - .configurable(true), - context, - ) - .expect("Defining new own properties for a new ordinary object cannot fail"); - // 5. Let index be 0. // 6. Repeat, while index < len, - for (index, value) in arguments_list.iter().cloned().enumerate() { - // a. Let val be argumentsList[index]. - // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). - obj.create_data_property_or_throw(index, value, context) - .expect("Defining new own properties for a new ordinary object cannot fail"); - - // c. Set index to index + 1. - } - - // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { - // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, - // [[Configurable]]: true }). - let values_function = context.intrinsics().objects().array_prototype_values(); - obj.define_property_or_throw( - symbol::JsSymbol::iterator(), - PropertyDescriptor::builder() - .value(values_function) - .writable(true) - .enumerable(false) - .configurable(true), - context, - ) - .expect("Defining new own properties for a new ordinary object cannot fail"); - - let throw_type_error = context.intrinsics().objects().throw_type_error(); - - // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { - // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, - // [[Configurable]]: false }). - obj.define_property_or_throw( - utf16!("callee"), - PropertyDescriptor::builder() - .get(throw_type_error.clone()) - .set(throw_type_error) - .enumerable(false) - .configurable(false), - context, - ) - .expect("Defining new own properties for a new ordinary object cannot fail"); + // a. Let val be argumentsList[index]. + // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). + // c. Set index to index + 1. + obj.borrow_mut() + .properties_mut() + .override_indexed_properties(arguments_list.iter().cloned().collect()); // 9. Return obj. obj @@ -222,71 +189,36 @@ impl Arguments { map.binding_indices[*property_index] = Some(*binding_index); } + // %Array.prototype.values% + let values_function = context.intrinsics().objects().array_prototype_values(); + // 11. Set obj.[[ParameterMap]] to map. - let obj = JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), + let obj = context.realm.mapped_arguments_object_templete.create( ObjectData::arguments(Self::Mapped(map)), + vec![ + // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + len.into(), + // 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // [[Configurable]]: true }). + values_function.into(), + // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + func.clone().into(), + ], ); // 14. Let index be 0. // 15. Repeat, while index < len, - for (index, val) in arguments_list.iter().cloned().enumerate() { - // a. Let val be argumentsList[index]. - // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). - // Note: Insert is used here because `CreateDataPropertyOrThrow` would cause a panic while executing - // exotic argument object set methods before the variables in the environment are initialized. - obj.insert( - index, - PropertyDescriptor::builder() - .value(val) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - ); - // c. Set index to index + 1. - } - - // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), - // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - obj.define_property_or_throw( - utf16!("length"), - PropertyDescriptor::builder() - .value(len) - .writable(true) - .enumerable(false) - .configurable(true), - context, - ) - .expect("Defining new own properties for a new ordinary object cannot fail"); - - // 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { - // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, - // [[Configurable]]: true }). - let values_function = context.intrinsics().objects().array_prototype_values(); - obj.define_property_or_throw( - JsSymbol::iterator(), - PropertyDescriptor::builder() - .value(values_function) - .writable(true) - .enumerable(false) - .configurable(true), - context, - ) - .expect("Defining new own properties for a new ordinary object cannot fail"); - - // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { - // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - obj.define_property_or_throw( - utf16!("callee"), - PropertyDescriptor::builder() - .value(func.clone()) - .writable(true) - .enumerable(false) - .configurable(true), - context, - ) - .expect("Defining new own properties for a new ordinary object cannot fail"); + // a. Let val be argumentsList[index]. + // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). + // Note: Direct initialization of indexed array is used here because `CreateDataPropertyOrThrow` + // would cause a panic while executing exotic argument object set methods before the variables + // in the environment are initialized. + obj.borrow_mut() + .properties_mut() + .override_indexed_properties(arguments_list.iter().cloned().collect()); // 22. Return obj. obj diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index 5af7149d1bb..c0cb6178758 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -353,7 +353,8 @@ impl BuiltInConstructor for Collator { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::collator, context)?; - let collator = JsObject::from_proto_and_data( + let collator = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), prototype, ObjectData::collator(Self { locale, diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index c7e23bc4a5b..dee019a567c 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -4,7 +4,7 @@ use crate::{ builtins::{BuiltInBuilder, IntrinsicObject}, context::intrinsics::Intrinsics, error::JsNativeError, - object::JsObject, + object::{JsObject, ObjectData}, string::utf16, symbol::JsSymbol, Context, JsResult, JsValue, @@ -186,14 +186,13 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte // 1. Assert: Type(done) is Boolean. // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). - let obj = JsObject::with_object_proto(context); - // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value). - obj.create_data_property_or_throw(utf16!("value"), value, context) - .expect("this CreateDataPropertyOrThrow call must not fail"); // 4. Perform ! CreateDataPropertyOrThrow(obj, "done", done). - obj.create_data_property_or_throw(utf16!("done"), done, context) - .expect("this CreateDataPropertyOrThrow call must not fail"); + let obj = context + .realm + .iterator_result_object_template + .create(ObjectData::ordinary(), vec![value, done.into()]); + // 5. Return obj. obj.into() } diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index b058f686cbe..346ca475aac 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -135,7 +135,11 @@ impl BuiltInConstructor for Map { // 3. Set map.[[MapData]] to a new empty List. let prototype = get_prototype_from_constructor(new_target, StandardConstructors::map, context)?; - let map = JsObject::from_proto_and_data(prototype, ObjectData::map(OrderedMap::new())); + let map = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::map(OrderedMap::new()), + ); // 4. If iterable is either undefined or null, return map. let iterable = match args.get_or_undefined(0) { diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index bb6c8c489cc..33bcac61889 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -120,7 +120,11 @@ impl BuiltInConstructor for Number { } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::number, context)?; - let this = JsObject::from_proto_and_data(prototype, ObjectData::number(data)); + let this = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::number(data), + ); Ok(this.into()) } } diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index daa35e76961..921a479e410 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -137,7 +137,11 @@ impl BuiltInConstructor for Object { // a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%"). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::object, context)?; - let object = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); + let object = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::ordinary(), + ); return Ok(object.into()); } diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 6a1a25cde7e..1517f04d0c4 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -384,7 +384,8 @@ impl BuiltInConstructor for Promise { let promise = get_prototype_from_constructor(new_target, StandardConstructors::promise, context)?; - let promise = JsObject::from_proto_and_data( + let promise = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), promise, // 4. Set promise.[[PromiseState]] to pending. // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 197dbb3a3f4..da81208ec5c 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -127,7 +127,8 @@ impl Proxy { // i. Set P.[[Construct]] as specified in 10.5.13. // 6. Set P.[[ProxyTarget]] to target. // 7. Set P.[[ProxyHandler]] to handler. - let p = JsObject::from_proto_and_data( + let p = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), context.intrinsics().constructors().object().prototype(), ObjectData::proxy( Self::new(target.clone(), handler.clone()), diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 8f70a93fcf6..71535735856 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -230,7 +230,11 @@ impl RegExp { // 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[RegExpMatcher]], [[OriginalSource]], [[OriginalFlags]] »). let proto = get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?; - let obj = JsObject::from_proto_and_data(proto, ObjectData::ordinary()); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + proto, + ObjectData::ordinary(), + ); // 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). obj.define_property_or_throw( diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 19418ebe73b..d9566e33f7b 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -126,7 +126,11 @@ impl BuiltInConstructor for Set { // 3. Set set.[[SetData]] to a new empty List. let prototype = get_prototype_from_constructor(new_target, StandardConstructors::set, context)?; - let set = JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::default())); + let set = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::set(OrderedSet::default()), + ); // 4. If iterable is either undefined or null, return set. let iterable = args.get_or_undefined(0); diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 4fcf1812fa3..b23c8a6953f 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -80,7 +80,8 @@ impl SetIterator { lock: SetLock, context: &Context<'_>, ) -> JsValue { - let set_iterator = JsObject::from_proto_and_data( + let set_iterator = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), context.intrinsics().objects().iterator_prototypes().set(), ObjectData::set_iterator(Self::new(set, kind, lock)), ); diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 5fa46a6f2d7..b29c94ad3bf 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -185,7 +185,11 @@ impl String { // 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1. // 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2. // 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3. - let s = JsObject::from_proto_and_data(prototype, ObjectData::string(value)); + let s = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::string(value), + ); // 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length), // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 32f4ba5e273..1674b618bb6 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -3196,7 +3196,11 @@ impl TypedArray { } // 2. Let obj be ! IntegerIndexedObjectCreate(proto). - let obj = JsObject::from_proto_and_data(proto, ObjectData::integer_indexed(indexed)); + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + proto, + ObjectData::integer_indexed(indexed), + ); // 9. Return obj. Ok(obj) diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs index ee4399c2508..80067294875 100644 --- a/boa_engine/src/builtins/weak/weak_ref.rs +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -159,7 +159,10 @@ mod tests { "#}, |v, _| v.is_object(), ), - TestAction::inspect_context(|_| boa_gc::force_collect()), + TestAction::inspect_context(|context| { + context.clear_kept_objects(); + boa_gc::force_collect(); + }), TestAction::assert_eq("ptr.deref()", JsValue::undefined()), ]); } diff --git a/boa_engine/src/builtins/weak_map/mod.rs b/boa_engine/src/builtins/weak_map/mod.rs index acb4a4ea6bb..c6285aae7e0 100644 --- a/boa_engine/src/builtins/weak_map/mod.rs +++ b/boa_engine/src/builtins/weak_map/mod.rs @@ -81,7 +81,8 @@ impl BuiltInConstructor for WeakMap { // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »). // 3. Set map.[[WeakMapData]] to a new empty List. - let map = JsObject::from_proto_and_data( + let map = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), get_prototype_from_constructor(new_target, StandardConstructors::weak_map, context)?, ObjectData::weak_map(boa_gc::WeakMap::new()), ); diff --git a/boa_engine/src/builtins/weak_set/mod.rs b/boa_engine/src/builtins/weak_set/mod.rs index 1c5d0873137..328e8e2ec73 100644 --- a/boa_engine/src/builtins/weak_set/mod.rs +++ b/boa_engine/src/builtins/weak_set/mod.rs @@ -77,7 +77,8 @@ impl BuiltInConstructor for WeakSet { // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakSet.prototype%", « [[WeakSetData]] »). // 3. Set set.[[WeakSetData]] to a new empty List. - let weak_set = JsObject::from_proto_and_data( + let weak_set = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), get_prototype_from_constructor(new_target, StandardConstructors::weak_set, context)?, ObjectData::weak_set(WeakMap::new()), ); diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 2e92181dfc5..8a1006592a4 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -22,7 +22,8 @@ use crate::{ class::{Class, ClassBuilder}, job::{IdleJobQueue, JobQueue, NativeJob}, native_function::NativeFunction, - object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject}, + object::PropertyMap, + object::{FunctionObjectBuilder, JsObject}, optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -340,7 +341,7 @@ impl Context<'_> { .build(); self.global_bindings_mut().insert( - name.into(), + &PropertyKey::String(name.into()), PropertyDescriptor::builder() .value(function) .writable(true) @@ -372,7 +373,7 @@ impl Context<'_> { .build(); self.global_bindings_mut().insert( - name.into(), + &PropertyKey::String(name.into()), PropertyDescriptor::builder() .value(function) .writable(true) @@ -410,7 +411,8 @@ impl Context<'_> { .configurable(T::ATTRIBUTES.configurable()) .build(); - self.global_bindings_mut().insert(T::NAME.into(), property); + self.global_bindings_mut() + .insert(&PropertyKey::String(T::NAME.into()), property); Ok(()) } @@ -486,7 +488,7 @@ impl Context<'_> { impl Context<'_> { /// Return a mutable reference to the global object string bindings. - pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap { + pub(crate) fn global_bindings_mut(&mut self) -> &mut PropertyMap { self.realm.global_bindings_mut() } diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 5b7725295b8..cdb81d00162 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -1,5 +1,7 @@ use crate::{ - environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue, + environments::runtime::BindingLocator, + property::{PropertyDescriptor, PropertyKey}, + Context, JsString, JsValue, }; use boa_ast::expression::Identifier; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; @@ -289,14 +291,12 @@ impl Context<'_> { .interner() .resolve_expect(name.sym()) .into_common::(false); - let desc = self - .realm - .global_property_map - .string_property_map() - .get(&name_str); + + let key = PropertyKey::String(name_str); + let desc = self.realm.global_property_map.get(&key); if desc.is_none() { self.global_bindings_mut().insert( - name_str, + &key, PropertyDescriptor::builder() .value(JsValue::Undefined) .writable(true) diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index c5f02ea446a..8e3d8c3ae56 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -669,7 +669,11 @@ impl JsNativeError { } }; - let o = JsObject::from_proto_and_data(prototype, ObjectData::error(tag)); + let o = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::error(tag), + ); o.create_non_enumerable_data_property_or_throw(utf16!("message"), &**message, context); diff --git a/boa_engine/src/object/builtins/jsdataview.rs b/boa_engine/src/object/builtins/jsdataview.rs index 444103a7d0d..0bf39867dfc 100644 --- a/boa_engine/src/object/builtins/jsdataview.rs +++ b/boa_engine/src/object/builtins/jsdataview.rs @@ -96,7 +96,8 @@ impl JsDataView { let prototype = get_prototype_from_constructor(&constructor, StandardConstructors::data_view, context)?; - let obj = JsObject::from_proto_and_data( + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), prototype, ObjectData::data_view(DataView { viewed_array_buffer: (**array_buffer).clone(), diff --git a/boa_engine/src/object/builtins/jsdate.rs b/boa_engine/src/object/builtins/jsdate.rs index 544a47ff141..3792a2980df 100644 --- a/boa_engine/src/object/builtins/jsdate.rs +++ b/boa_engine/src/object/builtins/jsdate.rs @@ -42,7 +42,11 @@ impl JsDate { #[inline] pub fn new(context: &mut Context<'_>) -> Self { let prototype = context.intrinsics().constructors().date().prototype(); - let inner = JsObject::from_proto_and_data(prototype, ObjectData::date(Date::default())); + let inner = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::date(Date::default()), + ); Self { inner } } @@ -558,7 +562,11 @@ impl JsDate { let date_time = Date::new(Some(date_time.naive_local())); Ok(Self { - inner: JsObject::from_proto_and_data(prototype, ObjectData::date(date_time)), + inner: JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::date(date_time), + ), }) } } diff --git a/boa_engine/src/object/builtins/jsgenerator.rs b/boa_engine/src/object/builtins/jsgenerator.rs index 0fccc5d9a4c..feef3ab017b 100644 --- a/boa_engine/src/object/builtins/jsgenerator.rs +++ b/boa_engine/src/object/builtins/jsgenerator.rs @@ -20,7 +20,8 @@ impl JsGenerator { pub fn new(context: &mut Context<'_>) -> Self { let prototype = context.intrinsics().objects().generator(); - let generator = JsObject::from_proto_and_data( + let generator = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), prototype, ObjectData::generator(Generator { state: GeneratorState::Undefined, diff --git a/boa_engine/src/object/builtins/jsmap.rs b/boa_engine/src/object/builtins/jsmap.rs index 5eb184ab970..3168d2c843e 100644 --- a/boa_engine/src/object/builtins/jsmap.rs +++ b/boa_engine/src/object/builtins/jsmap.rs @@ -184,7 +184,11 @@ impl JsMap { let prototype = context.intrinsics().constructors().map().prototype(); // Create a default map object with [[MapData]] as a new empty list - JsObject::from_proto_and_data(prototype, ObjectData::map(OrderedMap::new())) + JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::map(OrderedMap::new()), + ) } /// Returns a new [`JsMapIterator`] object that yields the `[key, value]` pairs within the [`JsMap`] in insertion order. diff --git a/boa_engine/src/object/builtins/jspromise.rs b/boa_engine/src/object/builtins/jspromise.rs index 8618e5dea68..91ca587011c 100644 --- a/boa_engine/src/object/builtins/jspromise.rs +++ b/boa_engine/src/object/builtins/jspromise.rs @@ -204,7 +204,8 @@ impl JsPromise { /// ``` #[inline] pub fn new_pending(context: &mut Context<'_>) -> (JsPromise, ResolvingFunctions) { - let promise = JsObject::from_proto_and_data( + let promise = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), context.intrinsics().constructors().promise().prototype(), ObjectData::promise(Promise::new()), ); diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index ba840f20fd6..78c105ec1cc 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -493,7 +493,8 @@ impl JsProxyBuilder { let callable = self.target.is_callable(); let constructor = self.target.is_constructor(); - let proxy = JsObject::from_proto_and_data( + let proxy = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), context.intrinsics().constructors().object().prototype(), ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor), ); diff --git a/boa_engine/src/object/internal_methods/global.rs b/boa_engine/src/object/internal_methods/global.rs index 41be3c3d052..f9c699fef35 100644 --- a/boa_engine/src/object/internal_methods/global.rs +++ b/boa_engine/src/object/internal_methods/global.rs @@ -254,30 +254,15 @@ pub(crate) fn global_own_property_keys( }; // 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do - // a. Add P as the last element of keys. + // a. Add P as the last element of keys. keys.extend(ordered_indexes.into_iter().map(Into::into)); // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - context - .realm - .global_property_map - .string_property_keys() - .cloned() - .map(Into::into), - ); - + // a. Add P as the last element of keys. + // // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - context - .realm - .global_property_map - .symbol_property_keys() - .cloned() - .map(Into::into), - ); + // a. Add P as the last element of keys. + keys.extend(context.realm.global_property_map.shape.keys()); // 5. Return keys. Ok(keys) diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs index e5cf99063d5..2e9f5d13117 100644 --- a/boa_engine/src/object/internal_methods/integer_indexed.rs +++ b/boa_engine/src/object/internal_methods/integer_indexed.rs @@ -222,30 +222,19 @@ pub(crate) fn integer_indexed_exotic_own_property_keys( vec![] } else { // 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then - // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do - // i. Add ! ToString(𝔽(i)) as the last element of keys. + // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do + // i. Add ! ToString(𝔽(i)) as the last element of keys. (0..inner.array_length()) .map(|index| PropertyKey::Index(index as u32)) .collect() }; // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - obj.properties - .string_property_keys() - .cloned() - .map(Into::into), - ); - + // a. Add P as the last element of keys. + // // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - obj.properties - .symbol_property_keys() - .cloned() - .map(Into::into), - ); + // a. Add P as the last element of keys. + keys.extend(obj.properties.shape.keys()); // 5. Return keys. Ok(keys) diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 00279675f66..37976b2abd8 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -348,14 +348,12 @@ pub(crate) fn ordinary_set_prototype_of( _: &mut Context<'_>, ) -> JsResult { // 1. Assert: Either Type(V) is Object or Type(V) is Null. - { - // 2. Let current be O.[[Prototype]]. - let current = obj.prototype(); + // 2. Let current be O.[[Prototype]]. + let current = obj.prototype(); - // 3. If SameValue(V, current) is true, return true. - if val == *current { - return Ok(true); - } + // 3. If SameValue(V, current) is true, return true. + if val == current { + return Ok(true); } // 4. Let extensible be O.[[Extensible]]. @@ -384,7 +382,7 @@ pub(crate) fn ordinary_set_prototype_of( break; } // ii. Else, set p to p.[[Prototype]]. - p = proto.prototype().clone(); + p = proto.prototype(); } // 9. Set O.[[Prototype]] to V. @@ -714,24 +712,11 @@ pub(crate) fn ordinary_own_property_keys( keys.extend(ordered_indexes.into_iter().map(Into::into)); // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - obj.borrow() - .properties - .string_property_keys() - .cloned() - .map(Into::into), - ); - + // a. Add P as the last element of keys. + // // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - obj.borrow() - .properties - .symbol_property_keys() - .cloned() - .map(Into::into), - ); + // a. Add P as the last element of keys. + keys.extend(obj.borrow().properties.shape.keys()); // 5. Return keys. Ok(keys) diff --git a/boa_engine/src/object/internal_methods/string.rs b/boa_engine/src/object/internal_methods/string.rs index c4151741694..0c919b5fab5 100644 --- a/boa_engine/src/object/internal_methods/string.rs +++ b/boa_engine/src/object/internal_methods/string.rs @@ -101,12 +101,12 @@ pub(crate) fn string_exotic_own_property_keys( let mut keys = Vec::with_capacity(len); // 5. For each integer i starting with 0 such that i < len, in ascending order, do - // a. Add ! ToString(𝔽(i)) as the last element of keys. + // a. Add ! ToString(𝔽(i)) as the last element of keys. keys.extend((0..len).map(Into::into)); // 6. For each own property key P of O such that P is an array index // and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do - // a. Add P as the last element of keys. + // a. Add P as the last element of keys. let mut remaining_indices: Vec<_> = obj .properties .index_property_keys() @@ -117,23 +117,12 @@ pub(crate) fn string_exotic_own_property_keys( // 7. For each own property key P of O such that Type(P) is String and P is not // an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - obj.properties - .string_property_keys() - .cloned() - .map(Into::into), - ); + // a. Add P as the last element of keys. // 8. For each own property key P of O such that Type(P) is Symbol, in ascending // chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend( - obj.properties - .symbol_property_keys() - .cloned() - .map(Into::into), - ); + // a. Add P as the last element of keys. + keys.extend(obj.properties.shape.keys()); // 9. Return keys. Ok(keys) diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index ca4f497a2fc..38926336b36 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -2,7 +2,10 @@ //! //! The `JsObject` is a garbage collected Object. -use super::{JsPrototype, NativeObject, Object, PropertyMap}; +use super::{ + shape::{shared_shape::SharedShape, Shape}, + JsPrototype, NativeObject, Object, PropertyMap, +}; use crate::{ error::JsNativeError, object::{ObjectData, ObjectKind}, @@ -17,6 +20,7 @@ use std::{ collections::HashMap, error::Error, fmt::{self, Debug, Display}, + hash::Hash, result::Result as StdResult, }; use thin_vec::ThinVec; @@ -72,9 +76,33 @@ impl JsObject { Self { inner: Gc::new(GcRefCell::new(Object { data, - prototype: prototype.into(), extensible: true, - properties: PropertyMap::default(), + properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + private_elements: ThinVec::new(), + })), + } + } + + /// Creates a new object with the provided prototype and object data. + /// + /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`], + /// with the difference that the `additionalInternalSlotsList` parameter is automatically set by + /// the [`ObjectData`] provided. + /// + /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate + pub(crate) fn from_proto_and_data_with_shared_shape>>( + root_shape: SharedShape, + prototype: O, + data: ObjectData, + ) -> Self { + Self { + inner: Gc::new(GcRefCell::new(Object { + data, + extensible: true, + properties: PropertyMap::from_prototype_with_shared_shape( + Shape::shared(root_shape), + prototype.into(), + ), private_elements: ThinVec::new(), })), } @@ -273,8 +301,8 @@ impl JsObject { /// Panics if the object is currently mutably borrowed. #[inline] #[track_caller] - pub fn prototype(&self) -> Ref<'_, JsPrototype> { - Ref::map(self.borrow(), Object::prototype) + pub fn prototype(&self) -> JsPrototype { + self.borrow().prototype() } /// Get the extensibility of the object. @@ -701,7 +729,7 @@ Cannot both specify accessors and a value or writable attribute", /// Helper function for property insertion. #[track_caller] - pub(crate) fn insert(&self, key: K, property: P) -> Option + pub(crate) fn insert(&self, key: K, property: P) -> bool where K: Into, P: Into, @@ -711,9 +739,9 @@ Cannot both specify accessors and a value or writable attribute", /// Inserts a field in the object `properties` without checking if it's writable. /// - /// If a field was already in the object with the same name that a `Some` is returned - /// with that field, otherwise None is returned. - pub fn insert_property(&self, key: K, property: P) -> Option + /// If a field was already in the object with the same name, than `true` is returned + /// with that field, otherwise `false` is returned. + pub fn insert_property(&self, key: K, property: P) -> bool where K: Into, P: Into, @@ -781,6 +809,14 @@ impl PartialEq for JsObject { } } +impl Eq for JsObject {} + +impl Hash for JsObject { + fn hash(&self, state: &mut H) { + std::ptr::hash(self.as_ref(), state); + } +} + /// An error returned by [`JsObject::try_borrow`](struct.JsObject.html#method.try_borrow). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BorrowError; diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index dd862edbdf4..7426a3153d0 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -72,6 +72,7 @@ pub mod builtins; mod jsobject; mod operations; mod property_map; +pub(crate) mod shape; pub(crate) use builtins::*; @@ -95,6 +96,11 @@ pub const PROTOTYPE: &[u16] = utf16!("prototype"); /// A `None` values means that the prototype is the `null` value. pub type JsPrototype = Option; +/// The internal storage of an object's property values. +/// +/// The [`shape::Shape`] contains the property names and attributes. +pub(crate) type ObjectStorage = Vec; + /// This trait allows Rust types to be passed around as objects. /// /// This is automatically implemented when a type implements `Any` and `Trace`. @@ -123,8 +129,6 @@ pub struct Object { pub data: ObjectData, /// The collection of properties contained in the object properties: PropertyMap, - /// Instance prototype `__proto__`. - prototype: JsPrototype, /// Whether it can have new properties added to it. extensible: bool, /// The `[[PrivateElements]]` internal slot. @@ -135,7 +139,6 @@ unsafe impl Trace for Object { boa_gc::custom_trace!(this, { mark(&this.data); mark(&this.properties); - mark(&this.prototype); for (_, element) in &this.private_elements { mark(element); } @@ -780,7 +783,6 @@ impl Default for Object { Self { data: ObjectData::ordinary(), properties: PropertyMap::default(), - prototype: None, extensible: true, private_elements: ThinVec::default(), } @@ -1620,8 +1622,8 @@ impl Object { /// Gets the prototype instance of this object. #[inline] - pub const fn prototype(&self) -> &JsPrototype { - &self.prototype + pub fn prototype(&self) -> JsPrototype { + self.properties.shape.prototype() } /// Sets the prototype instance of the object. @@ -1633,12 +1635,12 @@ impl Object { pub fn set_prototype>(&mut self, prototype: O) -> bool { let prototype = prototype.into(); if self.extensible { - self.prototype = prototype; + self.properties.shape = self.properties.shape.change_prototype_transition(prototype); true } else { // If target is non-extensible, [[SetPrototypeOf]] must return false // unless V is the SameValue as the target's observed [[GetPrototypeOf]] value. - self.prototype == prototype + self.prototype() == prototype } } @@ -1836,9 +1838,9 @@ impl Object { /// Inserts a field in the object `properties` without checking if it's writable. /// - /// If a field was already in the object with the same name, then a `Some` is returned - /// with that field's value, otherwise, `None` is returned. - pub(crate) fn insert(&mut self, key: K, property: P) -> Option + /// If a field was already in the object with the same name, then `true` is returned + /// otherwise, `false` is returned. + pub(crate) fn insert(&mut self, key: K, property: P) -> bool where K: Into, P: Into, @@ -1848,7 +1850,7 @@ impl Object { /// Helper function for property removal. #[inline] - pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option { + pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool { self.properties.remove(key) } @@ -2016,22 +2018,12 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> { /// Build the function object. pub fn build(self) -> JsFunction { - let function = JsObject::from_proto_and_data( - self.context - .intrinsics() - .constructors() - .function() - .prototype(), + let object = self.context.realm.function_object_template.create( ObjectData::function(self.function), + vec![self.name.into(), self.length.into()], ); - let property = PropertyDescriptor::builder() - .writable(false) - .enumerable(false) - .configurable(true); - function.insert_property(utf16!("length"), property.clone().value(self.length)); - function.insert_property(utf16!("name"), property.value(self.name)); - JsFunction::from_object_unchecked(function) + JsFunction::from_object_unchecked(object) } } @@ -2079,7 +2071,8 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> { /// Create a new `ObjectBuilder` with custom [`NativeObject`] data. pub fn with_native(data: T, context: &'ctx mut Context<'host>) -> Self { - let object = JsObject::from_proto_and_data( + let object = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), context.intrinsics().constructors().object().prototype(), ObjectData::native_object(data), ); diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index d9923f8ef19..54119a1159b 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -1,4 +1,12 @@ -use super::{PropertyDescriptor, PropertyKey}; +use super::{ + shape::{ + property_table::PropertyTableInner, + shared_shape::TransitionKey, + slot::{Slot, SlotAttribute}, + ChangeTransitionAction, Shape, UniqueShape, + }, + JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey, +}; use crate::{property::PropertyDescriptorBuilder, JsString, JsSymbol, JsValue}; use boa_gc::{custom_trace, Finalize, Trace}; use indexmap::IndexMap; @@ -6,10 +14,6 @@ use rustc_hash::{FxHashMap, FxHasher}; use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator}; use thin_vec::ThinVec; -/// Type alias to make it easier to work with the string properties on the global object. -pub(crate) type GlobalPropertyMap = - IndexMap>; - /// Wrapper around `indexmap::IndexMap` for usage in `PropertyMap`. #[derive(Debug, Finalize)] struct OrderedHashMap(IndexMap>); @@ -99,9 +103,9 @@ impl IndexedProperties { } /// Inserts a property descriptor with the specified key. - fn insert(&mut self, key: u32, property: PropertyDescriptor) -> Option { + fn insert(&mut self, key: u32, property: PropertyDescriptor) -> bool { let vec = match self { - Self::Sparse(map) => return map.insert(key, property), + Self::Sparse(map) => return map.insert(key, property).is_some(), Self::Dense(vec) => { let len = vec.len() as u32; if key <= len @@ -122,19 +126,12 @@ impl IndexedProperties { // Since the previous key is the current key - 1. Meaning that the elements are continuos. if key == len { vec.push(value); - return None; + return false; } // If it the key points in at a already taken index, swap and return it. std::mem::swap(&mut vec[key as usize], &mut value); - return Some( - PropertyDescriptorBuilder::new() - .writable(true) - .enumerable(true) - .configurable(true) - .value(value) - .build(), - ); + return true; } vec @@ -143,37 +140,32 @@ impl IndexedProperties { // Slow path: converting to sparse storage. let mut map = Self::convert_dense_to_sparse(vec); - let old_property = map.insert(key, property); + let replaced = map.insert(key, property).is_some(); *self = Self::Sparse(Box::new(map)); - old_property + replaced } /// Inserts a property descriptor with the specified key. - fn remove(&mut self, key: u32) -> Option { + fn remove(&mut self, key: u32) -> bool { let vec = match self { - Self::Sparse(map) => return map.remove(&key), + Self::Sparse(map) => { + return map.remove(&key).is_some(); + } Self::Dense(vec) => { // Fast Path: contiguous storage. // Has no elements or out of range, nothing to delete! if vec.is_empty() || key as usize >= vec.len() { - return None; + return false; } - // If the key is pointing at the last element, then we pop it and return it. + // If the key is pointing at the last element, then we pop it. // - // It does not make the storage sparse + // It does not make the storage sparse. if key as usize == vec.len().wrapping_sub(1) { - let value = vec.pop().expect("Already checked if it is out of bounds"); - return Some( - PropertyDescriptorBuilder::new() - .writable(true) - .enumerable(true) - .configurable(true) - .value(value) - .build(), - ); + vec.pop().expect("Already checked if it is out of bounds"); + return true; } vec @@ -182,10 +174,10 @@ impl IndexedProperties { // Slow Path: conversion to sparse storage. let mut map = Self::convert_dense_to_sparse(vec); - let old_property = map.remove(&key); + let removed = map.remove(&key).is_some(); *self = Self::Sparse(Box::new(map)); - old_property + removed } /// Check if we contain the key to a property descriptor. @@ -226,55 +218,175 @@ pub struct PropertyMap { /// Properties stored with integers as keys. indexed_properties: IndexedProperties, - /// Properties stored with `String`s a keys. - string_properties: OrderedHashMap, - - /// Properties stored with `Symbol`s a keys. - symbol_properties: OrderedHashMap, + pub(crate) shape: Shape, + pub(crate) storage: ObjectStorage, } impl PropertyMap { /// Create a new [`PropertyMap`]. #[must_use] #[inline] - pub fn new() -> Self { - Self::default() + pub fn new(shape: Shape) -> Self { + Self { + indexed_properties: IndexedProperties::default(), + shape, + storage: Vec::default(), + } + } + + /// TOOD: doc + #[must_use] + #[inline] + pub fn from_prototype_unique_shape(prototype: JsPrototype) -> Self { + Self { + indexed_properties: IndexedProperties::default(), + shape: Shape::unique(UniqueShape::new(prototype, PropertyTableInner::default())), + storage: Vec::default(), + } + } + + /// TOOD: doc + #[must_use] + #[inline] + pub fn from_prototype_with_shared_shape(mut shape: Shape, prototype: JsPrototype) -> Self { + shape = shape.change_prototype_transition(prototype); + Self { + indexed_properties: IndexedProperties::default(), + shape, + storage: Vec::default(), + } } /// Get the property with the given key from the [`PropertyMap`]. #[must_use] pub fn get(&self, key: &PropertyKey) -> Option { - match key { - PropertyKey::Index(index) => self.indexed_properties.get(*index), - PropertyKey::String(string) => self.string_properties.0.get(string).cloned(), - PropertyKey::Symbol(symbol) => self.symbol_properties.0.get(symbol).cloned(), + if let PropertyKey::Index(index) = key { + return self.indexed_properties.get(*index); + } + if let Some(slot) = self.shape.lookup(key) { + return Some(self.get_storage(slot)); } + + None + } + + /// Get the property with the given key from the [`PropertyMap`]. + #[must_use] + pub fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor { + let index = index as usize; + let mut builder = PropertyDescriptor::builder() + .configurable(attributes.contains(SlotAttribute::CONFIGURABLE)) + .enumerable(attributes.contains(SlotAttribute::ENUMERABLE)); + if attributes.is_accessor_descriptor() { + if attributes.has_get() { + builder = builder.get(self.storage[index].clone()); + } + if attributes.has_set() { + builder = builder.set(self.storage[index + 1].clone()); + } + } else { + builder = builder.writable(attributes.contains(SlotAttribute::WRITABLE)); + builder = builder.value(self.storage[index].clone()); + } + builder.build() } /// Insert the given property descriptor with the given key [`PropertyMap`]. - pub fn insert( - &mut self, - key: &PropertyKey, - property: PropertyDescriptor, - ) -> Option { - match &key { - PropertyKey::Index(index) => self.indexed_properties.insert(*index, property), - PropertyKey::String(string) => { - self.string_properties.0.insert(string.clone(), property) + pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool { + if let PropertyKey::Index(index) = key { + return self.indexed_properties.insert(*index, property); + } + + let attributes = property.to_slot_attributes(); + + if let Some(slot) = self.shape.lookup(key) { + let index = slot.index as usize; + + if slot.attributes != attributes { + let key = TransitionKey { + property_key: key.clone(), + attributes, + }; + let transition = self.shape.change_attributes_transition(key); + self.shape = transition.shape; + match transition.action { + ChangeTransitionAction::Nothing => {} + ChangeTransitionAction::Remove => { + self.storage.remove(slot.index as usize + 1); + } + ChangeTransitionAction::Insert => { + // insert after index which is (index + 1). + self.storage.insert(index, JsValue::undefined()); + } + } } - PropertyKey::Symbol(symbol) => { - self.symbol_properties.0.insert(symbol.clone(), property) + + if attributes.is_accessor_descriptor() { + if attributes.has_get() { + self.storage[index] = property + .get() + .cloned() + .map(JsValue::new) + .unwrap_or_default(); + } + if attributes.has_set() { + self.storage[index + 1] = property + .set() + .cloned() + .map(JsValue::new) + .unwrap_or_default(); + } + } else { + self.storage[index] = property.expect_value().clone(); } + return true; + } + + let transition_key = TransitionKey { + property_key: key.clone(), + attributes, + }; + self.shape = self.shape.insert_property_transition(transition_key); + if attributes.is_accessor_descriptor() { + self.storage.push( + property + .get() + .cloned() + .map(JsValue::new) + .unwrap_or_default(), + ); + self.storage.push( + property + .set() + .cloned() + .map(JsValue::new) + .unwrap_or_default(), + ); + } else { + self.storage + .push(property.value().cloned().unwrap_or_default()); } + + false } /// Remove the property with the given key from the [`PropertyMap`]. - pub fn remove(&mut self, key: &PropertyKey) -> Option { - match key { - PropertyKey::Index(index) => self.indexed_properties.remove(*index), - PropertyKey::String(string) => self.string_properties.0.shift_remove(string), - PropertyKey::Symbol(symbol) => self.symbol_properties.0.shift_remove(symbol), + pub fn remove(&mut self, key: &PropertyKey) -> bool { + if let PropertyKey::Index(index) = key { + return self.indexed_properties.remove(*index); } + if let Some(slot) = self.shape.lookup(key) { + // shift all elements when removing. + if slot.attributes.is_accessor_descriptor() { + self.storage.remove(slot.index as usize + 1); + } + self.storage.remove(slot.index as usize); + + self.shape = self.shape.remove_property_transition(key); + return true; + } + + false } /// Overrides all the indexed properties, setting it to dense storage. @@ -291,65 +403,6 @@ impl PropertyMap { } } - /// An iterator visiting all key-value pairs in arbitrary order. The iterator element type is `(PropertyKey, &'a Property)`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn iter(&self) -> Iter<'_> { - Iter { - indexed_properties: self.indexed_properties.iter(), - string_properties: self.string_properties.0.iter(), - symbol_properties: self.symbol_properties.0.iter(), - } - } - - /// An iterator visiting all keys in arbitrary order. The iterator element type is `PropertyKey`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn keys(&self) -> Keys<'_> { - Keys(self.iter()) - } - - /// An iterator visiting all values in arbitrary order. The iterator element type is `&'a Property`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn values(&self) -> Values<'_> { - Values(self.iter()) - } - - /// An iterator visiting all symbol key-value pairs in arbitrary order. The iterator element type is `(&'a RcSymbol, &'a Property)`. - /// - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn symbol_properties(&self) -> SymbolProperties<'_> { - SymbolProperties(self.symbol_properties.0.iter()) - } - - /// An iterator visiting all symbol keys in arbitrary order. The iterator element type is `&'a RcSymbol`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn symbol_property_keys(&self) -> SymbolPropertyKeys<'_> { - SymbolPropertyKeys(self.symbol_properties.0.keys()) - } - - /// An iterator visiting all symbol values in arbitrary order. The iterator element type is `&'a Property`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn symbol_property_values(&self) -> SymbolPropertyValues<'_> { - SymbolPropertyValues(self.symbol_properties.0.values()) - } - /// An iterator visiting all indexed key-value pairs in arbitrary order. The iterator element type is `(&'a u32, &'a Property)`. /// /// This iterator does not recurse down the prototype chain. @@ -377,50 +430,18 @@ impl PropertyMap { self.indexed_properties.values() } - /// An iterator visiting all string key-value pairs in arbitrary order. The iterator element type is `(&'a RcString, &'a Property)`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn string_properties(&self) -> StringProperties<'_> { - StringProperties(self.string_properties.0.iter()) - } - - /// An iterator visiting all string keys in arbitrary order. The iterator element type is `&'a RcString`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn string_property_keys(&self) -> StringPropertyKeys<'_> { - StringPropertyKeys(self.string_properties.0.keys()) - } - - /// An iterator visiting all string values in arbitrary order. The iterator element type is `&'a Property`. - /// - /// This iterator does not recurse down the prototype chain. - #[inline] - #[must_use] - pub fn string_property_values(&self) -> StringPropertyValues<'_> { - StringPropertyValues(self.string_properties.0.values()) - } - /// Returns `true` if the given key is contained in the [`PropertyMap`]. #[inline] #[must_use] pub fn contains_key(&self, key: &PropertyKey) -> bool { - match key { - PropertyKey::Index(index) => self.indexed_properties.contains_key(*index), - PropertyKey::String(string) => self.string_properties.0.contains_key(string), - PropertyKey::Symbol(symbol) => self.symbol_properties.0.contains_key(symbol), + if let PropertyKey::Index(index) = key { + return self.indexed_properties.contains_key(*index); + } + if self.shape.lookup(key).is_some() { + return true; } - } - - pub(crate) const fn string_property_map(&self) -> &GlobalPropertyMap { - &self.string_properties.0 - } - pub(crate) fn string_property_map_mut(&mut self) -> &mut GlobalPropertyMap { - &mut self.string_properties.0 + false } } diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs new file mode 100644 index 00000000000..bd2e4449afd --- /dev/null +++ b/boa_engine/src/object/shape/mod.rs @@ -0,0 +1,188 @@ +pub(crate) mod property_table; +pub(crate) mod shared_shape; +pub(crate) mod slot; +mod unique_shape; + +pub(crate) use unique_shape::UniqueShape; + +use std::fmt::Debug; + +use boa_gc::{Finalize, Trace}; + +use crate::property::PropertyKey; + +use self::{ + shared_shape::{SharedShape, TransitionKey}, + slot::Slot, +}; + +use super::JsPrototype; + +/// Action to be performed after a property attribute change +// +// Example: of { get/set x() { ... }, y: ... } into { x: ..., y: ... } +// +// 0 1 2 +// Storage: | get x | set x | y | +// +// We delete at position of x which is index 0 (it spans two elements) + 1: +// +// 0 1 +// Storage: | x | y | +pub(crate) enum ChangeTransitionAction { + /// Do nothing to storage. + Nothing, + + /// Remove element at (index + 1) from storage. + Remove, + + /// Insert element at (index + 1) into storage. + Insert, +} + +/// The result of a change property attribute transition. +pub(crate) struct ChangeTransition { + /// The shape after transition. + pub(crate) shape: T, + + /// The needed action to be performed after transition to the object storage. + pub(crate) action: ChangeTransitionAction, +} + +/// The internal representation of [`Shape`]. +#[derive(Debug, Trace, Finalize, Clone)] +enum Inner { + Unique(UniqueShape), + Shared(SharedShape), +} + +#[derive(Debug, Trace, Finalize, Clone)] +pub struct Shape { + inner: Inner, +} + +/// The default [`Shape`] type is [`UniqueShape`]. +impl Default for Shape { + fn default() -> Self { + Shape::unique(UniqueShape::default()) + } +} + +impl Shape { + /// The max transition count of a [`SharedShape`] from the root node, + /// before the shape will be converted into a [`UniqueShape`] + /// + /// NOTE: This only applies to [`SharedShape`]. + const TRANSITION_COUNT_MAX: u16 = 1024; + + /// Create a [`Shape`] from a [`SharedShape`]. + pub(crate) fn shared(inner: SharedShape) -> Self { + Self { + inner: Inner::Shared(inner), + } + } + + /// Create a [`Shape`] from a [`UniqueShape`]. + pub(crate) const fn unique(shape: UniqueShape) -> Self { + Self { + inner: Inner::Unique(shape), + } + } + + /// Create an insert property transitions returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there is no property with the given key! + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + match &self.inner { + Inner::Shared(shape) => { + let shape = shape.insert_property_transition(key); + if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { + return Self::unique(shape.to_unique()); + } + Self::shared(shape) + } + Inner::Unique(shape) => Self::unique(shape.insert_property_transition(key)), + } + } + + /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`] + /// and actions to be performed + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn change_attributes_transition( + &self, + key: TransitionKey, + ) -> ChangeTransition { + match &self.inner { + Inner::Shared(shape) => { + let change_transition = shape.change_attributes_transition(key); + let shape = + if change_transition.shape.transition_count() >= Self::TRANSITION_COUNT_MAX { + Self::unique(change_transition.shape.to_unique()) + } else { + Self::shared(change_transition.shape) + }; + ChangeTransition { + shape, + action: change_transition.action, + } + } + Inner::Unique(shape) => shape.change_attributes_transition(&key), + } + } + + /// Remove a property property from the [`Shape`] returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + match &self.inner { + Inner::Shared(shape) => { + let shape = shape.remove_property_transition(key); + if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { + return Self::unique(shape.to_unique()); + } + Self::shared(shape) + } + Inner::Unique(shape) => Self::unique(shape.remove_property_transition(key)), + } + } + + /// Create a prototype transitions returning the new transitioned [`Shape`]. + /// + /// NOTE: This assumes that there already is a property with the given key! + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + match &self.inner { + Inner::Shared(shape) => { + let shape = shape.change_prototype_transition(prototype); + if shape.transition_count() >= Self::TRANSITION_COUNT_MAX { + return Self::unique(shape.to_unique()); + } + Self::shared(shape) + } + Inner::Unique(shape) => Self::unique(shape.change_prototype_transition(prototype)), + } + } + + /// Get the property of the [`Shape`]. + pub(crate) fn prototype(&self) -> JsPrototype { + match &self.inner { + Inner::Shared(shape) => shape.prototype(), + Inner::Unique(shape) => shape.prototype(), + } + } + + #[inline] + pub fn lookup(&self, key: &PropertyKey) -> Option { + match &self.inner { + Inner::Shared(shape) => shape.lookup(key), + Inner::Unique(shape) => shape.lookup(key), + } + } + + pub(crate) fn keys(&self) -> Vec { + match &self.inner { + Inner::Shared(shape) => shape.keys(), + Inner::Unique(shape) => shape.keys(), + } + } +} diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs new file mode 100644 index 00000000000..ae27d70d5b9 --- /dev/null +++ b/boa_engine/src/object/shape/property_table.rs @@ -0,0 +1,144 @@ +use std::{cell::RefCell, rc::Rc}; + +use rustc_hash::FxHashMap; + +use crate::{ + object::shape::slot::{Slot, SlotAttribute}, + property::PropertyKey, +}; + +/// The internal representation of [`PropertyTable`]. +#[derive(Default, Debug, Clone)] +pub(crate) struct PropertyTableInner { + pub(crate) map: FxHashMap, + pub(crate) keys: Vec<(PropertyKey, Slot)>, +} + +impl PropertyTableInner { + pub(crate) fn keys(&self) -> Vec { + self.keys_count(self.keys.len() as u32) + } + + pub(crate) fn keys_count(&self, count: u32) -> Vec { + let count = count as usize; + + self.keys + .iter() + .take(count) + .map(|(key, _)| key) + .filter(|key| matches!(key, PropertyKey::String(_))) + .chain( + self.keys + .iter() + .take(count) + .map(|(key, _)| key) + .filter(|key| matches!(key, PropertyKey::Symbol(_))), + ) + .cloned() + .collect() + } + + pub(crate) fn clone_count(&self, count: u32) -> Self { + let count = count as usize; + + let mut keys = Vec::with_capacity(count); + let mut map = FxHashMap::default(); + + for (key, slot) in self.keys.iter().take(count) { + let index = keys.len() as u32; + keys.push((key.clone(), *slot)); + map.insert(key.clone(), (index, *slot)); + } + + Self { map, keys } + } + + pub(crate) fn insert(&mut self, key: PropertyKey, attributes: SlotAttribute) { + let slot = Slot::from_previous(self.keys.last().map(|x| x.1), attributes); + let index = self.keys.len() as u32; + self.keys.push((key.clone(), slot)); + let value = self.map.insert(key, (index, slot)); + debug_assert!(value.is_none()); + } +} + +/// Represents an ordered property table, that maps [`PropertyTable`] to [`Slot`]. +/// +/// This is shared between [`crate::object::shape::SharedShape`]. +#[derive(Default, Debug, Clone)] +pub(crate) struct PropertyTable { + pub(super) inner: Rc>, +} + +impl PropertyTable { + pub(super) fn inner(&self) -> &RefCell { + &self.inner + } + + /// Add a property to the [`PropertyTable`] or deep clone it, + /// if there already is property or the property [`SlotAttributes`] are not the same. + pub(crate) fn add_property_deep_clone_if_needed( + &self, + key: PropertyKey, + attributes: SlotAttribute, + property_count: u32, + ) -> Self { + // TODO: possible optimization if we are the only ones holding a reference to self we can add the property directly. + // TOOD: possible optimization if we already have the property in the exact position we want it to be with the same properties, + // then just return self, this might happen, if a branch is created than after not being used gets gc collected we still have the + // properties here. + // TODO: possible optimization, figure out if there **always** are as many properties as there are strong references. If so truncate + // on drop Rc ref drop, maybe? + { + let mut inner = self.inner.borrow_mut(); + if (property_count as usize) == inner.keys.len() && !inner.map.contains_key(&key) { + inner.insert(key, attributes); + return self.clone(); + } + } + + // property is already present need to make deep clone of property table. + let this = self.deep_clone(property_count); + { + let mut inner = this.inner.borrow_mut(); + inner.insert(key, attributes); + } + this + } + + /// Deep clone the secified amount from the [`PropertyTable`] in insertion order. + pub(crate) fn deep_clone(&self, count: u32) -> Self { + Self { + inner: Rc::new(RefCell::new(self.inner.borrow().clone_count(count))), + } + } + + /// Deep clone the [`PropertyTable`]. + pub(crate) fn deep_clone_all(&self) -> Self { + Self { + inner: Rc::new(RefCell::new((*self.inner.borrow()).clone())), + } + } + + /// Change the attribue of a property. + pub(crate) fn set_attributes_at_index(&self, index: usize, property_attributes: SlotAttribute) { + let mut inner = self.inner.borrow_mut(); + let Some((_key, slot)) = inner.keys.get_mut(index) else { + unreachable!("There should already be a property!") + }; + slot.attributes = property_attributes; + } + + /// Get a property from the [`PropertyTable`]. + /// + /// Panics: + /// + /// If it is not in the [`PropertyTable`]. + pub(crate) fn get_expect(&self, key: &PropertyKey) -> (usize, Slot) { + let inner = self.inner.borrow(); + let Some((index, slot)) = inner.map.get(key) else { + unreachable!("There should already be a property!") + }; + (*index as usize, *slot) + } +} diff --git a/boa_engine/src/object/shape/shared_shape/forward_transition.rs b/boa_engine/src/object/shape/shared_shape/forward_transition.rs new file mode 100644 index 00000000000..ba9c9488c83 --- /dev/null +++ b/boa_engine/src/object/shape/shared_shape/forward_transition.rs @@ -0,0 +1,58 @@ +use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc}; +use rustc_hash::FxHashMap; + +use crate::object::JsPrototype; + +use super::{Inner as SharedShapeInner, TransitionKey}; + +/// Maps transition key type to a [`SharedShapeInner`] transition. +type TransitionMap = FxHashMap>; + +/// The internal representation of [`ForwardTransition`]. +#[derive(Default, Debug, Trace, Finalize)] +struct Inner { + properties: Option>>, + prototypes: Option>>, +} + +/// Holds a forward reference to a previously created transition. +/// +/// The reference is weak, therefore it can be garbage collected if it is not in use. +#[derive(Default, Debug, Trace, Finalize)] +pub(super) struct ForwardTransition { + inner: GcRefCell, +} + +impl ForwardTransition { + /// Insert a property transition. + pub(super) fn insert_property(&self, key: TransitionKey, value: &Gc) { + let mut this = self.inner.borrow_mut(); + let properties = this.properties.get_or_insert_with(Box::default); + properties.insert(key, WeakGc::new(value)); + } + + /// Insert a prototype transition. + pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc) { + let mut this = self.inner.borrow_mut(); + let prototypes = this.prototypes.get_or_insert_with(Box::default); + prototypes.insert(key, WeakGc::new(value)); + } + + /// Get a property transition, return [`None`] otherwise. + pub(super) fn get_property(&self, key: &TransitionKey) -> Option> { + let this = self.inner.borrow(); + let Some(transitions) = this.properties.as_ref() else { + return None; + }; + transitions.get(key).cloned() + } + + /// Get a prototype transition, return [`None`] otherwise. + pub(super) fn get_prototype(&self, key: &JsPrototype) -> Option> { + let this = self.inner.borrow(); + let Some(transitions) = this.prototypes.as_ref() else { + return None; + }; + transitions.get(key).cloned() + } +} diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs new file mode 100644 index 00000000000..0c5fc1a146a --- /dev/null +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -0,0 +1,423 @@ +mod forward_transition; +pub(crate) mod template; + +use std::{collections::hash_map::RandomState, hash::Hash}; + +use boa_gc::{empty_trace, Finalize, Gc, Trace}; +use indexmap::IndexMap; + +use crate::{object::JsPrototype, property::PropertyKey}; + +use self::forward_transition::ForwardTransition; + +use super::{ + property_table::PropertyTable, slot::SlotAttribute, ChangeTransition, ChangeTransitionAction, + Slot, UniqueShape, +}; + +/// +#[derive(Debug, Finalize, Clone, PartialEq, Eq, Hash)] +pub(crate) struct TransitionKey { + pub(crate) property_key: PropertyKey, + pub(crate) attributes: SlotAttribute, +} + +// SAFETY: Non of the member of this struct are garbage collected, +// so this should be fine. +unsafe impl Trace for TransitionKey { + empty_trace!(); +} + +/// Represents the transition type of a [`SharedShape`]. +#[derive(Debug, Finalize, Clone, Copy, PartialEq, Eq)] +pub(crate) enum TransitionType { + /// Inserts a new property. + Insert, + + /// Change existing property attributes. + Configure, + + /// Change prototype. + Prototype, +} + +// SAFETY: This is safe because nothing needs tracing. +unsafe impl Trace for TransitionType { + empty_trace!(); +} + +/// The internal representation of a [`SharedShape`]. +#[derive(Debug, Trace, Finalize)] +struct Inner { + /// See [`ForwardTransition`]. + forward_transitions: ForwardTransition, + + /// The count of how many properties this [`SharedShape`] holds. + property_count: u32, + + /// Instance prototype `__proto__`. + prototype: JsPrototype, + + // SAFETY: This is safe because nothing in [`PropertyTable`] + // needs tracing + #[unsafe_ignore_trace] + property_table: PropertyTable, + + /// The previous shape in the transition chain. + /// + /// [`None`] if it is the root shape. + previous: Option, + + /// The transition type this [`SharedShape`] is. + transition_type: TransitionType, + + /// How many transitions have happened from the root node. + transition_count: u16, +} + +/// +#[derive(Debug, Trace, Finalize, Clone)] +pub(crate) struct SharedShape { + inner: Gc, +} + +impl SharedShape { + fn property_table(&self) -> &PropertyTable { + &self.inner.property_table + } + /// Return the property count that this shape owns in the [`PropertyTable`]. + fn property_count(&self) -> u32 { + self.inner.property_count + } + /// Return the index to the property in the the [`PropertyTable`]. + fn property_index(&self) -> u32 { + self.inner.property_count.saturating_sub(1) + } + /// Getter for the transition count field. + pub(crate) fn transition_count(&self) -> u16 { + self.inner.transition_count + } + /// Getter for the previous field. + pub(crate) fn previous(&self) -> Option<&SharedShape> { + self.inner.previous.as_ref() + } + /// Get the prototype of the shape. + pub(crate) fn prototype(&self) -> JsPrototype { + self.inner.prototype.clone() + } + /// Get the property this [`SharedShape`] referes to. + pub(crate) fn property(&self) -> (PropertyKey, Slot) { + let inner = self.property_table().inner().borrow(); + let (key, slot) = inner + .keys + .get(self.property_index() as usize) + .expect("There should be a property"); + (key.clone(), *slot) + } + /// Get the transition type of the [`SharedShape`]. + fn transition_type(&self) -> TransitionType { + self.inner.transition_type + } + + /// Getter for the [`ForwardTransition`] field. + fn forward_transitions(&self) -> &ForwardTransition { + &self.inner.forward_transitions + } + + /// Create a new [`SharedShape`]. + fn new(inner: Inner) -> Self { + Self { + inner: Gc::new(inner), + } + } + + /// Create a root [`SharedShape`]. + pub(crate) fn root() -> Self { + Self::new(Inner { + forward_transitions: ForwardTransition::default(), + prototype: None, + property_count: 0, + property_table: PropertyTable::default(), + previous: None, + transition_type: TransitionType::Insert, + transition_count: 0, + }) + } + + /// Create a [`SharedShape`] change prototype transition. + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + if let Some(shape) = self.forward_transitions().get_prototype(&prototype) { + if let Some(inner) = shape.upgrade() { + return Self { inner }; + } + } + let new_inner_shape = Inner { + forward_transitions: ForwardTransition::default(), + prototype: prototype.clone(), + property_table: self.property_table().clone(), + property_count: self.property_count(), + previous: Some(self.clone()), + transition_type: TransitionType::Prototype, + transition_count: self.transition_count() + 1, + }; + let new_shape = Self::new(new_inner_shape); + + self.forward_transitions() + .insert_prototype(prototype, &new_shape.inner); + + new_shape + } + + /// Create a [`SharedShape`] insert property transition. + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + // Check if we have already created such a transition, if so use it! + if let Some(shape) = self.forward_transitions().get_property(&key) { + if let Some(inner) = shape.upgrade() { + return Self { inner }; + } + } + + let property_table = self.property_table().add_property_deep_clone_if_needed( + key.property_key.clone(), + key.attributes, + self.property_count(), + ); + let new_inner_shape = Inner { + prototype: self.prototype(), + forward_transitions: ForwardTransition::default(), + property_table, + property_count: self.property_count() + 1, + previous: Some(self.clone()), + transition_type: TransitionType::Insert, + transition_count: self.transition_count() + 1, + }; + let new_shape = Self::new(new_inner_shape); + + self.forward_transitions() + .insert_property(key, &new_shape.inner); + + new_shape + } + + /// Create a [`SharedShape`] change prototype transition, returning [`ChangeTransition`]. + pub(crate) fn change_attributes_transition( + &self, + key: TransitionKey, + ) -> ChangeTransition { + let (index, slot) = self.property_table().get_expect(&key.property_key); + + // Check if we have already created such a transition, if so use it! + if let Some(shape) = self.forward_transitions().get_property(&key) { + if let Some(inner) = shape.upgrade() { + let action = if slot.attributes.width_match(key.attributes) { + ChangeTransitionAction::Nothing + } else if slot.attributes.is_accessor_descriptor() { + // Accessor property --> Data property + ChangeTransitionAction::Remove + } else { + // Data property --> Accessor property + ChangeTransitionAction::Insert + }; + + return ChangeTransition { + shape: Self { inner }, + action, + }; + } + } + + // The attribute change transitions, didn't change from accessor to data property or vice-versa. + if slot.attributes.width_match(key.attributes) { + let property_table = self.property_table().deep_clone_all(); + property_table.set_attributes_at_index(index, key.attributes); + let inner_shape = Inner { + forward_transitions: ForwardTransition::default(), + prototype: self.prototype(), + property_table, + property_count: self.property_count(), + previous: Some(self.clone()), + transition_type: TransitionType::Configure, + transition_count: self.transition_count() + 1, + }; + let shape = Self::new(inner_shape); + + self.forward_transitions() + .insert_property(key, &shape.inner); + + return ChangeTransition { + shape, + action: ChangeTransitionAction::Nothing, + }; + } + + // Rollback before the property has added. + let (mut base, prototype, transitions) = self.rollback_before(&key.property_key); + + // Apply prototype transition, if it was found. + if let Some(prototype) = prototype { + base = base.change_prototype_transition(prototype); + } + + // Apply this property. + base = base.insert_property_transition(key); + + // Apply previous properties. + for (property_key, attributes) in transitions.into_iter().rev() { + let transition = TransitionKey { + property_key, + attributes, + }; + base = base.insert_property_transition(transition); + } + + // Determine action to be performed on the storage. + let action = if slot.attributes.is_accessor_descriptor() { + // Accessor property --> Data property + ChangeTransitionAction::Remove + } else { + // Data property --> Accessor property + ChangeTransitionAction::Insert + }; + + ChangeTransition { + shape: base, + action, + } + } + + /// Rollback to shape before the insertion of the [`PropertyKey`] that is provided. + /// + /// This returns the shape before the insertion, if it sees a prototype transition it will return the lastest one, + /// ignoring any others, [`None`] otherwise. It also will return the property transitions ordered from + /// latest to oldest that it sees. + /// + /// NOTE: In the transitions it does not include the property that we are rolling back. + /// + /// NOTE: The prototype transitions if it sees a property insert and then later an attribute change it will condense + /// into one property insert transition with the new attribute in the change attribute transition, + /// in the same place that the property was inserted initially. + // + // For example with the following chain: + // + // INSERT(x) INSERT(y) INSERT(z) + // { } ------------> { x } ------------> { x, y } ------------> { x, y, z } + // + // Then we call rollback on `y`: + // + // INSERT(x) INSERT(y) INSERT(z) + // { } ------------> { x } ------------> { x, y } ------------> { x, y, z } + // ^ + // \--- base (with array of transitions to be performed: INSERT(z), + // and protortype: None ) + fn rollback_before( + &self, + key: &PropertyKey, + ) -> ( + SharedShape, + Option, + IndexMap, + ) { + let mut prototype = None; + let mut transitions: IndexMap = + IndexMap::default(); + + let mut current = Some(self); + let base = loop { + let Some(current_shape) = current else { + unreachable!("The chain should have insert transition type!") + }; + + // We only take the latest prototype change it, if it exists. + if current_shape.transition_type() == TransitionType::Prototype { + if prototype.is_none() { + prototype = Some(current_shape.prototype().clone()); + } + + // Skip when it is a prototype transition. + current = current_shape.previous(); + continue; + } + + let (current_property_key, slot) = current_shape.property(); + + if current_shape.transition_type() == TransitionType::Insert + && ¤t_property_key == key + { + let base = if let Some(base) = current_shape.previous() { + base.clone() + } else { + // It's the root, because it doesn't have previous. + current_shape.clone() + }; + break base; + } + + // Do not add property that we are trying to delete. + // this can happen if a configure was called after inserting it into the shape + if ¤t_property_key != key { + // Only take the latest changes to a property. To try to build a smaller tree. + transitions + .entry(current_property_key) + .or_insert(slot.attributes); + } + + current = current_shape.previous(); + }; + + (base, prototype, transitions) + } + + /// Remove a property from [`SharedShape`], returning the new [`SharedShape`]. + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + let (mut base, prototype, transitions) = self.rollback_before(key); + + // Apply prototype transition, if it was found. + if let Some(prototype) = prototype { + base = base.change_prototype_transition(prototype); + } + + for (property_key, attributes) in transitions.into_iter().rev() { + let transition = TransitionKey { + property_key, + attributes, + }; + base = base.insert_property_transition(transition); + } + + base + } + + /// Do a property lookup, returns [`None`] if property not found. + pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { + let property_count = self.property_count(); + if property_count == 0 { + return None; + } + + let property_table_inner = self.property_table().inner().borrow(); + if let Some((property_table_index, slot)) = property_table_inner.map.get(key) { + // Check if we are trying to access properties that belong to another shape. + if *property_table_index < self.property_count() { + return Some(*slot); + } + } + None + } + + /// Gets all keys first strings then symbols in creation order. + pub(crate) fn keys(&self) -> Vec { + let property_table = self.property_table().inner().borrow(); + property_table.keys_count(self.property_count()) + } + + /// Returns a new [`UniqueShape`] with the properties of the [`SharedShape`]. + pub(crate) fn to_unique(&self) -> UniqueShape { + UniqueShape::new( + self.prototype(), + self.property_table() + .inner() + .borrow() + .clone_count(self.property_count()), + ) + } +} diff --git a/boa_engine/src/object/shape/shared_shape/template.rs b/boa_engine/src/object/shape/shared_shape/template.rs new file mode 100644 index 00000000000..1d0128db8cd --- /dev/null +++ b/boa_engine/src/object/shape/shared_shape/template.rs @@ -0,0 +1,135 @@ +use boa_gc::{Gc, GcRefCell}; +use thin_vec::ThinVec; + +use crate::{ + object::{ + shape::{slot::SlotAttribute, Shape}, + JsObject, JsPrototype, Object, ObjectData, PropertyMap, + }, + property::{Attribute, PropertyKey}, + JsValue, +}; + +use super::{SharedShape, TransitionKey}; + +#[derive(Debug, Clone)] +pub(crate) struct ObjectTemplate { + shape: SharedShape, + storage_len: usize, +} + +impl ObjectTemplate { + pub(crate) fn new(root_shape: &SharedShape, prototype: JsObject) -> Self { + let shape = root_shape.change_prototype_transition(Some(prototype)); + Self { + shape, + storage_len: 0, + } + } + + pub(crate) fn prototype(&self) -> JsPrototype { + self.shape.prototype() + } + + pub(crate) fn data_property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { + // TOOD: We don't support indexed keys. + debug_assert!(!matches!(&key, PropertyKey::Index(_))); + + let attributes = { + let mut result = SlotAttribute::empty(); + result.set( + SlotAttribute::WRITABLE, + attributes.contains(Attribute::WRITABLE), + ); + result.set( + SlotAttribute::CONFIGURABLE, + attributes.contains(Attribute::CONFIGURABLE), + ); + result.set( + SlotAttribute::ENUMERABLE, + attributes.contains(Attribute::ENUMERABLE), + ); + result + }; + + self.shape = self.shape.insert_property_transition(TransitionKey { + property_key: key, + attributes, + }); + self.storage_len += 1; + + self + } + + pub(crate) fn accessor_property( + &mut self, + key: PropertyKey, + get: bool, + set: bool, + attributes: Attribute, + ) -> &mut Self { + // TOOD: We don't support indexed keys. + debug_assert!(!matches!(&key, PropertyKey::Index(_))); + + let attributes = { + let mut result = SlotAttribute::empty(); + result.set( + SlotAttribute::CONFIGURABLE, + attributes.contains(Attribute::CONFIGURABLE), + ); + result.set( + SlotAttribute::ENUMERABLE, + attributes.contains(Attribute::ENUMERABLE), + ); + + result.set(SlotAttribute::GET, get); + result.set(SlotAttribute::SET, set); + + result + }; + + self.shape = self.shape.insert_property_transition(TransitionKey { + property_key: key, + attributes, + }); + self.storage_len += 2; + + self + } + + pub(crate) fn create_with_index_properties( + &self, + data: ObjectData, + storage: Vec, + indexed: ThinVec, + ) -> JsObject { + let mut object = Object { + data, + extensible: true, + properties: PropertyMap::new(Shape::shared(self.shape.clone())), + private_elements: ThinVec::new(), + }; + + debug_assert_eq!(self.storage_len, storage.len()); + + object.properties.storage = storage; + object.properties.override_indexed_properties(indexed); + + Gc::new(GcRefCell::new(object)).into() + } + + pub(crate) fn create(&self, data: ObjectData, storage: Vec) -> JsObject { + let mut object = Object { + data, + extensible: true, + properties: PropertyMap::new(Shape::shared(self.shape.clone())), + private_elements: ThinVec::new(), + }; + + debug_assert_eq!(self.storage_len, storage.len()); + + object.properties.storage = storage; + + Gc::new(GcRefCell::new(object)).into() + } +} diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs new file mode 100644 index 00000000000..cab40dd7c4a --- /dev/null +++ b/boa_engine/src/object/shape/slot.rs @@ -0,0 +1,80 @@ +use bitflags::bitflags; +pub(crate) type SlotIndex = u32; + +bitflags! { + /// Attributes of a slot. + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct SlotAttribute: u8 { + const WRITABLE = 0b0000_0001; + const ENUMERABLE = 0b0000_0010; + const CONFIGURABLE = 0b0000_0100; + const GET = 0b0000_1000; + const SET = 0b0001_0000; + } +} + +impl SlotAttribute { + pub const fn is_accessor_descriptor(self) -> bool { + self.contains(Self::GET) || self.contains(Self::SET) + } + pub const fn is_data_descriptor(self) -> bool { + !self.is_accessor_descriptor() + } + + pub const fn has_get(self) -> bool { + self.contains(Self::GET) + } + pub const fn has_set(self) -> bool { + self.contains(Self::SET) + } + + /// Check if slot type width matches, this can only happens, + /// if they are both accessors, or both data properties. + pub const fn width_match(self, other: Self) -> bool { + self.is_accessor_descriptor() == other.is_accessor_descriptor() + } + + /// Get the width of the slot. + pub fn width(self) -> u32 { + // accessor take 2 positions in the storage to accomodate for the `get` and `set` fields. + 1 + u32::from(self.is_accessor_descriptor()) + } +} + +/// Represents an [`u32`] index and a [`SlotAttribute`] field of an element in a dense storage. +/// +/// Slots can be diffent width depending on its attributes, accessors properties have width `2`, +/// while data properties have width `1`. +#[derive(Debug, Clone, Copy)] +pub struct Slot { + pub index: SlotIndex, + pub attributes: SlotAttribute, +} + +impl Slot { + /// Get the width of the slot. + pub(crate) fn width(self) -> u32 { + self.attributes.width() + } + + /// Calculate next slot from previous one. + /// + /// This is needed because slots do not have the same width. + pub(crate) fn from_previous( + previous_slot: Option, + new_attributes: SlotAttribute, + ) -> Self { + // If there was no previous slot then return 0 as the index. + let Some(previous_slot) = previous_slot else { + return Self { + index: 0, + attributes: new_attributes, + } + }; + + Self { + index: previous_slot.index + previous_slot.width(), + attributes: new_attributes, + } + } +} diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs new file mode 100644 index 00000000000..bc5efe770b0 --- /dev/null +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -0,0 +1,203 @@ +use std::{cell::RefCell, fmt::Debug}; + +use boa_gc::{Finalize, Gc, GcRefCell, Trace}; + +use crate::property::PropertyKey; + +use super::{ + property_table::PropertyTableInner, shared_shape::TransitionKey, ChangeTransition, + ChangeTransitionAction, JsPrototype, Shape, Slot, +}; + +/// The internal representation of [`UniqueShape`]. +#[derive(Default, Debug, Trace, Finalize)] +struct Inner { + /// The property table that maps a [`PropertyKey`] to a slot in the objects storage. + // + // SAFETY: This is safe becasue nothing in this field needs tracing. + #[unsafe_ignore_trace] + property_table: RefCell, + + /// The prototype of the shape. + prototype: GcRefCell, +} + +/// Represents a [`Shape`] that is not shared with any other object. +/// +/// This is useful for objects that are inherently unique like, +/// the builtin object. +/// +/// Cloning this does a shallow clone. +#[derive(Default, Debug, Clone, Trace, Finalize)] +pub(crate) struct UniqueShape { + inner: Gc, +} + +impl UniqueShape { + /// Create a new [`UniqueShape`]. + pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { + Self { + inner: Gc::new(Inner { + property_table: RefCell::new(property_table), + prototype: GcRefCell::new(prototype), + }), + } + } + + /// Get the prototype of the [`UniqueShape`]. + pub(crate) fn prototype(&self) -> JsPrototype { + self.inner.prototype.borrow().clone() + } + + /// Get the property table of the [`UniqueShape`]. + pub(crate) fn property_table(&self) -> &RefCell { + &self.inner.property_table + } + + /// Inserts a new property into the [`UniqueShape`]. + pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self { + let mut property_table = self.property_table().borrow_mut(); + property_table.insert(key.property_key, key.attributes); + self.clone() + } + + /// Remove a property from the [`UniqueShape`]. + /// + /// This will cause the current shape to be invalidated, and a new [`UniqueShape`] will be returned. + pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self { + let mut property_table = self.property_table().borrow_mut(); + let Some((index, _attributes)) = property_table.map.remove(key) else { + return self.clone(); + }; + + let index = index as usize; + + // shift elements + property_table.keys.remove(index); + + // The property that was deleted was not the last property added. + // Therefore we need to create a new unique shape, + // to invalidate any pointers to this shape i.e inline caches. + let mut property_table = std::mem::take(&mut *property_table); + + // If it is not the last property that was deleted, + // then update all the property slots that are after it. + if index != property_table.keys.len() { + // Get the previous value before the value at index, + // + // NOTE: calling wrapping_sub when usize index is 0 will wrap into usize::MAX + // which will return None, avoiding unneeded checks. + let mut previous_slot = property_table.keys.get(index.wrapping_sub(1)).map(|x| x.1); + + // Update all slot positions + for (index, (key, slot)) in property_table.keys.iter_mut().enumerate().skip(index) { + *slot = Slot::from_previous(previous_slot, slot.attributes); + + let Some((map_index, map_slot)) = property_table.map.get_mut(key) else { + unreachable!("There should already be a property") + }; + *map_index = index as u32; + *map_slot = *slot; + + previous_slot = Some(*slot); + } + } + + let prototype = self.inner.prototype.borrow_mut().take(); + Self::new(prototype, property_table) + } + + /// Does a property lookup on the [`UniqueShape`] returning the [`Slot`] where it's + /// located or [`None`] otherwise. + pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { + // println!("Unique: lookup {key}"); + let property_table = self.property_table().borrow(); + if let Some((_, slot)) = property_table.map.get(key) { + return Some(*slot); + } + + None + } + + /// Change the attributes of a property from the [`UniqueShape`]. + /// + /// This will cause the current shape to be invalidated, and a new [`UniqueShape`] will be returned. + /// + /// NOTE: This assumes that the property had already been inserted. + pub(crate) fn change_attributes_transition( + &self, + key: &TransitionKey, + ) -> ChangeTransition { + let mut property_table = self.property_table().borrow_mut(); + let Some((index, slot)) = property_table.map.get_mut(&key.property_key) else { + unreachable!("Attribute change can only happen on existing property") + }; + + let index = *index as usize; + + // If property does not change type, there is no need to shift. + if slot.attributes.width_match(key.attributes) { + slot.attributes = key.attributes; + // TODO: invalidate the pointer. + return ChangeTransition { + shape: Shape::unique(self.clone()), + action: ChangeTransitionAction::Nothing, + }; + } + + slot.attributes = key.attributes; + + let action = if key.attributes.is_accessor_descriptor() { + // Data --> Accessor + ChangeTransitionAction::Insert + } else { + // Accessor --> Data + ChangeTransitionAction::Remove + }; + + let mut previous_slot = *slot; + + // The property that was deleted was not the last property added. + // Therefore we need to create a new unique shape, + // to invalidate any pointers to this shape i.e inline caches. + let mut property_table = std::mem::take(&mut *property_table); + + // Update all slot positions + for (key, slot) in property_table.keys.iter_mut().skip(index) { + *slot = Slot::from_previous(Some(previous_slot), slot.attributes); + + let Some((map_index, map_slot)) = property_table.map.get_mut(key) else { + unreachable!("There should already be a property") + }; + *map_index = index as u32; + *map_slot = *slot; + + previous_slot = *slot; + } + + let prototype = self.inner.prototype.borrow_mut().take(); + let shape = Self::new(prototype, property_table); + + ChangeTransition { + shape: Shape::unique(shape), + action, + } + } + + /// Change the prototype of the [`UniqueShape`]. + /// + /// This will cause the current shape to be invalidated, and a new [`UniqueShape`] will be returned. + pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self { + let mut property_table = self.inner.property_table.borrow_mut(); + + // We need to create a new unique shape, + // to invalidate any pointers to this shape i.e inline caches. + let property_table = std::mem::take(&mut *property_table); + Self::new(prototype, property_table) + } + + /// Gets all keys first strings then symbols in creation order. + pub(crate) fn keys(&self) -> Vec { + self.property_table().borrow().keys() + } +} diff --git a/boa_engine/src/property/attribute/mod.rs b/boa_engine/src/property/attribute/mod.rs index ac816d172a6..fe67effba1f 100644 --- a/boa_engine/src/property/attribute/mod.rs +++ b/boa_engine/src/property/attribute/mod.rs @@ -15,7 +15,7 @@ bitflags! { /// - `[[Configurable]]` (`CONFIGURABLE`) - If `false`, attempts to delete the property, /// change the property to be an `accessor property`, or change its attributes (other than `[[Value]]`, /// or changing `[[Writable]]` to `false`) will fail. - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Attribute: u8 { /// The `Writable` attribute decides whether the value associated with the property can be changed or not, from its initial value. const WRITABLE = 0b0000_0001; diff --git a/boa_engine/src/property/mod.rs b/boa_engine/src/property/mod.rs index fd639b6857b..83b5b6ac813 100644 --- a/boa_engine/src/property/mod.rs +++ b/boa_engine/src/property/mod.rs @@ -17,7 +17,7 @@ mod attribute; -use crate::{js_string, JsString, JsSymbol, JsValue}; +use crate::{js_string, object::shape::slot::SlotAttribute, JsString, JsSymbol, JsValue}; use boa_gc::{Finalize, Trace}; use std::fmt; @@ -338,6 +338,19 @@ impl PropertyDescriptor { self.configurable = Some(configurable); } } + + pub(crate) fn to_slot_attributes(&self) -> SlotAttribute { + let mut attributes = SlotAttribute::empty(); + attributes.set(SlotAttribute::CONFIGURABLE, self.expect_configurable()); + attributes.set(SlotAttribute::ENUMERABLE, self.expect_enumerable()); + if self.is_data_descriptor() { + attributes.set(SlotAttribute::WRITABLE, self.expect_writable()); + } else { + attributes.set(SlotAttribute::GET, self.get().is_some()); + attributes.set(SlotAttribute::SET, self.set().is_some()); + } + attributes + } } /// A builder for [`PropertyDescriptor`]. @@ -557,7 +570,7 @@ impl From for PropertyDescriptor { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ispropertykey -#[derive(PartialEq, Debug, Clone, Eq, Hash)] +#[derive(Finalize, PartialEq, Debug, Clone, Eq, Hash)] pub enum PropertyKey { /// A string property key. String(JsString), diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index e76c323cbf9..d24cdee8ef6 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -9,7 +9,12 @@ use crate::{ context::{intrinsics::Intrinsics, HostHooks}, environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack}, - object::{GlobalPropertyMap, JsObject, PropertyMap}, + object::{ + shape::shared_shape::{template::ObjectTemplate, SharedShape}, + JsObject, PropertyMap, CONSTRUCTOR, PROTOTYPE, + }, + property::{Attribute, PropertyKey}, + JsSymbol, }; use boa_gc::{Gc, GcRefCell}; use boa_profiler::Profiler; @@ -25,6 +30,25 @@ pub struct Realm { pub(crate) compile_env: Gc>, global_object: JsObject, global_this: JsObject, + + // TODO: probably refactor into struct + pub(crate) root_shape: SharedShape, + pub(crate) iterator_result_object_template: ObjectTemplate, + pub(crate) empty_object_template: ObjectTemplate, + pub(crate) array_object_template: ObjectTemplate, + pub(crate) number_object_template: ObjectTemplate, + pub(crate) string_object_template: ObjectTemplate, + pub(crate) symbol_object_template: ObjectTemplate, + pub(crate) bigint_object_template: ObjectTemplate, + pub(crate) boolean_object_template: ObjectTemplate, + + pub(crate) unmapped_arguments_object_templete: ObjectTemplate, + pub(crate) mapped_arguments_object_templete: ObjectTemplate, + pub(crate) function_with_prototype_object_template: ObjectTemplate, + pub(crate) function_prototype_object_template: ObjectTemplate, + + pub(crate) function_object_template: ObjectTemplate, + pub(crate) async_function_object_template: ObjectTemplate, } impl Realm { @@ -43,6 +67,121 @@ impl Realm { let global_compile_environment = Gc::new(GcRefCell::new(CompileTimeEnvironment::new_global())); + // pre-initialize used shapes. + let root_shape = SharedShape::root(); + let empty_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().object().prototype()); + let mut array_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().array().prototype()); + + let length_property_key: PropertyKey = "length".into(); + array_object_template.data_property( + length_property_key.clone(), + Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, + ); + + let number_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().number().prototype()); + let symbol_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().symbol().prototype()); + let bigint_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().bigint().prototype()); + let boolean_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().boolean().prototype()); + let mut string_object_template = + ObjectTemplate::new(&root_shape, intrinsics.constructors().string().prototype()); + string_object_template.data_property( + length_property_key.clone(), + Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, + ); + + // TODO: put in a better place! + // TODO: maybe construct it name, length, then prototype to create the least amount of branching. + let name_property_key: PropertyKey = "name".into(); + let mut function_object_template = ObjectTemplate::new( + &root_shape, + intrinsics.constructors().function().prototype(), + ); + function_object_template.data_property( + name_property_key.clone(), + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + function_object_template.data_property( + length_property_key.clone(), + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + + let mut async_function_object_template = ObjectTemplate::new( + &root_shape, + intrinsics.constructors().async_function().prototype(), + ); + async_function_object_template.data_property( + name_property_key, + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + async_function_object_template.data_property( + length_property_key.clone(), + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + + let mut function_with_prototype_object_template = function_object_template.clone(); + function_with_prototype_object_template.data_property( + PROTOTYPE.into(), + Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, + ); + + let mut function_prototype_object_template = empty_object_template.clone(); + function_prototype_object_template.data_property( + CONSTRUCTOR.into(), + Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + + let mut unmapped_arguments_object_templete = empty_object_template.clone(); + + // // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + unmapped_arguments_object_templete.data_property( + length_property_key, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + + // // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // // [[Configurable]]: true }). + unmapped_arguments_object_templete.data_property( + JsSymbol::iterator().into(), + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + + let mut mapped_arguments_object_templete = unmapped_arguments_object_templete.clone(); + + // // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, + // // [[Configurable]]: false }). + unmapped_arguments_object_templete.accessor_property( + "callee".into(), + true, + true, + Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ); + + // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + mapped_arguments_object_templete.data_property( + "callee".into(), + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + + let mut iterator_result_object_template = empty_object_template.clone(); + iterator_result_object_template.data_property( + "value".into(), + Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, + ); + iterator_result_object_template.data_property( + "done".into(), + Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, + ); + #[allow(unreachable_code)] Self { intrinsics, @@ -51,6 +190,21 @@ impl Realm { global_property_map: PropertyMap::default(), environments: DeclarativeEnvironmentStack::new(global_compile_environment.clone()), compile_env: global_compile_environment, + root_shape, + empty_object_template, + iterator_result_object_template, + unmapped_arguments_object_templete, + mapped_arguments_object_templete, + array_object_template, + function_with_prototype_object_template, + async_function_object_template, + function_object_template, + function_prototype_object_template, + bigint_object_template, + boolean_object_template, + number_object_template, + string_object_template, + symbol_object_template, } } @@ -62,8 +216,8 @@ impl Realm { &self.global_this } - pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap { - self.global_property_map.string_property_map_mut() + pub(crate) fn global_bindings_mut(&mut self) -> &mut PropertyMap { + &mut self.global_property_map } /// Set the number of bindings on the global environment. diff --git a/boa_engine/src/value/conversions/serde_json.rs b/boa_engine/src/value/conversions/serde_json.rs index 153c05c2680..7f2187a55b1 100644 --- a/boa_engine/src/value/conversions/serde_json.rs +++ b/boa_engine/src/value/conversions/serde_json.rs @@ -137,8 +137,8 @@ impl JsValue { Ok(Value::Array(arr)) } else { let mut map = Map::new(); - for (key, property) in obj.borrow().properties().iter() { - let key = match &key { + for property_key in obj.borrow().properties().shape.keys() { + let key = match &property_key { PropertyKey::String(string) => string.to_std_string_escaped(), PropertyKey::Index(i) => i.to_string(), PropertyKey::Symbol(_sym) => { @@ -148,7 +148,12 @@ impl JsValue { } }; - let value = match property.value() { + let value = match obj + .borrow() + .properties() + .get(&property_key) + .and_then(|x| x.value().cloned()) + { Some(val) => val.to_json(context)?, None => Value::Null, }; diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index ea54b0ffc1c..313f31b2b4f 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -67,15 +67,19 @@ macro_rules! print_obj_value { } }; (props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => { - print_obj_value!(impl $obj, |(key, val)| { + {let mut keys: Vec<_> = $obj.borrow().properties().index_property_keys().map(crate::property::PropertyKey::Index).collect(); + keys.extend($obj.borrow().properties().shape.keys()); + let mut result = Vec::default(); + for key in keys { + let val = $obj.borrow().properties().get(&key).expect("There should be a value"); if val.is_data_descriptor() { let v = &val.expect_value(); - format!( + result.push(format!( "{:>width$}: {}", key, $display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals), width = $indent, - ) + )); } else { let display = match (val.set().is_some(), val.get().is_some()) { (true, true) => "Getter & Setter", @@ -83,20 +87,10 @@ macro_rules! print_obj_value { (false, true) => "Getter", _ => "No Getter/Setter" }; - format!("{:>width$}: {}", key, display, width = $indent) + result.push(format!("{:>width$}: {}", key, display, width = $indent)); } - }) - }; - - // A private overload of the macro - // DO NOT use directly - (impl $v:expr, $f:expr) => { - $v - .borrow() - .properties() - .iter() - .map($f) - .collect::>() + } + result} }; } diff --git a/boa_engine/src/value/hash.rs b/boa_engine/src/value/hash.rs index 6536b1a77ae..c657037b1cd 100644 --- a/boa_engine/src/value/hash.rs +++ b/boa_engine/src/value/hash.rs @@ -45,7 +45,7 @@ impl Hash for JsValue { Self::BigInt(ref bigint) => bigint.hash(state), Self::Rational(rational) => RationalHashable(*rational).hash(state), Self::Symbol(ref symbol) => Hash::hash(symbol, state), - Self::Object(ref object) => std::ptr::hash(object.as_ref(), state), + Self::Object(ref object) => object.hash(state), } } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index c27dedd30f8..a89c819e939 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -21,7 +21,6 @@ use crate::{ error::JsNativeError, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyKey}, - string::utf16, symbol::JsSymbol, Context, JsBigInt, JsResult, JsString, }; @@ -467,61 +466,35 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context<'_>) -> JsResult { + // TODO: add fast paths with object template match self { Self::Undefined | Self::Null => Err(JsNativeError::typ() .with_message("cannot convert 'null' or 'undefined' to object") .into()), - Self::Boolean(boolean) => { - let prototype = context.intrinsics().constructors().boolean().prototype(); - Ok(JsObject::from_proto_and_data( - prototype, - ObjectData::boolean(*boolean), - )) - } - Self::Integer(integer) => { - let prototype = context.intrinsics().constructors().number().prototype(); - Ok(JsObject::from_proto_and_data( - prototype, - ObjectData::number(f64::from(*integer)), - )) - } - Self::Rational(rational) => { - let prototype = context.intrinsics().constructors().number().prototype(); - Ok(JsObject::from_proto_and_data( - prototype, - ObjectData::number(*rational), - )) - } - Self::String(ref string) => { - let prototype = context.intrinsics().constructors().string().prototype(); - - let object = - JsObject::from_proto_and_data(prototype, ObjectData::string(string.clone())); - // Make sure the correct length is set on our new string object - object.insert_property( - utf16!("length"), - PropertyDescriptor::builder() - .value(string.len()) - .writable(false) - .enumerable(false) - .configurable(false), - ); - Ok(object) - } - Self::Symbol(ref symbol) => { - let prototype = context.intrinsics().constructors().symbol().prototype(); - Ok(JsObject::from_proto_and_data( - prototype, - ObjectData::symbol(symbol.clone()), - )) - } - Self::BigInt(ref bigint) => { - let prototype = context.intrinsics().constructors().bigint().prototype(); - Ok(JsObject::from_proto_and_data( - prototype, - ObjectData::big_int(bigint.clone()), - )) - } + Self::Boolean(boolean) => Ok(context + .realm + .boolean_object_template + .create(ObjectData::boolean(*boolean), Vec::default())), + Self::Integer(integer) => Ok(context + .realm + .number_object_template + .create(ObjectData::number(f64::from(*integer)), Vec::default())), + Self::Rational(rational) => Ok(context + .realm + .number_object_template + .create(ObjectData::number(*rational), Vec::default())), + Self::String(ref string) => Ok(context.realm.string_object_template.create( + ObjectData::string(string.clone()), + vec![string.len().into()], + )), + Self::Symbol(ref symbol) => Ok(context + .realm + .symbol_object_template + .create(ObjectData::symbol(symbol.clone()), Vec::default())), + Self::BigInt(ref bigint) => Ok(context + .realm + .bigint_object_template + .create(ObjectData::big_int(bigint.clone()), Vec::default())), Self::Object(jsobject) => Ok(jsobject.clone()), } } diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index 6e404e04d01..0e69999bff1 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -1,3 +1,4 @@ +use boa_macros::utf16; use indoc::indoc; use super::*; diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index b99aebacb65..c3015e65d6c 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -651,6 +651,78 @@ pub(crate) fn create_function_object( constructor } +pub(crate) fn create_function_object_opcode( + code: Gc, + r#async: bool, + arrow: bool, + method: bool, + context: &mut Context<'_>, +) -> JsObject { + let _timer = Profiler::global().start_event("JsVmFunction::new", "vm"); + + let name: JsValue = context + .interner() + .resolve_expect(code.name) + .into_common::(false) + .into(); + + let length: JsValue = code.length.into(); + + let function = if r#async { + let promise_capability = PromiseCapability::new( + &context.intrinsics().constructors().promise().constructor(), + context, + ) + .expect("cannot fail per spec"); + + Function::Async { + code, + environments: context.realm.environments.clone(), + home_object: None, + promise_capability, + class_object: None, + } + } else { + Function::Ordinary { + code, + environments: context.realm.environments.clone(), + constructor_kind: ConstructorKind::Base, + home_object: None, + fields: ThinVec::new(), + private_methods: ThinVec::new(), + class_object: None, + } + }; + + let data = ObjectData::function(function); + + if r#async { + context + .realm + .async_function_object_template + .create(data, vec![name, length]) + } else if arrow || method { + context + .realm + .function_object_template + .create(data, vec![name, length]) + } else { + let prototype = context + .realm + .function_prototype_object_template + .create(ObjectData::ordinary(), vec![JsValue::undefined()]); + + let constructor = context + .realm + .function_with_prototype_object_template + .create(data, vec![name, length, prototype.clone().into()]); + + prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); + + constructor + } +} + /// Creates a new generator function object. pub(crate) fn create_generator_function_object( code: Gc, diff --git a/boa_engine/src/vm/opcode/define/mod.rs b/boa_engine/src/vm/opcode/define/mod.rs index 30adcab0665..dc6561df4ab 100644 --- a/boa_engine/src/vm/opcode/define/mod.rs +++ b/boa_engine/src/vm/opcode/define/mod.rs @@ -1,5 +1,5 @@ use crate::{ - property::PropertyDescriptor, + property::{PropertyDescriptor, PropertyKey}, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, JsValue, }; @@ -30,14 +30,19 @@ impl Operation for DefVar { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - context.global_bindings_mut().entry(key).or_insert( - PropertyDescriptor::builder() - .value(JsValue::Undefined) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - ); + + let key = PropertyKey::String(key); + if context.global_bindings_mut().get(&key).is_none() { + context.global_bindings_mut().insert( + &key, + PropertyDescriptor::builder() + .value(JsValue::Undefined) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + ); + }; } else { context.realm.environments.put_value_if_uninitialized( binding_locator.environment_index(), diff --git a/boa_engine/src/vm/opcode/get/function.rs b/boa_engine/src/vm/opcode/get/function.rs index 83cc8b92a1d..20fb2f34d20 100644 --- a/boa_engine/src/vm/opcode/get/function.rs +++ b/boa_engine/src/vm/opcode/get/function.rs @@ -1,5 +1,5 @@ use crate::{ - vm::{code_block::create_function_object, opcode::Operation, CompletionType}, + vm::{code_block::create_function_object_opcode, opcode::Operation, CompletionType}, Context, JsResult, }; @@ -18,7 +18,7 @@ impl Operation for GetArrowFunction { let index = context.vm.read::(); context.vm.read::(); let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object(code, false, true, None, false, context); + let function = create_function_object_opcode(code, false, true, false, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -39,7 +39,7 @@ impl Operation for GetAsyncArrowFunction { let index = context.vm.read::(); context.vm.read::(); let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object(code, true, true, None, false, context); + let function = create_function_object_opcode(code, true, true, false, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -60,7 +60,7 @@ impl Operation for GetFunction { let index = context.vm.read::(); let method = context.vm.read::() != 0; let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object(code, false, false, None, method, context); + let function = create_function_object_opcode(code, false, false, method, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -81,7 +81,7 @@ impl Operation for GetFunctionAsync { let index = context.vm.read::(); let method = context.vm.read::() != 0; let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object(code, true, false, None, method, context); + let function = create_function_object_opcode(code, true, false, method, context); context.vm.push(function); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/get/name.rs b/boa_engine/src/vm/opcode/get/name.rs index 7a128716bb2..6e5c078da7b 100644 --- a/boa_engine/src/vm/opcode/get/name.rs +++ b/boa_engine/src/vm/opcode/get/name.rs @@ -1,6 +1,6 @@ use crate::{ error::JsNativeError, - property::DescriptorKind, + property::{DescriptorKind, PropertyKey}, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, JsValue, }; @@ -29,7 +29,10 @@ impl Operation for GetName { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - match context.global_bindings_mut().get(&key) { + match context + .global_bindings_mut() + .get(&PropertyKey::String(key.clone())) + { Some(desc) => match desc.kind() { DescriptorKind::Data { value: Some(value), .. @@ -98,7 +101,7 @@ impl Operation for GetNameOrUndefined { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - match context.global_bindings_mut().get(&key) { + match context.global_bindings_mut().get(&PropertyKey::String(key)) { Some(desc) => match desc.kind() { DescriptorKind::Data { value: Some(value), .. diff --git a/boa_engine/src/vm/opcode/push/array.rs b/boa_engine/src/vm/opcode/push/array.rs index 18e5fa3e937..0673d9a2fce 100644 --- a/boa_engine/src/vm/opcode/push/array.rs +++ b/boa_engine/src/vm/opcode/push/array.rs @@ -1,8 +1,9 @@ use crate::{ builtins::{iterable::IteratorRecord, Array}, + object::ObjectData, string::utf16, vm::{opcode::Operation, CompletionType}, - Context, JsResult, + Context, JsResult, JsValue, }; /// `PushNewArray` implements the Opcode Operation for `Opcode::PushNewArray` @@ -17,8 +18,10 @@ impl Operation for PushNewArray { const INSTRUCTION: &'static str = "INST - PushNewArray"; fn execute(context: &mut Context<'_>) -> JsResult { - let array = Array::array_create(0, None, context) - .expect("Array creation with 0 length should never fail"); + let array = context + .realm + .array_object_template + .create(ObjectData::array(), vec![JsValue::new(0)]); context.vm.push(array); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/push/object.rs b/boa_engine/src/vm/opcode/push/object.rs index 9ec4b2bda03..52560853d7b 100644 --- a/boa_engine/src/vm/opcode/push/object.rs +++ b/boa_engine/src/vm/opcode/push/object.rs @@ -1,5 +1,5 @@ use crate::{ - object::JsObject, + object::ObjectData, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -16,7 +16,10 @@ impl Operation for PushEmptyObject { const INSTRUCTION: &'static str = "INST - PushEmptyObject"; fn execute(context: &mut Context<'_>) -> JsResult { - let o = JsObject::with_object_proto(context); + let o = context + .realm + .empty_object_template + .create(ObjectData::ordinary(), Vec::default()); context.vm.push(o); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index c8a4753404e..d3e76273d22 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -32,7 +32,11 @@ impl Operation for SetClassPrototype { _ => unreachable!(), }; - let proto = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); + let proto = JsObject::from_proto_and_data_with_shared_shape( + context.realm.root_shape.clone(), + prototype, + ObjectData::ordinary(), + ); let class = context.vm.pop(); { diff --git a/boa_engine/src/vm/opcode/set/name.rs b/boa_engine/src/vm/opcode/set/name.rs index e3f99f76a29..7ed206e22b7 100644 --- a/boa_engine/src/vm/opcode/set/name.rs +++ b/boa_engine/src/vm/opcode/set/name.rs @@ -1,5 +1,6 @@ use crate::{ error::JsNativeError, + property::PropertyKey, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, }; @@ -30,7 +31,10 @@ impl Operation for SetName { .interner() .resolve_expect(binding_locator.name().sym()) .into_common(false); - let exists = context.global_bindings_mut().contains_key(&key); + let exists = context + .global_bindings_mut() + .get(&PropertyKey::String(key.clone())) + .is_some(); if !exists && context.vm.frame().code_block.strict { return Err(JsNativeError::reference() diff --git a/docs/shapes.md b/docs/shapes.md new file mode 100644 index 00000000000..93fa38d76cf --- /dev/null +++ b/docs/shapes.md @@ -0,0 +1,220 @@ +# Shapes (Hidden Classes) + +The best way to explain object shapes is through examples. It all begins with the root shape. + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + + Root(Root Shape\nPrototype: None) -->| Property Count 0 | PropertyTable(PropertyTable\n) +``` + +The root shape is where the transition chain starts from, it has a pointer to a `PropertyTable`, +we will explain what it is and does later on! + +**NOTE:** We will annotate the shapes with `S` followed by a number. + +If we have an example of JavaScript code like: + +```js +let o = {}; +``` + +The following chain is created: + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + + Root(S0: Root Shape\nPrototype: None) -->|Property Count: 0| PropertyTable(PropertyTable\n) + + ObjectPrototype(S1: Prototype Shape\nPrototype: Object.prototype) -->|Property Count: 0|PropertyTable + ObjectPrototype:::New -->|Previous| Root +``` + +We transition, the object `o` has `S1` shape. The root shape does not have a prototype. So we transition into a shape that has the +`Object.prototype` as `__proto__`. We can see that the shapes inherited the `PropertyTable` from the `root`. + +Ok, Let us add a property `x`: + +```js +o.x = 100; // The value is not important! +``` + +Then this happens: + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + + Root(S0: Root Shape\nPrototype: None) -->|Property Count: 0| PropertyTable(PropertyTable\nx: Slot 0, writable, configurable, enumerable) + + ObjectPrototype(S1: Prototype Shape\nPrototype: Object.prototype) -->|Property Count: 0|PropertyTable + ObjectPrototype -->|Previous| Root + + InsertX(S2: Insert Shape\nProperty: 'x') --> |Property Count: 1|PropertyTable + InsertX:::New -->|Previous| ObjectPrototype +``` + +The object `o` has shape `S2` shape now, we can see that it also inherited the `PropertyTable`, but it's property count is `1` and +an entry has been added into the `PropertyTable`. + +We can see that the property added is `writable`, `configurable`, and `enumerable`, but we also see `Slot 0`, +this is the index into the dense storage in the object itself. + +Here is how it would look with the `o` object: + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + + Root(S0: Root Shape\nPrototype: None) -->| Property Count: 0 | PropertyTable(PropertyTable\nx: Slot 0, writable, configurable, enumerable) + + ObjectPrototype(S1: Prototype Shape\nPrototype: Object.prototype) -->| Property Count: 0 |PropertyTable + ObjectPrototype -->|Previous| Root + + InsertX(S2: Insert Shape\nProperty: 'x') --> | Property Count: 1 | PropertyTable + InsertX -->|Previous| ObjectPrototype + + O(Object o\nElement 0: JsValue: 100) + O:::New --> InsertX +``` + +Let's define a getter and setter `y` + +```js +// What the getter/setter are not important! +Object.defineProperty(obj, "y", { + enumerable: true, + configurable: true, + get: function () { + return this.x; + }, + set: function (value) { + this.x = value; + }, +}); +``` + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + + Root(S0: Root Shape\nPrototype: None) -->|Property Count: 0| PropertyTable(PropertyTable\nx: Slot 0, writable, configurable, enumerable\ny: Slot 1, has_get, has_set, configurable, enumerable) + + ObjectPrototype(S1: Prototype Shape\nPrototype: Object.prototype) -->| Property Count: 0 |PropertyTable + ObjectPrototype -->|Previous| Root + + InsertX(S2: Insert Shape\nProperty: 'x') --> |Property Count: 1| PropertyTable + InsertX -->|Previous| ObjectPrototype + + InsertY(S2: Insert Shape\nProperty: 'y') --> |Property Count: 2| PropertyTable + InsertY:::New -->|Previous| InsertX + + O(Object o\nElement 0: JsValue: 100\nElement 1: JsValue: func\nElement 2: JsValue: func) --> InsertY +``` + +We can see that the property has been added into the property table, it has the `has_get` and `has_set` flags set, +in the object there are two elements added, the first is the `get` function and the second is the `set` function. + +Slots are varying in length, two for accessor properties and one for data properties, the index points to the first +value in the object storage. + +What would happen if an object had `S2` shape and we tried to access a property `y` how does it know if it +has or doesn't have a property named `y`? By the property count on the shape, it has property count `1`, +all the object in the `PropertyTable` are stored in a map that preserves the order and and can be indexed. + +When we do a lookup the on property table, if the index of the property is greater than the property count (`1`), +than it does not belong to the shape. + +Now, Let's create a new object `o2`, with property `x`: + +```js +let o2 = { x: 200 }; +``` + +After this `o2` would have the `S2` shape. + +How does the shape know that it can reuse `S1` then to go to `S2`? This is not the real structure! +Every shape has pointers to forward transitions that happened, these are weak pointers so we don't keep +alive unused shapes. The pointers have been omitted, so the diagrams are clearer (too many arrows). + +Ok, now let us define a property `z` instead of `y`: + +```js +o2.z = 300; +``` + +The following changes accure to the shape tree: + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + style PropertyTableFork fill:#071E22 + + Root(S0: Root Shape\nPrototype: None) -->| Property Count: 0 | PropertyTable(PropertyTable\nx: Slot 0, writable, configurable, enumerable\ny: Slot 1, has_get, has_set, configurable, enumerable) + + ObjectPrototype(S1: Prototype Shape\nPrototype: Object.prototype) -->| Property Count: 0 |PropertyTable + ObjectPrototype -->|Previous| Root + + InsertX(S2: Insert Shape\nProperty: 'x') --> | Property Count: 1 | PropertyTable + InsertX -->|Previous| ObjectPrototype + + InsertY(S3: Insert Shape\nProperty: 'y') --> | Property Count: 2 | PropertyTable + InsertY -->|Previous| InsertX + + PropertyTableFork(PropertyTable\nx: Slot 0, writable, configurable, enumerable\nz: Slot 1, writable, configurable, enumerable) + InsertZ(S4: Insert Shape\nProperty: 'z') --> | Property Count: 2 | PropertyTableFork:::New + InsertZ:::New -->|Previous| InsertX + + O(Object o\nElement 0: JsValue: 100\nElement 1: JsValue: func\nElement 2: JsValue: func) --> InsertY + O2(Object o2\nElement 0: JsValue: 200\nElement 1: JsValue: 300) + O2:::New --> InsertZ +``` + +Now `o2` has `S4` shape. We can also see that `PropertyTable` has been forked, because we can no longer add a property at position `1`. + +What would happen if we wanted to delete a property `x` from object `o`: + +```js +delete o.x; +``` + +```mermaid +flowchart LR + classDef New style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + style Root stroke:#000,stroke-width:5px + style PropertyTable fill:#071E22 + style PropertyTableFork fill:#071E22 + + Root(S0: Root Shape\nPrototype: None) -->| Property Count: 0 | PropertyTable(PropertyTable\nx: Slot 0, writable, configurable, enumerable\ny: Slot 1, has_get, has_set, configurable, enumerable) + + ObjectPrototype(S1: Prototype Shape\nPrototype: Object.prototype) -->| Property Count: 0 |PropertyTable + ObjectPrototype -->|Previous| Root + + + PropertyTableFork(PropertyTable\ny: Slot 0, has_get, has_set, configurable, enumerable) + InsertYNew(S3: Insert Shape\nProperty: y) --> | Property Count: 1 |PropertyTableFork:::New + InsertYNew:::New -->|Previous| ObjectPrototype + + O(Object o\nElement 0: JsValue: func\nElement 1: JsValue: func) --> InsertYNew + O:::New +``` + +**NOTE:**: `o2` has been omitted and it's shape. + +When a deletion happens, we find the node in the chain where we added the property, and get it's parent (`base`), +we also remember that what transitions happened after the property insertion, then we apply them +one by one until we construct the chain and return the last shape in that chain. From 07bb99db9bc5f9020f6c02c935ab00f2ef549662 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 03:19:16 +0200 Subject: [PATCH 02/20] Fix property attribute change in unique shapes --- boa_engine/src/object/property_map.rs | 10 ++++++++ boa_engine/src/object/shape/slot.rs | 2 +- boa_engine/src/object/shape/unique_shape.rs | 27 +++++++++++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index 54119a1159b..199dfd2cc8f 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -347,6 +347,16 @@ impl PropertyMap { attributes, }; self.shape = self.shape.insert_property_transition(transition_key); + + // Make Sure that if we are inserting, it has the correct slot index. + debug_assert_eq!( + self.shape.lookup(key), + Some(Slot { + index: self.storage.len() as u32, + attributes + }) + ); + if attributes.is_accessor_descriptor() { self.storage.push( property diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index cab40dd7c4a..ecff6cc1098 100644 --- a/boa_engine/src/object/shape/slot.rs +++ b/boa_engine/src/object/shape/slot.rs @@ -45,7 +45,7 @@ impl SlotAttribute { /// /// Slots can be diffent width depending on its attributes, accessors properties have width `2`, /// while data properties have width `1`. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Slot { pub index: SlotIndex, pub attributes: SlotAttribute, diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index bc5efe770b0..112803b9a32 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -162,8 +162,31 @@ impl UniqueShape { // to invalidate any pointers to this shape i.e inline caches. let mut property_table = std::mem::take(&mut *property_table); - // Update all slot positions - for (key, slot) in property_table.keys.iter_mut().skip(index) { + // Update all slot positions, after the target property. + // + // Example: Change 2nd one from accessor to data + // + // | Idx: 0, DATA | Idx: 1, ACCESSOR | Idx: 3, DATA | + // \----- target + // + // Change attributes of target (given trough arguments). + // + // | Idx: 0, DATA | Idx: 1, DATA | Idx: 3, DATA | + // \----- target + // + // The target becomes the previous pointer and next is target_index + 1, + // continue until we reach the end. + // + // | Idx: 0, DATA | Idx: 1, DATA | Idx: 3, DATA | + // previous ----/ \-------- next + // + // When next is out of range we quit, everything has been set. + // + // | Idx: 0, DATA | Idx: 1, DATA | Idx: 2, DATA | EMPTY | + // previous ----/ \-------- next + // + let next = index + 1; + for (key, slot) in property_table.keys.iter_mut().skip(next) { *slot = Slot::from_previous(Some(previous_slot), slot.attributes); let Some((map_index, map_slot)) = property_table.map.get_mut(key) else { From 0861511e5eedcf39c3f102e4ae732b2bca132b0e Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 03:21:14 +0200 Subject: [PATCH 03/20] Remove overwritten length property on TypedArray --- boa_engine/src/builtins/typed_array/mod.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 1674b618bb6..26e2f697bdd 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -275,11 +275,6 @@ impl IntrinsicObject for TypedArray { None, Attribute::CONFIGURABLE, ) - .property( - utf16!("length"), - 0, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, - ) .property( JsSymbol::iterator(), values_function, From aefe1ca480fb6d339e80a32b61dba1078fe02abc Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 04:47:17 +0200 Subject: [PATCH 04/20] Fix property attribute change --- boa_engine/src/object/shape/property_table.rs | 17 ++++++++++++----- boa_engine/src/object/shape/shared_shape/mod.rs | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs index ae27d70d5b9..763b9f26fe0 100644 --- a/boa_engine/src/object/shape/property_table.rs +++ b/boa_engine/src/object/shape/property_table.rs @@ -121,12 +121,19 @@ impl PropertyTable { } /// Change the attribue of a property. - pub(crate) fn set_attributes_at_index(&self, index: usize, property_attributes: SlotAttribute) { + pub(crate) fn set_attributes_at_index( + &self, + key: &PropertyKey, + property_attributes: SlotAttribute, + ) { let mut inner = self.inner.borrow_mut(); - let Some((_key, slot)) = inner.keys.get_mut(index) else { + let Some((index, slot)) = inner.map.get_mut(key) else { unreachable!("There should already be a property!") }; slot.attributes = property_attributes; + let index = *index as usize; + + inner.keys[index].1.attributes = property_attributes; } /// Get a property from the [`PropertyTable`]. @@ -134,11 +141,11 @@ impl PropertyTable { /// Panics: /// /// If it is not in the [`PropertyTable`]. - pub(crate) fn get_expect(&self, key: &PropertyKey) -> (usize, Slot) { + pub(crate) fn get_expect(&self, key: &PropertyKey) -> Slot { let inner = self.inner.borrow(); - let Some((index, slot)) = inner.map.get(key) else { + let Some((_, slot)) = inner.map.get(key) else { unreachable!("There should already be a property!") }; - (*index as usize, *slot) + *slot } } diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index 0c5fc1a146a..e1374dad810 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -204,7 +204,7 @@ impl SharedShape { &self, key: TransitionKey, ) -> ChangeTransition { - let (index, slot) = self.property_table().get_expect(&key.property_key); + let slot = self.property_table().get_expect(&key.property_key); // Check if we have already created such a transition, if so use it! if let Some(shape) = self.forward_transitions().get_property(&key) { @@ -229,7 +229,7 @@ impl SharedShape { // The attribute change transitions, didn't change from accessor to data property or vice-versa. if slot.attributes.width_match(key.attributes) { let property_table = self.property_table().deep_clone_all(); - property_table.set_attributes_at_index(index, key.attributes); + property_table.set_attributes_at_index(&key.property_key, key.attributes); let inner_shape = Inner { forward_transitions: ForwardTransition::default(), prototype: self.prototype(), From 228a719ebea295e11bc0ec1698d54b44925dbb1f Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 04:52:51 +0200 Subject: [PATCH 05/20] Simplify builtin function construction --- boa_engine/src/builtins/array/mod.rs | 15 ++--- boa_engine/src/builtins/array_buffer/mod.rs | 6 +- boa_engine/src/builtins/dataview/mod.rs | 9 +-- boa_engine/src/builtins/eval/mod.rs | 3 +- boa_engine/src/builtins/function/mod.rs | 16 +++--- boa_engine/src/builtins/intl/collator/mod.rs | 3 +- boa_engine/src/builtins/intl/locale/mod.rs | 30 ++++------ boa_engine/src/builtins/map/mod.rs | 9 +-- boa_engine/src/builtins/mod.rs | 60 ++++++++++++-------- boa_engine/src/builtins/number/globals.rs | 12 ++-- boa_engine/src/builtins/object/mod.rs | 6 +- boa_engine/src/builtins/promise/mod.rs | 3 +- boa_engine/src/builtins/regexp/mod.rs | 30 ++++------ boa_engine/src/builtins/set/mod.rs | 9 +-- boa_engine/src/builtins/symbol/mod.rs | 6 +- boa_engine/src/builtins/typed_array/mod.rs | 24 +++----- boa_engine/src/builtins/uri/mod.rs | 12 ++-- 17 files changed, 107 insertions(+), 146 deletions(-) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 77552468091..136e43439d2 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -46,16 +46,17 @@ impl IntrinsicObject for Array { let symbol_iterator = JsSymbol::iterator(); let symbol_unscopables = JsSymbol::unscopables(); - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); - let values_function = - BuiltInBuilder::with_object(intrinsics, intrinsics.objects().array_prototype_values()) - .callable(Self::values) - .name("values") - .build(); + let values_function = BuiltInBuilder::callable_with_object( + intrinsics, + intrinsics.objects().array_prototype_values(), + Self::values, + ) + .name("values") + .build(); let unscopables_object = Self::unscopables_object(); diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 7dc414d3fea..974e3fad846 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -52,13 +52,11 @@ impl IntrinsicObject for ArrayBuffer { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); - let get_byte_length = BuiltInBuilder::new(intrinsics) - .callable(Self::get_byte_length) + let get_byte_length = BuiltInBuilder::callable(intrinsics, Self::get_byte_length) .name("get byteLength") .build(); diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 72d14c4f6cc..8264c3d24d9 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -34,18 +34,15 @@ impl IntrinsicObject for DataView { fn init(intrinsics: &Intrinsics) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_buffer = BuiltInBuilder::new(intrinsics) - .callable(Self::get_buffer) + let get_buffer = BuiltInBuilder::callable(intrinsics, Self::get_buffer) .name("get buffer") .build(); - let get_byte_length = BuiltInBuilder::new(intrinsics) - .callable(Self::get_byte_length) + let get_byte_length = BuiltInBuilder::callable(intrinsics, Self::get_byte_length) .name("get byteLength") .build(); - let get_byte_offset = BuiltInBuilder::new(intrinsics) - .callable(Self::get_byte_offset) + let get_byte_offset = BuiltInBuilder::callable(intrinsics, Self::get_byte_offset) .name("get byteOffset") .build(); diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 981efaf896a..544e611d3b9 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -28,8 +28,7 @@ impl IntrinsicObject for Eval { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(Self::eval) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, Self::eval) .name(Self::NAME) .length(1) .build(); diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index ca45f2206ea..960ea7328e4 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -401,14 +401,16 @@ impl IntrinsicObject for BuiltInFunctionObject { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event("function", "init"); - BuiltInBuilder::with_object(intrinsics, intrinsics.constructors().function().prototype()) - .callable(Self::prototype) - .name("") - .length(0) - .build(); + BuiltInBuilder::callable_with_object( + intrinsics, + intrinsics.constructors().function().prototype(), + Self::prototype, + ) + .name("") + .length(0) + .build(); - let has_instance = BuiltInBuilder::new(intrinsics) - .callable(Self::has_instance) + let has_instance = BuiltInBuilder::callable(intrinsics, Self::has_instance) .name("[Symbol.iterator]") .length(1) .build(); diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index c0cb6178758..c9fe07a7e68 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -162,8 +162,7 @@ impl IntrinsicObject for Collator { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let compare = BuiltInBuilder::new(intrinsics) - .callable(Self::compare) + let compare = BuiltInBuilder::callable(intrinsics, Self::compare) .name("get compare") .build(); diff --git a/boa_engine/src/builtins/intl/locale/mod.rs b/boa_engine/src/builtins/intl/locale/mod.rs index 264335c0ac9..3a80f1c7fe1 100644 --- a/boa_engine/src/builtins/intl/locale/mod.rs +++ b/boa_engine/src/builtins/intl/locale/mod.rs @@ -35,53 +35,43 @@ impl IntrinsicObject for Locale { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let base_name = BuiltInBuilder::new(intrinsics) - .callable(Self::base_name) + let base_name = BuiltInBuilder::callable(intrinsics, Self::base_name) .name("get baseName") .build(); - let calendar = BuiltInBuilder::new(intrinsics) - .callable(Self::calendar) + let calendar = BuiltInBuilder::callable(intrinsics, Self::calendar) .name("get calendar") .build(); - let case_first = BuiltInBuilder::new(intrinsics) - .callable(Self::case_first) + let case_first = BuiltInBuilder::callable(intrinsics, Self::case_first) .name("get caseFirst") .build(); - let collation = BuiltInBuilder::new(intrinsics) - .callable(Self::collation) + let collation = BuiltInBuilder::callable(intrinsics, Self::collation) .name("get collation") .build(); - let hour_cycle = BuiltInBuilder::new(intrinsics) - .callable(Self::hour_cycle) + let hour_cycle = BuiltInBuilder::callable(intrinsics, Self::hour_cycle) .name("get hourCycle") .build(); - let numeric = BuiltInBuilder::new(intrinsics) - .callable(Self::numeric) + let numeric = BuiltInBuilder::callable(intrinsics, Self::numeric) .name("get numeric") .build(); - let numbering_system = BuiltInBuilder::new(intrinsics) - .callable(Self::numbering_system) + let numbering_system = BuiltInBuilder::callable(intrinsics, Self::numbering_system) .name("get numberingSystem") .build(); - let language = BuiltInBuilder::new(intrinsics) - .callable(Self::language) + let language = BuiltInBuilder::callable(intrinsics, Self::language) .name("get language") .build(); - let script = BuiltInBuilder::new(intrinsics) - .callable(Self::script) + let script = BuiltInBuilder::callable(intrinsics, Self::script) .name("get script") .build(); - let region = BuiltInBuilder::new(intrinsics) - .callable(Self::region) + let region = BuiltInBuilder::callable(intrinsics, Self::region) .name("get region") .build(); diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 346ca475aac..3ab76fe5609 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -40,18 +40,15 @@ impl IntrinsicObject for Map { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); - let get_size = BuiltInBuilder::new(intrinsics) - .callable(Self::get_size) + let get_size = BuiltInBuilder::callable(intrinsics, Self::get_size) .name("get size") .build(); - let entries_function = BuiltInBuilder::new(intrinsics) - .callable(Self::entries) + let entries_function = BuiltInBuilder::callable(intrinsics, Self::entries) .name("entries") .build(); diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 0d4191d6edc..28c3e2d181e 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -520,54 +520,68 @@ struct BuiltInBuilder<'ctx, Kind> { } impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { - fn new(intrinsics: &'ctx Intrinsics) -> BuiltInBuilder<'ctx, OrdinaryObject> { + fn with_intrinsic( + intrinsics: &'ctx Intrinsics, + ) -> BuiltInBuilder<'ctx, OrdinaryObject> { BuiltInBuilder { intrinsics, - object: JsObject::with_null_proto(), + object: I::get(intrinsics), kind: OrdinaryObject, prototype: intrinsics.constructors().object().prototype(), } } +} - fn with_intrinsic( +impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { + fn callable( intrinsics: &'ctx Intrinsics, - ) -> BuiltInBuilder<'ctx, OrdinaryObject> { + function: NativeFunctionPointer, + ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { intrinsics, - object: I::get(intrinsics), - kind: OrdinaryObject, - prototype: intrinsics.constructors().object().prototype(), + object: JsObject::with_null_proto(), + kind: Callable { + function, + name: js_string!(""), + length: 0, + kind: OrdinaryFunction, + }, + prototype: intrinsics.constructors().function().prototype(), } } - fn with_object( + fn callable_with_intrinsic( intrinsics: &'ctx Intrinsics, - object: JsObject, - ) -> BuiltInBuilder<'ctx, OrdinaryObject> { + function: NativeFunctionPointer, + ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { intrinsics, - object, - kind: OrdinaryObject, - prototype: intrinsics.constructors().object().prototype(), + object: I::get(intrinsics), + kind: Callable { + function, + name: js_string!(""), + length: 0, + kind: OrdinaryFunction, + }, + prototype: intrinsics.constructors().function().prototype(), } } -} -impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { - fn callable( - self, + fn callable_with_object( + intrinsics: &'ctx Intrinsics, + object: JsObject, function: NativeFunctionPointer, ) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { - intrinsics: self.intrinsics, - object: self.object, + intrinsics, + object, kind: Callable { function, name: js_string!(""), length: 0, kind: OrdinaryFunction, }, - prototype: self.intrinsics.constructors().function().prototype(), + prototype: intrinsics.constructors().function().prototype(), } } } @@ -616,8 +630,7 @@ impl BuiltInBuilder<'_, T> { B: Into, { let binding = binding.into(); - let function = BuiltInBuilder::new(self.intrinsics) - .callable(function) + let function = BuiltInBuilder::callable(self.intrinsics, function) .name(binding.name) .length(length) .build(); @@ -684,8 +697,7 @@ impl BuiltInBuilder<'_, Callable> { B: Into, { let binding = binding.into(); - let function = BuiltInBuilder::new(self.intrinsics) - .callable(function) + let function = BuiltInBuilder::callable(self.intrinsics, function) .name(binding.name) .length(length) .build(); diff --git a/boa_engine/src/builtins/number/globals.rs b/boa_engine/src/builtins/number/globals.rs index 6ef92ba1a51..49d1383c47a 100644 --- a/boa_engine/src/builtins/number/globals.rs +++ b/boa_engine/src/builtins/number/globals.rs @@ -37,8 +37,7 @@ pub(crate) struct IsFinite; impl IntrinsicObject for IsFinite { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(is_finite) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, is_finite) .name(Self::NAME) .length(1) .build(); @@ -84,8 +83,7 @@ pub(crate) struct IsNaN; impl IntrinsicObject for IsNaN { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(is_nan) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, is_nan) .name(Self::NAME) .length(1) .build(); @@ -226,8 +224,7 @@ pub(crate) struct ParseInt; impl IntrinsicObject for ParseInt { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(parse_int) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, parse_int) .name(Self::NAME) .length(2) .build(); @@ -300,8 +297,7 @@ pub(crate) struct ParseFloat; impl IntrinsicObject for ParseFloat { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(parse_float) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, parse_float) .name(Self::NAME) .length(1) .build(); diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 921a479e410..836a2b33630 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -47,13 +47,11 @@ impl IntrinsicObject for Object { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let legacy_proto_getter = BuiltInBuilder::new(intrinsics) - .callable(Self::legacy_proto_getter) + let legacy_proto_getter = BuiltInBuilder::callable(intrinsics, Self::legacy_proto_getter) .name("get __proto__") .build(); - let legacy_setter_proto = BuiltInBuilder::new(intrinsics) - .callable(Self::legacy_proto_setter) + let legacy_setter_proto = BuiltInBuilder::callable(intrinsics, Self::legacy_proto_setter) .name("set __proto__") .build(); diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 1517f04d0c4..275e4cdbfc3 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -311,8 +311,7 @@ impl IntrinsicObject for Promise { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 71535735856..9411dbce68e 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -47,47 +47,37 @@ impl IntrinsicObject for RegExp { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_has_indices = BuiltInBuilder::new(intrinsics) - .callable(Self::get_has_indices) + let get_has_indices = BuiltInBuilder::callable(intrinsics, Self::get_has_indices) .name("get hasIndices") .build(); - let get_global = BuiltInBuilder::new(intrinsics) - .callable(Self::get_global) + let get_global = BuiltInBuilder::callable(intrinsics, Self::get_global) .name("get global") .build(); - let get_ignore_case = BuiltInBuilder::new(intrinsics) - .callable(Self::get_ignore_case) + let get_ignore_case = BuiltInBuilder::callable(intrinsics, Self::get_ignore_case) .name("get ignoreCase") .build(); - let get_multiline = BuiltInBuilder::new(intrinsics) - .callable(Self::get_multiline) + let get_multiline = BuiltInBuilder::callable(intrinsics, Self::get_multiline) .name("get multiline") .build(); - let get_dot_all = BuiltInBuilder::new(intrinsics) - .callable(Self::get_dot_all) + let get_dot_all = BuiltInBuilder::callable(intrinsics, Self::get_dot_all) .name("get dotAll") .build(); - let get_unicode = BuiltInBuilder::new(intrinsics) - .callable(Self::get_unicode) + let get_unicode = BuiltInBuilder::callable(intrinsics, Self::get_unicode) .name("get unicode") .build(); - let get_sticky = BuiltInBuilder::new(intrinsics) - .callable(Self::get_sticky) + let get_sticky = BuiltInBuilder::callable(intrinsics, Self::get_sticky) .name("get sticky") .build(); - let get_flags = BuiltInBuilder::new(intrinsics) - .callable(Self::get_flags) + let get_flags = BuiltInBuilder::callable(intrinsics, Self::get_flags) .name("get flags") .build(); - let get_source = BuiltInBuilder::new(intrinsics) - .callable(Self::get_source) + let get_source = BuiltInBuilder::callable(intrinsics, Self::get_source) .name("get source") .build(); BuiltInBuilder::from_standard_constructor::(intrinsics) diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index d9566e33f7b..7a6b0376b85 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -43,18 +43,15 @@ impl IntrinsicObject for Set { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); - let size_getter = BuiltInBuilder::new(intrinsics) - .callable(Self::size_getter) + let size_getter = BuiltInBuilder::callable(intrinsics, Self::size_getter) .name("get size") .build(); - let values_function = BuiltInBuilder::new(intrinsics) - .callable(Self::values) + let values_function = BuiltInBuilder::callable(intrinsics, Self::values) .name("values") .build(); diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index b1a0bc9b868..2a9439d6087 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -110,14 +110,12 @@ impl IntrinsicObject for Symbol { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let to_primitive = BuiltInBuilder::new(intrinsics) - .callable(Self::to_primitive) + let to_primitive = BuiltInBuilder::callable(intrinsics, Self::to_primitive) .name("[Symbol.toPrimitive]") .length(1) .build(); - let get_description = BuiltInBuilder::new(intrinsics) - .callable(Self::get_description) + let get_description = BuiltInBuilder::callable(intrinsics, Self::get_description) .name("get description") .build(); diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 26e2f697bdd..75fdadee868 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -50,8 +50,7 @@ macro_rules! typed_array { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = BuiltInBuilder::new(intrinsics) - .callable(TypedArray::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, TypedArray::get_species) .name("get [Symbol.species]") .build(); @@ -232,38 +231,31 @@ pub(crate) struct TypedArray; impl IntrinsicObject for TypedArray { fn init(intrinsics: &Intrinsics) { - let get_species = BuiltInBuilder::new(intrinsics) - .callable(Self::get_species) + let get_species = BuiltInBuilder::callable(intrinsics, Self::get_species) .name("get [Symbol.species]") .build(); - let get_buffer = BuiltInBuilder::new(intrinsics) - .callable(Self::buffer) + let get_buffer = BuiltInBuilder::callable(intrinsics, Self::buffer) .name("get buffer") .build(); - let get_byte_length = BuiltInBuilder::new(intrinsics) - .callable(Self::byte_length) + let get_byte_length = BuiltInBuilder::callable(intrinsics, Self::byte_length) .name("get byteLength") .build(); - let get_byte_offset = BuiltInBuilder::new(intrinsics) - .callable(Self::byte_offset) + let get_byte_offset = BuiltInBuilder::callable(intrinsics, Self::byte_offset) .name("get byteOffset") .build(); - let get_length = BuiltInBuilder::new(intrinsics) - .callable(Self::length) + let get_length = BuiltInBuilder::callable(intrinsics, Self::length) .name("get length") .build(); - let get_to_string_tag = BuiltInBuilder::new(intrinsics) - .callable(Self::to_string_tag) + let get_to_string_tag = BuiltInBuilder::callable(intrinsics, Self::to_string_tag) .name("get [Symbol.toStringTag]") .build(); - let values_function = BuiltInBuilder::new(intrinsics) - .callable(Self::values) + let values_function = BuiltInBuilder::callable(intrinsics, Self::values) .name("values") .length(0) .build(); diff --git a/boa_engine/src/builtins/uri/mod.rs b/boa_engine/src/builtins/uri/mod.rs index e8350e044bb..0c25223f247 100644 --- a/boa_engine/src/builtins/uri/mod.rs +++ b/boa_engine/src/builtins/uri/mod.rs @@ -79,8 +79,7 @@ pub(crate) struct DecodeUri; impl IntrinsicObject for DecodeUri { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(decode_uri) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, decode_uri) .name(Self::NAME) .length(1) .build(); @@ -98,8 +97,7 @@ pub(crate) struct DecodeUriComponent; impl IntrinsicObject for DecodeUriComponent { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(decode_uri_component) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, decode_uri_component) .name(Self::NAME) .length(1) .build(); @@ -121,8 +119,7 @@ pub(crate) struct EncodeUri; impl IntrinsicObject for EncodeUri { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(encode_uri) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, encode_uri) .name(Self::NAME) .length(1) .build(); @@ -139,8 +136,7 @@ pub(crate) struct EncodeUriComponent; impl IntrinsicObject for EncodeUriComponent { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(encode_uri_component) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, encode_uri_component) .name(Self::NAME) .length(1) .build(); From 01533f16f4eba07e62bd8f10fa37bdb15ec80c23 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 16:03:14 +0200 Subject: [PATCH 06/20] Refactor templates into separate struct --- boa_engine/benches/full.rs | 6 +- .../src/builtins/array/array_iterator.rs | 2 +- boa_engine/src/builtins/array/mod.rs | 28 ++- boa_engine/src/builtins/array_buffer/mod.rs | 2 +- boa_engine/src/builtins/boolean/mod.rs | 2 +- boa_engine/src/builtins/dataview/mod.rs | 2 +- boa_engine/src/builtins/date/mod.rs | 2 +- boa_engine/src/builtins/error/aggregate.rs | 2 +- boa_engine/src/builtins/error/eval.rs | 2 +- boa_engine/src/builtins/error/mod.rs | 2 +- boa_engine/src/builtins/error/range.rs | 2 +- boa_engine/src/builtins/error/reference.rs | 2 +- boa_engine/src/builtins/error/syntax.rs | 2 +- boa_engine/src/builtins/error/type.rs | 2 +- boa_engine/src/builtins/error/uri.rs | 2 +- boa_engine/src/builtins/function/arguments.rs | 40 ++-- boa_engine/src/builtins/intl/collator/mod.rs | 2 +- boa_engine/src/builtins/iterable/mod.rs | 5 +- boa_engine/src/builtins/map/mod.rs | 2 +- boa_engine/src/builtins/mod.rs | 7 +- boa_engine/src/builtins/number/mod.rs | 2 +- boa_engine/src/builtins/object/mod.rs | 2 +- boa_engine/src/builtins/promise/mod.rs | 2 +- boa_engine/src/builtins/proxy/mod.rs | 2 +- boa_engine/src/builtins/regexp/mod.rs | 2 +- boa_engine/src/builtins/set/mod.rs | 2 +- boa_engine/src/builtins/set/set_iterator.rs | 2 +- boa_engine/src/builtins/string/mod.rs | 2 +- boa_engine/src/builtins/typed_array/mod.rs | 2 +- boa_engine/src/builtins/weak_map/mod.rs | 2 +- boa_engine/src/builtins/weak_set/mod.rs | 2 +- boa_engine/src/context/intrinsics.rs | 212 +++++++++++++++++- boa_engine/src/context/mod.rs | 12 +- boa_engine/src/error.rs | 2 +- boa_engine/src/object/builtins/jsdataview.rs | 2 +- boa_engine/src/object/builtins/jsdate.rs | 4 +- boa_engine/src/object/builtins/jsgenerator.rs | 2 +- boa_engine/src/object/builtins/jsmap.rs | 2 +- boa_engine/src/object/builtins/jspromise.rs | 2 +- boa_engine/src/object/builtins/jsproxy.rs | 2 +- boa_engine/src/object/mod.rs | 6 +- boa_engine/src/object/property_map.rs | 8 +- boa_engine/src/object/shape/mod.rs | 10 +- .../src/object/shape/shared_shape/mod.rs | 23 +- .../src/object/shape/shared_shape/template.rs | 33 +-- boa_engine/src/realm.rs | 160 +------------ boa_engine/src/value/mod.rs | 27 ++- boa_engine/src/vm/code_block.rs | 20 +- boa_engine/src/vm/opcode/push/array.rs | 5 +- boa_engine/src/vm/opcode/push/object.rs | 5 +- .../src/vm/opcode/set/class_prototype.rs | 2 +- 51 files changed, 384 insertions(+), 293 deletions(-) diff --git a/boa_engine/benches/full.rs b/boa_engine/benches/full.rs index a5591d63abe..5317bbdb650 100644 --- a/boa_engine/benches/full.rs +++ b/boa_engine/benches/full.rs @@ -1,7 +1,8 @@ //! Benchmarks of the whole execution engine in Boa. use boa_engine::{ - context::DefaultHooks, optimizer::OptimizerOptions, realm::Realm, Context, Source, + context::DefaultHooks, object::shape::SharedShape, optimizer::OptimizerOptions, realm::Realm, + Context, Source, }; use criterion::{criterion_group, criterion_main, Criterion}; use std::hint::black_box; @@ -15,7 +16,8 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; fn create_realm(c: &mut Criterion) { c.bench_function("Create Realm", move |b| { - b.iter(|| Realm::create(&DefaultHooks)) + let root_shape = SharedShape::root(); + b.iter(|| Realm::create(&DefaultHooks, &root_shape)) }); } diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index d96220dce4a..6f0c103f666 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -78,7 +78,7 @@ impl ArrayIterator { context: &Context<'_>, ) -> JsValue { let array_iterator = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), context.intrinsics().objects().iterator_prototypes().array(), ObjectData::array_iterator(Self::new(array, kind)), ); diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 136e43439d2..30b80d9b563 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -243,10 +243,11 @@ impl Array { } // Fast path: - if prototype.is_none() || prototype == context.realm.array_object_template.prototype() { + if prototype.is_none() { return Ok(context - .realm - .array_object_template + .intrinsics() + .templates() + .array() .create(ObjectData::array(), vec![JsValue::new(length)])); } @@ -258,8 +259,22 @@ impl Array { let prototype = prototype.unwrap_or_else(|| context.intrinsics().constructors().array().prototype()); + // Fast path: + if context + .intrinsics() + .templates() + .array() + .has_prototype(&prototype) + { + return Ok(context + .intrinsics() + .templates() + .array() + .create(ObjectData::array(), vec![JsValue::new(length)])); + } + let array = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::array(), ); @@ -302,8 +317,9 @@ impl Array { let length = elements.len(); context - .realm - .array_object_template + .intrinsics() + .templates() + .array() .create_with_index_properties(ObjectData::array(), vec![JsValue::new(length)], elements) } diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 974e3fad846..014d2a50690 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -353,7 +353,7 @@ impl ArrayBuffer { // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. let obj = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::array_buffer(Self { array_buffer_data: Some(block), diff --git a/boa_engine/src/builtins/boolean/mod.rs b/boa_engine/src/builtins/boolean/mod.rs index 1ed97311afd..54f11146c13 100644 --- a/boa_engine/src/builtins/boolean/mod.rs +++ b/boa_engine/src/builtins/boolean/mod.rs @@ -68,7 +68,7 @@ impl BuiltInConstructor for Boolean { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?; let boolean = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::boolean(data), ); diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 8264c3d24d9..e944e553ec9 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -191,7 +191,7 @@ impl BuiltInConstructor for DataView { } let obj = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::data_view(Self { // 11. Set O.[[ViewedArrayBuffer]] to buffer. diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index d44e9227409..92fdbb483c3 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -268,7 +268,7 @@ impl BuiltInConstructor for Date { // 7. Set O.[[DateValue]] to dv. let obj = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::date(dv), ); diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index 1df777ef24a..11c7f5132d7 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -67,7 +67,7 @@ impl BuiltInConstructor for AggregateError { context, )?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Aggregate), ); diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index a9d57b95338..9525d5c665b 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -66,7 +66,7 @@ impl BuiltInConstructor for EvalError { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Eval), ); diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index 8a2f17f0957..254481b1140 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -167,7 +167,7 @@ impl BuiltInConstructor for Error { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Error), ); diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index 6a796343336..a952f3db581 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -64,7 +64,7 @@ impl BuiltInConstructor for RangeError { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Range), ); diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index 659d8164053..1a08e498cd4 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -66,7 +66,7 @@ impl BuiltInConstructor for ReferenceError { context, )?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Reference), ); diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index eb18c1a4d5b..cbd248824e4 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -69,7 +69,7 @@ impl BuiltInConstructor for SyntaxError { context, )?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Syntax), ); diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 43bee8ba2f1..0c79cc4d6b3 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -74,7 +74,7 @@ impl BuiltInConstructor for TypeError { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Type), ); diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index d455a43873e..3a34534a940 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -65,7 +65,7 @@ impl BuiltInConstructor for UriError { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(ErrorKind::Uri), ); diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 7fe20aee5bf..b882a5715fa 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -80,23 +80,27 @@ impl Arguments { // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). // 3. Set obj.[[ParameterMap]] to undefined. // skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]` - let obj = context.realm.unmapped_arguments_object_templete.create( - ObjectData::arguments(Self::Unmapped), - vec![ - // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), - // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - len.into(), - // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { - // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, - // [[Configurable]]: true }). - values_function.into(), - // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { - // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, - // [[Configurable]]: false }). - throw_type_error.clone().into(), // get - throw_type_error.into(), // set - ], - ); + let obj = context + .intrinsics() + .templates() + .unmapped_arguments() + .create( + ObjectData::arguments(Self::Unmapped), + vec![ + // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + len.into(), + // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // [[Configurable]]: true }). + values_function.into(), + // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, + // [[Configurable]]: false }). + throw_type_error.clone().into(), // get + throw_type_error.into(), // set + ], + ); // 5. Let index be 0. // 6. Repeat, while index < len, @@ -193,7 +197,7 @@ impl Arguments { let values_function = context.intrinsics().objects().array_prototype_values(); // 11. Set obj.[[ParameterMap]] to map. - let obj = context.realm.mapped_arguments_object_templete.create( + let obj = context.intrinsics().templates().mapped_arguments().create( ObjectData::arguments(Self::Mapped(map)), vec![ // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index c9fe07a7e68..d5ea6ee894b 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -353,7 +353,7 @@ impl BuiltInConstructor for Collator { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::collator, context)?; let collator = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::collator(Self { locale, diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index dee019a567c..b393669fa79 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -189,8 +189,9 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte // 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value). // 4. Perform ! CreateDataPropertyOrThrow(obj, "done", done). let obj = context - .realm - .iterator_result_object_template + .intrinsics() + .templates() + .iterator_result() .create(ObjectData::ordinary(), vec![value, done.into()]); // 5. Return obj. diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 3ab76fe5609..0b220b8bb6f 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -133,7 +133,7 @@ impl BuiltInConstructor for Map { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::map, context)?; let map = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::map(OrderedMap::new()), ); diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 28c3e2d181e..2974bc039d0 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/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, + shape::shared_shape::SharedShape, FunctionBinding, JsFunction, JsObject, JsPrototype, + ObjectData, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyDescriptor, PropertyKey}, string::utf16, @@ -187,8 +188,8 @@ impl Intrinsics { /// Abstract operation [`CreateIntrinsics ( realmRec )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createintrinsics - pub(crate) fn new() -> Self { - let intrinsics = Self::default(); + pub(crate) fn populate(root_shape: &SharedShape) -> Self { + let intrinsics = Self::new(root_shape); BuiltInFunctionObject::init(&intrinsics); BuiltInObjectObject::init(&intrinsics); diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index 33bcac61889..ce5a636feef 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -121,7 +121,7 @@ impl BuiltInConstructor for Number { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::number, context)?; let this = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::number(data), ); diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 836a2b33630..90050cfba2c 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -136,7 +136,7 @@ impl BuiltInConstructor for Object { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::object, context)?; let object = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::ordinary(), ); diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 275e4cdbfc3..30fd1d1feb2 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -384,7 +384,7 @@ impl BuiltInConstructor for Promise { get_prototype_from_constructor(new_target, StandardConstructors::promise, context)?; let promise = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), promise, // 4. Set promise.[[PromiseState]] to pending. // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index da81208ec5c..f9eda8c930d 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -128,7 +128,7 @@ impl Proxy { // 6. Set P.[[ProxyTarget]] to target. // 7. Set P.[[ProxyHandler]] to handler. let p = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), context.intrinsics().constructors().object().prototype(), ObjectData::proxy( Self::new(target.clone(), handler.clone()), diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 9411dbce68e..813269f75ae 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -221,7 +221,7 @@ impl RegExp { let proto = get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?; let obj = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), proto, ObjectData::ordinary(), ); diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 7a6b0376b85..79497e61b6a 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -124,7 +124,7 @@ impl BuiltInConstructor for Set { let prototype = get_prototype_from_constructor(new_target, StandardConstructors::set, context)?; let set = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::set(OrderedSet::default()), ); diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index b23c8a6953f..68fe17c045a 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -81,7 +81,7 @@ impl SetIterator { context: &Context<'_>, ) -> JsValue { let set_iterator = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), context.intrinsics().objects().iterator_prototypes().set(), ObjectData::set_iterator(Self::new(set, kind, lock)), ); diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index b29c94ad3bf..d21cfee0bc4 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -186,7 +186,7 @@ impl String { // 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2. // 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3. let s = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::string(value), ); diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 75fdadee868..acd242d5736 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -3184,7 +3184,7 @@ impl TypedArray { // 2. Let obj be ! IntegerIndexedObjectCreate(proto). let obj = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), proto, ObjectData::integer_indexed(indexed), ); diff --git a/boa_engine/src/builtins/weak_map/mod.rs b/boa_engine/src/builtins/weak_map/mod.rs index c6285aae7e0..950be2fea66 100644 --- a/boa_engine/src/builtins/weak_map/mod.rs +++ b/boa_engine/src/builtins/weak_map/mod.rs @@ -82,7 +82,7 @@ impl BuiltInConstructor for WeakMap { // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »). // 3. Set map.[[WeakMapData]] to a new empty List. let map = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), get_prototype_from_constructor(new_target, StandardConstructors::weak_map, context)?, ObjectData::weak_map(boa_gc::WeakMap::new()), ); diff --git a/boa_engine/src/builtins/weak_set/mod.rs b/boa_engine/src/builtins/weak_set/mod.rs index 328e8e2ec73..a851b9ed94e 100644 --- a/boa_engine/src/builtins/weak_set/mod.rs +++ b/boa_engine/src/builtins/weak_set/mod.rs @@ -78,7 +78,7 @@ impl BuiltInConstructor for WeakSet { // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakSet.prototype%", « [[WeakSetData]] »). // 3. Set set.[[WeakSetData]] to a new empty List. let weak_set = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), get_prototype_from_constructor(new_target, StandardConstructors::weak_set, context)?, ObjectData::weak_set(WeakMap::new()), ); diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index c5a00d53fba..15e5b4cb126 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -2,19 +2,37 @@ use crate::{ builtins::{iterable::IteratorPrototypes, uri::UriFunctions}, - object::{JsFunction, JsObject, ObjectData}, + object::{ + shape::shared_shape::{template::ObjectTemplate, SharedShape}, + JsFunction, JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE, + }, + property::{Attribute, PropertyKey}, + JsSymbol, }; /// The intrinsic objects and constructors. -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Intrinsics { /// Cached standard constructors pub(super) constructors: StandardConstructors, /// Cached intrinsic objects pub(super) objects: IntrinsicObjects, + + pub(super) templates: ObjectTemplates, } impl Intrinsics { + pub(crate) fn new(root_shape: &SharedShape) -> Self { + let constructors = StandardConstructors::default(); + let templates = ObjectTemplates::new(root_shape, &constructors); + + Self { + constructors, + objects: IntrinsicObjects::default(), + templates, + } + } + /// Return the cached intrinsic objects. #[inline] pub const fn objects(&self) -> &IntrinsicObjects { @@ -26,6 +44,10 @@ impl Intrinsics { pub const fn constructors(&self) -> &StandardConstructors { &self.constructors } + + pub(crate) const fn templates(&self) -> &ObjectTemplates { + &self.templates + } } /// Store a builtin constructor (such as `Object`) and its corresponding prototype. @@ -928,3 +950,189 @@ impl IntrinsicObjects { self.intl.clone() } } + +#[derive(Debug)] +pub(crate) struct ObjectTemplates { + iterator_result: ObjectTemplate, + ordinary_object: ObjectTemplate, + array: ObjectTemplate, + number: ObjectTemplate, + string: ObjectTemplate, + symbol: ObjectTemplate, + bigint: ObjectTemplate, + boolean: ObjectTemplate, + + unmapped_arguments: ObjectTemplate, + mapped_arguments: ObjectTemplate, + + function_with_prototype: ObjectTemplate, + function_prototype: ObjectTemplate, + + function: ObjectTemplate, + async_function: ObjectTemplate, +} + +impl ObjectTemplates { + pub(crate) fn new(root_shape: &SharedShape, constructors: &StandardConstructors) -> Self { + // pre-initialize used shapes. + let ordinary_object = ObjectTemplate::new(root_shape, constructors.object().prototype()); + let mut array = ObjectTemplate::new(root_shape, constructors.array().prototype()); + + let length_property_key: PropertyKey = "length".into(); + array.data_property( + length_property_key.clone(), + Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, + ); + + let number = ObjectTemplate::new(root_shape, constructors.number().prototype()); + let symbol = ObjectTemplate::new(root_shape, constructors.symbol().prototype()); + let bigint = ObjectTemplate::new(root_shape, constructors.bigint().prototype()); + let boolean = ObjectTemplate::new(root_shape, constructors.boolean().prototype()); + let mut string = ObjectTemplate::new(root_shape, constructors.string().prototype()); + string.data_property( + length_property_key.clone(), + Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, + ); + + // TODO: maybe construct it name, length, then prototype to create the least amount of branching. + let name_property_key: PropertyKey = "name".into(); + let mut function = ObjectTemplate::new(root_shape, constructors.function().prototype()); + function.data_property( + name_property_key.clone(), + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + function.data_property( + length_property_key.clone(), + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + + let mut async_function = + ObjectTemplate::new(root_shape, constructors.async_function().prototype()); + async_function.data_property( + name_property_key, + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + async_function.data_property( + length_property_key.clone(), + Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + + let mut function_with_prototype = function.clone(); + function_with_prototype.data_property( + PROTOTYPE.into(), + Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, + ); + + let mut function_prototype = ordinary_object.clone(); + function_prototype.data_property( + CONSTRUCTOR.into(), + Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ); + + let mut unmapped_arguments = ordinary_object.clone(); + + // // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), + // // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + unmapped_arguments.data_property( + length_property_key, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + + // // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { + // // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, + // // [[Configurable]]: true }). + unmapped_arguments.data_property( + JsSymbol::iterator().into(), + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + + let mut mapped_arguments = unmapped_arguments.clone(); + + // // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, + // // [[Configurable]]: false }). + unmapped_arguments.accessor_property( + "callee".into(), + true, + true, + Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ); + + // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { + // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + mapped_arguments.data_property( + "callee".into(), + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ); + + let mut iterator_result = ordinary_object.clone(); + iterator_result.data_property( + "value".into(), + Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, + ); + iterator_result.data_property( + "done".into(), + Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, + ); + + Self { + iterator_result, + ordinary_object, + array, + number, + string, + symbol, + bigint, + boolean, + unmapped_arguments, + mapped_arguments, + function_with_prototype, + function_prototype, + function, + async_function, + } + } + + pub(crate) const fn iterator_result(&self) -> &ObjectTemplate { + &self.iterator_result + } + pub(crate) const fn ordinary_object(&self) -> &ObjectTemplate { + &self.ordinary_object + } + pub(crate) const fn array(&self) -> &ObjectTemplate { + &self.array + } + pub(crate) const fn number(&self) -> &ObjectTemplate { + &self.number + } + pub(crate) const fn string(&self) -> &ObjectTemplate { + &self.string + } + pub(crate) const fn symbol(&self) -> &ObjectTemplate { + &self.symbol + } + pub(crate) const fn bigint(&self) -> &ObjectTemplate { + &self.bigint + } + pub(crate) const fn boolean(&self) -> &ObjectTemplate { + &self.boolean + } + pub(crate) const fn unmapped_arguments(&self) -> &ObjectTemplate { + &self.unmapped_arguments + } + pub(crate) const fn mapped_arguments(&self) -> &ObjectTemplate { + &self.mapped_arguments + } + pub(crate) const fn function_with_prototype(&self) -> &ObjectTemplate { + &self.function_with_prototype + } + pub(crate) const fn function_prototype(&self) -> &ObjectTemplate { + &self.function_prototype + } + pub(crate) const fn function(&self) -> &ObjectTemplate { + &self.function + } + pub(crate) const fn async_function(&self) -> &ObjectTemplate { + &self.async_function + } +} diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 8a1006592a4..6e46a24f84e 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -23,7 +23,7 @@ use crate::{ job::{IdleJobQueue, JobQueue, NativeJob}, native_function::NativeFunction, object::PropertyMap, - object::{FunctionObjectBuilder, JsObject}, + object::{shape::shared_shape::SharedShape, FunctionObjectBuilder, JsObject}, optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -105,6 +105,7 @@ pub struct Context<'host> { job_queue: &'host dyn JobQueue, optimizer_options: OptimizerOptions, + root_shape: SharedShape, } impl std::fmt::Debug for Context<'_> { @@ -482,6 +483,10 @@ impl Context<'_> { pub fn clear_kept_objects(&mut self) { self.kept_alive.clear(); } + + pub(crate) fn root_shape(&self) -> SharedShape { + self.root_shape.clone() + } } // ==== Private API ==== @@ -656,8 +661,10 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> { { let host_hooks = self.host_hooks.unwrap_or(&DefaultHooks); + let root_shape = SharedShape::root(); + let mut context = Context { - realm: Realm::create(host_hooks), + realm: Realm::create(host_hooks, &root_shape), interner: self.interner.unwrap_or_default(), vm: Vm::default(), strict: false, @@ -672,6 +679,7 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> { host_hooks, job_queue: self.job_queue.unwrap_or(&IdleJobQueue), optimizer_options: OptimizerOptions::OPTIMIZE_ALL, + root_shape, }; builtins::set_default_global_bindings(&mut context)?; diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 8e3d8c3ae56..f754102938f 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -670,7 +670,7 @@ impl JsNativeError { }; let o = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::error(tag), ); diff --git a/boa_engine/src/object/builtins/jsdataview.rs b/boa_engine/src/object/builtins/jsdataview.rs index 0bf39867dfc..14472baf4d7 100644 --- a/boa_engine/src/object/builtins/jsdataview.rs +++ b/boa_engine/src/object/builtins/jsdataview.rs @@ -97,7 +97,7 @@ impl JsDataView { get_prototype_from_constructor(&constructor, StandardConstructors::data_view, context)?; let obj = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::data_view(DataView { viewed_array_buffer: (**array_buffer).clone(), diff --git a/boa_engine/src/object/builtins/jsdate.rs b/boa_engine/src/object/builtins/jsdate.rs index 3792a2980df..77ef87d8b1a 100644 --- a/boa_engine/src/object/builtins/jsdate.rs +++ b/boa_engine/src/object/builtins/jsdate.rs @@ -43,7 +43,7 @@ impl JsDate { pub fn new(context: &mut Context<'_>) -> Self { let prototype = context.intrinsics().constructors().date().prototype(); let inner = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::date(Date::default()), ); @@ -563,7 +563,7 @@ impl JsDate { Ok(Self { inner: JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::date(date_time), ), diff --git a/boa_engine/src/object/builtins/jsgenerator.rs b/boa_engine/src/object/builtins/jsgenerator.rs index feef3ab017b..a60b329d14b 100644 --- a/boa_engine/src/object/builtins/jsgenerator.rs +++ b/boa_engine/src/object/builtins/jsgenerator.rs @@ -21,7 +21,7 @@ impl JsGenerator { let prototype = context.intrinsics().objects().generator(); let generator = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::generator(Generator { state: GeneratorState::Undefined, diff --git a/boa_engine/src/object/builtins/jsmap.rs b/boa_engine/src/object/builtins/jsmap.rs index 3168d2c843e..ee99cbe0067 100644 --- a/boa_engine/src/object/builtins/jsmap.rs +++ b/boa_engine/src/object/builtins/jsmap.rs @@ -185,7 +185,7 @@ impl JsMap { // Create a default map object with [[MapData]] as a new empty list JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::map(OrderedMap::new()), ) diff --git a/boa_engine/src/object/builtins/jspromise.rs b/boa_engine/src/object/builtins/jspromise.rs index 91ca587011c..36cb800e1e5 100644 --- a/boa_engine/src/object/builtins/jspromise.rs +++ b/boa_engine/src/object/builtins/jspromise.rs @@ -205,7 +205,7 @@ impl JsPromise { #[inline] pub fn new_pending(context: &mut Context<'_>) -> (JsPromise, ResolvingFunctions) { let promise = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), context.intrinsics().constructors().promise().prototype(), ObjectData::promise(Promise::new()), ); diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index 78c105ec1cc..295f1e0876f 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -494,7 +494,7 @@ impl JsProxyBuilder { let constructor = self.target.is_constructor(); let proxy = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), context.intrinsics().constructors().object().prototype(), ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor), ); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 7426a3153d0..46b2b3d3cac 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -72,7 +72,7 @@ pub mod builtins; mod jsobject; mod operations; mod property_map; -pub(crate) mod shape; +pub mod shape; pub(crate) use builtins::*; @@ -2018,7 +2018,7 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> { /// Build the function object. pub fn build(self) -> JsFunction { - let object = self.context.realm.function_object_template.create( + let object = self.context.intrinsics().templates().function().create( ObjectData::function(self.function), vec![self.name.into(), self.length.into()], ); @@ -2072,7 +2072,7 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> { /// Create a new `ObjectBuilder` with custom [`NativeObject`] data. pub fn with_native(data: T, context: &'ctx mut Context<'host>) -> Self { let object = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), context.intrinsics().constructors().object().prototype(), ObjectData::native_object(data), ); diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index 199dfd2cc8f..11877636696 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -67,6 +67,10 @@ impl Default for IndexedProperties { } impl IndexedProperties { + fn new(elements: ThinVec) -> Self { + Self::Dense(elements) + } + /// Get a property descriptor if it exists. fn get(&self, key: u32) -> Option { match self { @@ -226,9 +230,9 @@ impl PropertyMap { /// Create a new [`PropertyMap`]. #[must_use] #[inline] - pub fn new(shape: Shape) -> Self { + pub fn new(shape: Shape, elements: ThinVec) -> Self { Self { - indexed_properties: IndexedProperties::default(), + indexed_properties: IndexedProperties::new(elements), shape, storage: Vec::default(), } diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index bd2e4449afd..8fd8f642a68 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -1,8 +1,11 @@ +//! Implements object shapes. + pub(crate) mod property_table; pub(crate) mod shared_shape; pub(crate) mod slot; mod unique_shape; +pub use shared_shape::SharedShape; pub(crate) use unique_shape::UniqueShape; use std::fmt::Debug; @@ -11,10 +14,7 @@ use boa_gc::{Finalize, Trace}; use crate::property::PropertyKey; -use self::{ - shared_shape::{SharedShape, TransitionKey}, - slot::Slot, -}; +use self::{shared_shape::TransitionKey, slot::Slot}; use super::JsPrototype; @@ -56,6 +56,7 @@ enum Inner { Shared(SharedShape), } +/// Represents the shape of an object. #[derive(Debug, Trace, Finalize, Clone)] pub struct Shape { inner: Inner, @@ -171,6 +172,7 @@ impl Shape { } } + /// Lookup a property in the shape #[inline] pub fn lookup(&self, key: &PropertyKey) -> Option { match &self.inner { diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index e1374dad810..b18d058f2ba 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -6,7 +6,7 @@ use std::{collections::hash_map::RandomState, hash::Hash}; use boa_gc::{empty_trace, Finalize, Gc, Trace}; use indexmap::IndexMap; -use crate::{object::JsPrototype, property::PropertyKey}; +use crate::{object::JsPrototype, property::PropertyKey, JsObject}; use self::forward_transition::ForwardTransition; @@ -75,9 +75,9 @@ struct Inner { transition_count: u16, } -/// +/// Represents a shared object shape. #[derive(Debug, Trace, Finalize, Clone)] -pub(crate) struct SharedShape { +pub struct SharedShape { inner: Gc, } @@ -94,15 +94,15 @@ impl SharedShape { self.inner.property_count.saturating_sub(1) } /// Getter for the transition count field. - pub(crate) fn transition_count(&self) -> u16 { + pub fn transition_count(&self) -> u16 { self.inner.transition_count } /// Getter for the previous field. - pub(crate) fn previous(&self) -> Option<&SharedShape> { + pub fn previous(&self) -> Option<&SharedShape> { self.inner.previous.as_ref() } /// Get the prototype of the shape. - pub(crate) fn prototype(&self) -> JsPrototype { + pub fn prototype(&self) -> JsPrototype { self.inner.prototype.clone() } /// Get the property this [`SharedShape`] referes to. @@ -118,11 +118,17 @@ impl SharedShape { fn transition_type(&self) -> TransitionType { self.inner.transition_type } - /// Getter for the [`ForwardTransition`] field. fn forward_transitions(&self) -> &ForwardTransition { &self.inner.forward_transitions } + /// Check if the prototype has the given prototype. + pub fn has_prototype(&self, prototype: &JsObject) -> bool { + self.inner + .prototype + .as_ref() + .map_or(false, |this| this == prototype) + } /// Create a new [`SharedShape`]. fn new(inner: Inner) -> Self { @@ -132,7 +138,8 @@ impl SharedShape { } /// Create a root [`SharedShape`]. - pub(crate) fn root() -> Self { + #[must_use] + pub fn root() -> Self { Self::new(Inner { forward_transitions: ForwardTransition::default(), prototype: None, diff --git a/boa_engine/src/object/shape/shared_shape/template.rs b/boa_engine/src/object/shape/shared_shape/template.rs index 1d0128db8cd..f3229c6849d 100644 --- a/boa_engine/src/object/shape/shared_shape/template.rs +++ b/boa_engine/src/object/shape/shared_shape/template.rs @@ -4,7 +4,7 @@ use thin_vec::ThinVec; use crate::{ object::{ shape::{slot::SlotAttribute, Shape}, - JsObject, JsPrototype, Object, ObjectData, PropertyMap, + JsObject, Object, ObjectData, PropertyMap, }, property::{Attribute, PropertyKey}, JsValue, @@ -27,37 +27,20 @@ impl ObjectTemplate { } } - pub(crate) fn prototype(&self) -> JsPrototype { - self.shape.prototype() + pub(crate) fn has_prototype(&self, prototype: &JsObject) -> bool { + self.shape.has_prototype(prototype) } pub(crate) fn data_property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { // TOOD: We don't support indexed keys. debug_assert!(!matches!(&key, PropertyKey::Index(_))); - let attributes = { - let mut result = SlotAttribute::empty(); - result.set( - SlotAttribute::WRITABLE, - attributes.contains(Attribute::WRITABLE), - ); - result.set( - SlotAttribute::CONFIGURABLE, - attributes.contains(Attribute::CONFIGURABLE), - ); - result.set( - SlotAttribute::ENUMERABLE, - attributes.contains(Attribute::ENUMERABLE), - ); - result - }; - + let attributes = SlotAttribute::from_bits_truncate(attributes.bits()); self.shape = self.shape.insert_property_transition(TransitionKey { property_key: key, attributes, }); self.storage_len += 1; - self } @@ -93,7 +76,6 @@ impl ObjectTemplate { attributes, }); self.storage_len += 2; - self } @@ -101,19 +83,18 @@ impl ObjectTemplate { &self, data: ObjectData, storage: Vec, - indexed: ThinVec, + elements: ThinVec, ) -> JsObject { let mut object = Object { data, extensible: true, - properties: PropertyMap::new(Shape::shared(self.shape.clone())), + properties: PropertyMap::new(Shape::shared(self.shape.clone()), elements), private_elements: ThinVec::new(), }; debug_assert_eq!(self.storage_len, storage.len()); object.properties.storage = storage; - object.properties.override_indexed_properties(indexed); Gc::new(GcRefCell::new(object)).into() } @@ -122,7 +103,7 @@ impl ObjectTemplate { let mut object = Object { data, extensible: true, - properties: PropertyMap::new(Shape::shared(self.shape.clone())), + properties: PropertyMap::new(Shape::shared(self.shape.clone()), ThinVec::default()), private_elements: ThinVec::new(), }; diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index d24cdee8ef6..dbffa97ff24 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -9,12 +9,7 @@ use crate::{ context::{intrinsics::Intrinsics, HostHooks}, environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack}, - object::{ - shape::shared_shape::{template::ObjectTemplate, SharedShape}, - JsObject, PropertyMap, CONSTRUCTOR, PROTOTYPE, - }, - property::{Attribute, PropertyKey}, - JsSymbol, + object::{shape::shared_shape::SharedShape, JsObject, PropertyMap}, }; use boa_gc::{Gc, GcRefCell}; use boa_profiler::Profiler; @@ -30,34 +25,15 @@ pub struct Realm { pub(crate) compile_env: Gc>, global_object: JsObject, global_this: JsObject, - - // TODO: probably refactor into struct - pub(crate) root_shape: SharedShape, - pub(crate) iterator_result_object_template: ObjectTemplate, - pub(crate) empty_object_template: ObjectTemplate, - pub(crate) array_object_template: ObjectTemplate, - pub(crate) number_object_template: ObjectTemplate, - pub(crate) string_object_template: ObjectTemplate, - pub(crate) symbol_object_template: ObjectTemplate, - pub(crate) bigint_object_template: ObjectTemplate, - pub(crate) boolean_object_template: ObjectTemplate, - - pub(crate) unmapped_arguments_object_templete: ObjectTemplate, - pub(crate) mapped_arguments_object_templete: ObjectTemplate, - pub(crate) function_with_prototype_object_template: ObjectTemplate, - pub(crate) function_prototype_object_template: ObjectTemplate, - - pub(crate) function_object_template: ObjectTemplate, - pub(crate) async_function_object_template: ObjectTemplate, } impl Realm { /// Create a new Realm. #[inline] - pub fn create(hooks: &dyn HostHooks) -> Self { + pub fn create(hooks: &dyn HostHooks, root_shape: &SharedShape) -> Self { let _timer = Profiler::global().start_event("Realm::create", "realm"); - let intrinsics = Intrinsics::new(); + let intrinsics = Intrinsics::populate(root_shape); let global_object = hooks.create_global_object(&intrinsics); let global_this = hooks @@ -67,121 +43,6 @@ impl Realm { let global_compile_environment = Gc::new(GcRefCell::new(CompileTimeEnvironment::new_global())); - // pre-initialize used shapes. - let root_shape = SharedShape::root(); - let empty_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().object().prototype()); - let mut array_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().array().prototype()); - - let length_property_key: PropertyKey = "length".into(); - array_object_template.data_property( - length_property_key.clone(), - Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, - ); - - let number_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().number().prototype()); - let symbol_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().symbol().prototype()); - let bigint_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().bigint().prototype()); - let boolean_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().boolean().prototype()); - let mut string_object_template = - ObjectTemplate::new(&root_shape, intrinsics.constructors().string().prototype()); - string_object_template.data_property( - length_property_key.clone(), - Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, - ); - - // TODO: put in a better place! - // TODO: maybe construct it name, length, then prototype to create the least amount of branching. - let name_property_key: PropertyKey = "name".into(); - let mut function_object_template = ObjectTemplate::new( - &root_shape, - intrinsics.constructors().function().prototype(), - ); - function_object_template.data_property( - name_property_key.clone(), - Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - function_object_template.data_property( - length_property_key.clone(), - Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - - let mut async_function_object_template = ObjectTemplate::new( - &root_shape, - intrinsics.constructors().async_function().prototype(), - ); - async_function_object_template.data_property( - name_property_key, - Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - async_function_object_template.data_property( - length_property_key.clone(), - Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - - let mut function_with_prototype_object_template = function_object_template.clone(); - function_with_prototype_object_template.data_property( - PROTOTYPE.into(), - Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, - ); - - let mut function_prototype_object_template = empty_object_template.clone(); - function_prototype_object_template.data_property( - CONSTRUCTOR.into(), - Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - - let mut unmapped_arguments_object_templete = empty_object_template.clone(); - - // // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), - // // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - unmapped_arguments_object_templete.data_property( - length_property_key, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); - - // // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { - // // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, - // // [[Configurable]]: true }). - unmapped_arguments_object_templete.data_property( - JsSymbol::iterator().into(), - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); - - let mut mapped_arguments_object_templete = unmapped_arguments_object_templete.clone(); - - // // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { - // // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, - // // [[Configurable]]: false }). - unmapped_arguments_object_templete.accessor_property( - "callee".into(), - true, - true, - Attribute::NON_ENUMERABLE | Attribute::PERMANENT, - ); - - // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { - // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - mapped_arguments_object_templete.data_property( - "callee".into(), - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); - - let mut iterator_result_object_template = empty_object_template.clone(); - iterator_result_object_template.data_property( - "value".into(), - Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, - ); - iterator_result_object_template.data_property( - "done".into(), - Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, - ); - #[allow(unreachable_code)] Self { intrinsics, @@ -190,21 +51,6 @@ impl Realm { global_property_map: PropertyMap::default(), environments: DeclarativeEnvironmentStack::new(global_compile_environment.clone()), compile_env: global_compile_environment, - root_shape, - empty_object_template, - iterator_result_object_template, - unmapped_arguments_object_templete, - mapped_arguments_object_templete, - array_object_template, - function_with_prototype_object_template, - async_function_object_template, - function_object_template, - function_prototype_object_template, - bigint_object_template, - boolean_object_template, - number_object_template, - string_object_template, - symbol_object_template, } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index a89c819e939..f302f466b4a 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -472,28 +472,33 @@ impl JsValue { .with_message("cannot convert 'null' or 'undefined' to object") .into()), Self::Boolean(boolean) => Ok(context - .realm - .boolean_object_template + .intrinsics() + .templates() + .boolean() .create(ObjectData::boolean(*boolean), Vec::default())), Self::Integer(integer) => Ok(context - .realm - .number_object_template + .intrinsics() + .templates() + .number() .create(ObjectData::number(f64::from(*integer)), Vec::default())), Self::Rational(rational) => Ok(context - .realm - .number_object_template + .intrinsics() + .templates() + .number() .create(ObjectData::number(*rational), Vec::default())), - Self::String(ref string) => Ok(context.realm.string_object_template.create( + Self::String(ref string) => Ok(context.intrinsics().templates().string().create( ObjectData::string(string.clone()), vec![string.len().into()], )), Self::Symbol(ref symbol) => Ok(context - .realm - .symbol_object_template + .intrinsics() + .templates() + .symbol() .create(ObjectData::symbol(symbol.clone()), Vec::default())), Self::BigInt(ref bigint) => Ok(context - .realm - .bigint_object_template + .intrinsics() + .templates() + .bigint() .create(ObjectData::big_int(bigint.clone()), Vec::default())), Self::Object(jsobject) => Ok(jsobject.clone()), } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index c3015e65d6c..948643ce47d 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -698,23 +698,27 @@ pub(crate) fn create_function_object_opcode( if r#async { context - .realm - .async_function_object_template + .intrinsics() + .templates() + .async_function() .create(data, vec![name, length]) } else if arrow || method { context - .realm - .function_object_template + .intrinsics() + .templates() + .function() .create(data, vec![name, length]) } else { let prototype = context - .realm - .function_prototype_object_template + .intrinsics() + .templates() + .function_prototype() .create(ObjectData::ordinary(), vec![JsValue::undefined()]); let constructor = context - .realm - .function_with_prototype_object_template + .intrinsics() + .templates() + .function_with_prototype() .create(data, vec![name, length, prototype.clone().into()]); prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); diff --git a/boa_engine/src/vm/opcode/push/array.rs b/boa_engine/src/vm/opcode/push/array.rs index 0673d9a2fce..db5b40334b1 100644 --- a/boa_engine/src/vm/opcode/push/array.rs +++ b/boa_engine/src/vm/opcode/push/array.rs @@ -19,8 +19,9 @@ impl Operation for PushNewArray { fn execute(context: &mut Context<'_>) -> JsResult { let array = context - .realm - .array_object_template + .intrinsics() + .templates() + .array() .create(ObjectData::array(), vec![JsValue::new(0)]); context.vm.push(array); Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/push/object.rs b/boa_engine/src/vm/opcode/push/object.rs index 52560853d7b..46089038eda 100644 --- a/boa_engine/src/vm/opcode/push/object.rs +++ b/boa_engine/src/vm/opcode/push/object.rs @@ -17,8 +17,9 @@ impl Operation for PushEmptyObject { fn execute(context: &mut Context<'_>) -> JsResult { let o = context - .realm - .empty_object_template + .intrinsics() + .templates() + .ordinary_object() .create(ObjectData::ordinary(), Vec::default()); context.vm.push(o); Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index d3e76273d22..9327e757d3b 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -33,7 +33,7 @@ impl Operation for SetClassPrototype { }; let proto = JsObject::from_proto_and_data_with_shared_shape( - context.realm.root_shape.clone(), + context.root_shape(), prototype, ObjectData::ordinary(), ); From 9971f0bb555dfb2538f2d285cbcc6a8337424804 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 16:23:57 +0200 Subject: [PATCH 07/20] Apply shapes to callable builtins --- boa_engine/src/builtins/mod.rs | 55 +++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 2974bc039d0..9877c3443ac 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -533,21 +533,56 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { } } +struct BuiltInCallable<'ctx> { + intrinsics: &'ctx Intrinsics, + function: NativeFunctionPointer, + name: JsString, + length: usize, +} + +impl BuiltInCallable<'_> { + /// Specify how many arguments the constructor function takes. + /// + /// Default is `0`. + #[inline] + const fn length(mut self, length: usize) -> Self { + self.length = length; + self + } + + /// Specify the name of the constructor function. + /// + /// Default is `""` + fn name>(mut self, name: N) -> Self { + self.name = name.into(); + self + } + + fn build(self) -> JsFunction { + let function = function::Function::Native { + function: NativeFunction::from_fn_ptr(self.function), + constructor: None, + }; + + let object = self.intrinsics.templates().function().create( + ObjectData::function(function), + vec![JsValue::new(self.name), JsValue::new(self.length)], + ); + + JsFunction::from_object_unchecked(object) + } +} + impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { fn callable( intrinsics: &'ctx Intrinsics, function: NativeFunctionPointer, - ) -> BuiltInBuilder<'ctx, Callable> { - BuiltInBuilder { + ) -> BuiltInCallable<'ctx> { + BuiltInCallable { intrinsics, - object: JsObject::with_null_proto(), - kind: Callable { - function, - name: js_string!(""), - length: 0, - kind: OrdinaryFunction, - }, - prototype: intrinsics.constructors().function().prototype(), + function, + length: 0, + name: js_string!(""), } } From c87a7a70e3250522264aab6db8b03f0ba1f1f4d5 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 31 Mar 2023 16:38:58 +0200 Subject: [PATCH 08/20] Fix UniqueShape change attribute --- boa_engine/src/object/shape/mod.rs | 1 - boa_engine/src/object/shape/unique_shape.rs | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 8fd8f642a68..4631297d5e9 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -62,7 +62,6 @@ pub struct Shape { inner: Inner, } -/// The default [`Shape`] type is [`UniqueShape`]. impl Default for Shape { fn default() -> Self { Shape::unique(UniqueShape::default()) diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index 112803b9a32..ddf09cfee43 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -138,15 +138,19 @@ impl UniqueShape { // If property does not change type, there is no need to shift. if slot.attributes.width_match(key.attributes) { slot.attributes = key.attributes; + property_table.keys[index].1.attributes = key.attributes; // TODO: invalidate the pointer. return ChangeTransition { shape: Shape::unique(self.clone()), action: ChangeTransitionAction::Nothing, }; } - slot.attributes = key.attributes; + let mut previous_slot = *slot; + + property_table.keys[index].1.attributes = key.attributes; + let action = if key.attributes.is_accessor_descriptor() { // Data --> Accessor ChangeTransitionAction::Insert @@ -155,8 +159,6 @@ impl UniqueShape { ChangeTransitionAction::Remove }; - let mut previous_slot = *slot; - // The property that was deleted was not the last property added. // Therefore we need to create a new unique shape, // to invalidate any pointers to this shape i.e inline caches. From bd5f88a09bfb39168da83715499fb13ce715c450 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Mon, 3 Apr 2023 16:25:47 +0200 Subject: [PATCH 09/20] Fix incorrect property conversion from data to accessor --- boa_engine/src/object/shape/unique_shape.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index ddf09cfee43..ef456e5ba6d 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -191,10 +191,9 @@ impl UniqueShape { for (key, slot) in property_table.keys.iter_mut().skip(next) { *slot = Slot::from_previous(Some(previous_slot), slot.attributes); - let Some((map_index, map_slot)) = property_table.map.get_mut(key) else { + let Some((_, map_slot)) = property_table.map.get_mut(key) else { unreachable!("There should already be a property") }; - *map_index = index as u32; *map_slot = *slot; previous_slot = *slot; From e8e785a6ed1454f6c0099bfe840541d75e5aa613 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Mon, 3 Apr 2023 23:19:31 +0200 Subject: [PATCH 10/20] Initialize builtin constructors directly --- boa_engine/src/builtins/array/mod.rs | 2 +- boa_engine/src/builtins/array_buffer/mod.rs | 2 +- boa_engine/src/builtins/async_function/mod.rs | 2 +- .../builtins/async_generator_function/mod.rs | 2 +- boa_engine/src/builtins/bigint/mod.rs | 2 +- boa_engine/src/builtins/boolean/mod.rs | 2 +- boa_engine/src/builtins/dataview/mod.rs | 2 +- boa_engine/src/builtins/date/mod.rs | 2 +- boa_engine/src/builtins/error/aggregate.rs | 2 +- boa_engine/src/builtins/error/eval.rs | 2 +- boa_engine/src/builtins/error/mod.rs | 2 +- boa_engine/src/builtins/error/range.rs | 2 +- boa_engine/src/builtins/error/reference.rs | 2 +- boa_engine/src/builtins/error/syntax.rs | 2 +- boa_engine/src/builtins/error/type.rs | 2 +- boa_engine/src/builtins/error/uri.rs | 2 +- boa_engine/src/builtins/function/mod.rs | 20 +- .../src/builtins/generator_function/mod.rs | 2 +- boa_engine/src/builtins/intl/collator/mod.rs | 2 +- .../src/builtins/intl/date_time_format.rs | 2 +- .../src/builtins/intl/list_format/mod.rs | 2 +- boa_engine/src/builtins/intl/locale/mod.rs | 2 +- boa_engine/src/builtins/intl/segmenter/mod.rs | 2 +- boa_engine/src/builtins/map/mod.rs | 2 +- boa_engine/src/builtins/mod.rs | 337 +++++++++++++++--- boa_engine/src/builtins/number/mod.rs | 2 +- boa_engine/src/builtins/object/mod.rs | 2 +- boa_engine/src/builtins/promise/mod.rs | 2 +- boa_engine/src/builtins/regexp/mod.rs | 2 +- boa_engine/src/builtins/set/mod.rs | 2 +- boa_engine/src/builtins/string/mod.rs | 2 +- boa_engine/src/builtins/symbol/mod.rs | 2 +- boa_engine/src/builtins/typed_array/mod.rs | 4 +- boa_engine/src/builtins/weak/weak_ref.rs | 2 + boa_engine/src/builtins/weak_map/mod.rs | 2 +- boa_engine/src/builtins/weak_set/mod.rs | 2 +- boa_engine/src/object/shape/mod.rs | 7 + boa_engine/src/object/shape/property_table.rs | 5 + boa_engine/src/object/shape/unique_shape.rs | 9 + 39 files changed, 347 insertions(+), 101 deletions(-) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 30b80d9b563..aa5e2f8fe6e 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -60,7 +60,7 @@ impl IntrinsicObject for Array { let unscopables_object = Self::unscopables_object(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_accessor( JsSymbol::species(), Some(get_species), diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 014d2a50690..e095a3bfc53 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -60,7 +60,7 @@ impl IntrinsicObject for ArrayBuffer { .name("get byteLength") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .accessor( utf16!("byteLength"), Some(get_byte_length), diff --git a/boa_engine/src/builtins/async_function/mod.rs b/boa_engine/src/builtins/async_function/mod.rs index 53b0f0a89a8..1ac0acfd7ca 100644 --- a/boa_engine/src/builtins/async_function/mod.rs +++ b/boa_engine/src/builtins/async_function/mod.rs @@ -26,7 +26,7 @@ impl IntrinsicObject for AsyncFunction { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().function().constructor()) .inherits(Some(intrinsics.constructors().function().prototype())) .property( diff --git a/boa_engine/src/builtins/async_generator_function/mod.rs b/boa_engine/src/builtins/async_generator_function/mod.rs index e4d50e088ac..1e2d649273b 100644 --- a/boa_engine/src/builtins/async_generator_function/mod.rs +++ b/boa_engine/src/builtins/async_generator_function/mod.rs @@ -26,7 +26,7 @@ impl IntrinsicObject for AsyncGeneratorFunction { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .inherits(Some(intrinsics.constructors().function().prototype())) .constructor_attributes(Attribute::CONFIGURABLE) .property( diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index 2d2ac22aec4..4376262df0a 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -38,7 +38,7 @@ impl IntrinsicObject for BigInt { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .method(Self::to_string, "toString", 0) .method(Self::value_of, "valueOf", 0) .static_method(Self::as_int_n, "asIntN", 2) diff --git a/boa_engine/src/builtins/boolean/mod.rs b/boa_engine/src/builtins/boolean/mod.rs index 54f11146c13..b160f09cf4a 100644 --- a/boa_engine/src/builtins/boolean/mod.rs +++ b/boa_engine/src/builtins/boolean/mod.rs @@ -31,7 +31,7 @@ impl IntrinsicObject for Boolean { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .method(Self::to_string, "toString", 0) .method(Self::value_of, "valueOf", 0) .build(); diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index e944e553ec9..199129b9e04 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -46,7 +46,7 @@ impl IntrinsicObject for DataView { .name("get byteOffset") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .accessor(utf16!("buffer"), Some(get_buffer), None, flag_attributes) .accessor( utf16!("byteLength"), diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index 92fdbb483c3..c2230f0c0da 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -103,7 +103,7 @@ impl IntrinsicObject for Date { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .method(Self::get_date::, "getDate", 0) .method(Self::get_day::, "getDay", 0) .method(Self::get_full_year::, "getFullYear", 0) diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index 11c7f5132d7..3c6aee22ce7 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -30,7 +30,7 @@ impl IntrinsicObject for AggregateError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index 9525d5c665b..ca998b71d93 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -32,7 +32,7 @@ impl IntrinsicObject for EvalError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index 254481b1140..7a9247725ea 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -131,7 +131,7 @@ impl IntrinsicObject for Error { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .property(utf16!("name"), Self::NAME, attribute) .property(utf16!("message"), "", attribute) .method(Self::to_string, "toString", 0) diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index a952f3db581..7fe9f90b52e 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -30,7 +30,7 @@ impl IntrinsicObject for RangeError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index 1a08e498cd4..7f1a3042f7c 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -29,7 +29,7 @@ impl IntrinsicObject for ReferenceError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index cbd248824e4..450af40c9fb 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -32,7 +32,7 @@ impl IntrinsicObject for SyntaxError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 0c79cc4d6b3..af53750e915 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -40,7 +40,7 @@ impl IntrinsicObject for TypeError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index 3a34534a940..f1a5a583b7e 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -31,7 +31,7 @@ impl IntrinsicObject for UriError { let _timer = Profiler::global().start_event(Self::NAME, "init"); let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().error().constructor()) .inherits(Some(intrinsics.constructors().error().prototype())) .property(utf16!("name"), Self::NAME, attribute) diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 960ea7328e4..ceed19891b7 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -401,15 +401,6 @@ impl IntrinsicObject for BuiltInFunctionObject { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event("function", "init"); - BuiltInBuilder::callable_with_object( - intrinsics, - intrinsics.constructors().function().prototype(), - Self::prototype, - ) - .name("") - .length(0) - .build(); - let has_instance = BuiltInBuilder::callable(intrinsics, Self::has_instance) .name("[Symbol.iterator]") .length(1) @@ -417,7 +408,7 @@ impl IntrinsicObject for BuiltInFunctionObject { let throw_type_error = intrinsics.objects().throw_type_error(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .method(Self::apply, "apply", 2) .method(Self::bind, "bind", 1) .method(Self::call, "call", 1) @@ -436,6 +427,15 @@ impl IntrinsicObject for BuiltInFunctionObject { Attribute::CONFIGURABLE, ) .build(); + + let prototype = intrinsics.constructors().function().prototype(); + + BuiltInBuilder::callable_with_object(intrinsics, prototype.clone(), Self::prototype) + .name("") + .length(0) + .build(); + + prototype.set_prototype(Some(intrinsics.constructors().object().prototype())); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/generator_function/mod.rs b/boa_engine/src/builtins/generator_function/mod.rs index 15d48636b3b..3c18c544038 100644 --- a/boa_engine/src/builtins/generator_function/mod.rs +++ b/boa_engine/src/builtins/generator_function/mod.rs @@ -31,7 +31,7 @@ impl IntrinsicObject for GeneratorFunction { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .inherits(Some(intrinsics.constructors().function().prototype())) .constructor_attributes(Attribute::CONFIGURABLE) .property( diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index d5ea6ee894b..7c70ff8b3d9 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -166,7 +166,7 @@ impl IntrinsicObject for Collator { .name("get compare") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_method(Self::supported_locales_of, "supportedLocalesOf", 1) .property( JsSymbol::to_string_tag(), diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index ca38623e9ac..3f2552709e2 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -65,7 +65,7 @@ impl IntrinsicObject for DateTimeFormat { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics).build(); + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics).build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/intl/list_format/mod.rs b/boa_engine/src/builtins/intl/list_format/mod.rs index af3575c49ee..23dbd9a5bcb 100644 --- a/boa_engine/src/builtins/intl/list_format/mod.rs +++ b/boa_engine/src/builtins/intl/list_format/mod.rs @@ -51,7 +51,7 @@ impl IntrinsicObject for ListFormat { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_method(Self::supported_locales_of, "supportedLocalesOf", 1) .property( JsSymbol::to_string_tag(), diff --git a/boa_engine/src/builtins/intl/locale/mod.rs b/boa_engine/src/builtins/intl/locale/mod.rs index 3a80f1c7fe1..6b3c92f2824 100644 --- a/boa_engine/src/builtins/intl/locale/mod.rs +++ b/boa_engine/src/builtins/intl/locale/mod.rs @@ -75,7 +75,7 @@ impl IntrinsicObject for Locale { .name("get region") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .property( JsSymbol::to_string_tag(), "Intl.Locale", diff --git a/boa_engine/src/builtins/intl/segmenter/mod.rs b/boa_engine/src/builtins/intl/segmenter/mod.rs index 6684bc4d9f8..6b74ce11f94 100644 --- a/boa_engine/src/builtins/intl/segmenter/mod.rs +++ b/boa_engine/src/builtins/intl/segmenter/mod.rs @@ -20,7 +20,7 @@ impl IntrinsicObject for Segmenter { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics).build(); + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics).build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 0b220b8bb6f..18822c27599 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -52,7 +52,7 @@ impl IntrinsicObject for Map { .name("entries") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_accessor( JsSymbol::species(), Some(get_species), diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 9877c3443ac..cf67485125c 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -97,8 +97,10 @@ use crate::{ js_string, native_function::{NativeFunction, NativeFunctionPointer}, object::{ - shape::shared_shape::SharedShape, FunctionBinding, JsFunction, JsObject, JsPrototype, - ObjectData, CONSTRUCTOR, PROTOTYPE, + shape::{ + property_table::PropertyTableInner, shared_shape::SharedShape, slot::SlotAttribute, + }, + FunctionBinding, JsFunction, JsObject, JsPrototype, ObjectData, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyDescriptor, PropertyKey}, string::utf16, @@ -533,6 +535,261 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { } } +struct BuiltInConstructorWithPrototype<'ctx> { + intrinsics: &'ctx Intrinsics, + function: NativeFunctionPointer, + name: JsString, + length: usize, + + object_property_table: PropertyTableInner, + object_storage: Vec, + object: JsObject, + + prototype_property_table: PropertyTableInner, + prototype_storage: Vec, + prototype: JsObject, + __proto__: JsPrototype, + inherits: Option, + attributes: Attribute, +} + +#[allow(dead_code)] +impl BuiltInConstructorWithPrototype<'_> { + /// Specify how many arguments the constructor function takes. + /// + /// Default is `0`. + #[inline] + const fn length(mut self, length: usize) -> Self { + self.length = length; + self + } + + /// Specify the name of the constructor function. + /// + /// Default is `""` + fn name>(mut self, name: N) -> Self { + self.name = name.into(); + self + } + + /// Adds a new static method to the builtin object. + fn static_method( + mut self, + function: NativeFunctionPointer, + binding: B, + length: usize, + ) -> Self + where + B: Into, + { + let binding = binding.into(); + let function = BuiltInBuilder::callable(self.intrinsics, function) + .name(binding.name) + .length(length) + .build(); + + debug_assert!(self + .object_property_table + .map + .get(&binding.binding) + .is_none()); + self.object_property_table.insert( + binding.binding, + SlotAttribute::WRITABLE | SlotAttribute::CONFIGURABLE, + ); + self.object_storage.push(function.into()); + self + } + + /// Adds a new static data property to the builtin object. + fn static_property(mut self, key: K, value: V, attribute: Attribute) -> Self + where + K: Into, + V: Into, + { + let key = key.into(); + + debug_assert!(self.object_property_table.map.get(&key).is_none()); + self.object_property_table + .insert(key, SlotAttribute::from_bits_truncate(attribute.bits())); + self.object_storage.push(value.into()); + self + } + + /// Adds a new static accessor property to the builtin object. + fn static_accessor( + mut self, + key: K, + get: Option, + set: Option, + attribute: Attribute, + ) -> Self + where + K: Into, + { + let mut attributes = SlotAttribute::from_bits_truncate(attribute.bits()); + debug_assert!(!attributes.contains(SlotAttribute::WRITABLE)); + attributes.set(SlotAttribute::GET, get.is_some()); + attributes.set(SlotAttribute::SET, set.is_some()); + + let key = key.into(); + + debug_assert!(self.object_property_table.map.get(&key).is_none()); + self.object_property_table.insert(key, attributes); + self.object_storage.extend([ + get.map(JsValue::new).unwrap_or_default(), + set.map(JsValue::new).unwrap_or_default(), + ]); + self + } + + /// Specify the `[[Prototype]]` internal field of the builtin object. + /// + /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. + fn prototype(mut self, prototype: JsObject) -> Self { + self.__proto__ = Some(prototype); + self + } + + /// Adds a new method to the constructor's prototype. + fn method(mut self, function: NativeFunctionPointer, binding: B, length: usize) -> Self + where + B: Into, + { + let binding = binding.into(); + let function = BuiltInBuilder::callable(self.intrinsics, function) + .name(binding.name) + .length(length) + .build(); + + debug_assert!(self + .prototype_property_table + .map + .get(&binding.binding) + .is_none()); + self.prototype_property_table.insert( + binding.binding, + SlotAttribute::WRITABLE | SlotAttribute::CONFIGURABLE, + ); + self.prototype_storage.push(function.into()); + self + } + + /// Adds a new data property to the constructor's prototype. + fn property(mut self, key: K, value: V, attribute: Attribute) -> Self + where + K: Into, + V: Into, + { + let key = key.into(); + + debug_assert!(self.prototype_property_table.map.get(&key).is_none()); + self.prototype_property_table + .insert(key, SlotAttribute::from_bits_truncate(attribute.bits())); + self.prototype_storage.push(value.into()); + self + } + + /// Adds new accessor property to the constructor's prototype. + fn accessor( + mut self, + key: K, + get: Option, + set: Option, + attribute: Attribute, + ) -> Self + where + K: Into, + { + let mut attributes = SlotAttribute::from_bits_truncate(attribute.bits()); + debug_assert!(!attributes.contains(SlotAttribute::WRITABLE)); + attributes.set(SlotAttribute::GET, get.is_some()); + attributes.set(SlotAttribute::SET, set.is_some()); + + let key = key.into(); + + debug_assert!(self.prototype_property_table.map.get(&key).is_none()); + self.prototype_property_table.insert(key, attributes); + self.prototype_storage.extend([ + get.map(JsValue::new).unwrap_or_default(), + set.map(JsValue::new).unwrap_or_default(), + ]); + self + } + + /// Specifies the parent prototype which objects created by this constructor inherit from. + /// + /// Default is `Object.prototype`. + #[allow(clippy::missing_const_for_fn)] + fn inherits(mut self, prototype: JsPrototype) -> Self { + self.inherits = prototype; + self + } + + /// Specifies the property attributes of the prototype's "constructor" property. + const fn constructor_attributes(mut self, attributes: Attribute) -> Self { + self.attributes = attributes; + self + } + + fn build(mut self) { + let function = function::Function::Native { + function: NativeFunction::from_fn_ptr(self.function), + constructor: (true).then_some(function::ConstructorKind::Base), + }; + + let length = self.length; + let name = self.name.clone(); + let prototype = self.prototype.clone(); + self = self.static_property("length", length, Attribute::CONFIGURABLE); + self = self.static_property("name", name, Attribute::CONFIGURABLE); + self = self.static_property(PROTOTYPE, prototype, Attribute::empty()); + + let attributes = self.attributes; + let object = self.object.clone(); + self = self.property(CONSTRUCTOR, object, attributes); + + { + // Remove excess space. + self.prototype_property_table.shrink_to_fit(); + self.prototype_storage.shrink_to_fit(); + + let mut prototype = self.prototype.borrow_mut(); + prototype + .properties_mut() + .shape + .as_unique() + .expect("The object should have a unique shape") + .override_internal(self.prototype_property_table, self.inherits); + + let prototype_old_storage = std::mem::replace( + &mut prototype.properties_mut().storage, + self.prototype_storage, + ); + + debug_assert_eq!(prototype_old_storage.len(), 0); + } + + // Remove excess space. + self.object_property_table.shrink_to_fit(); + self.object_storage.shrink_to_fit(); + + let mut object = self.object.borrow_mut(); + object.data = ObjectData::function(function); + object + .properties_mut() + .shape + .as_unique() + .expect("The object should have a unique shape") + .override_internal(self.object_property_table, self.__proto__); + + let object_old_storage = + std::mem::replace(&mut object.properties_mut().storage, self.object_storage); + + debug_assert_eq!(object_old_storage.len(), 0); + } +} + struct BuiltInCallable<'ctx> { intrinsics: &'ctx Intrinsics, function: NativeFunctionPointer, @@ -644,6 +901,27 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable> { } } + fn from_standard_constructor_with_prototype( + intrinsics: &'ctx Intrinsics, + ) -> BuiltInConstructorWithPrototype<'ctx> { + let constructor = SC::STANDARD_CONSTRUCTOR(intrinsics.constructors()); + BuiltInConstructorWithPrototype { + intrinsics, + function: SC::constructor, + name: js_string!(SC::NAME), + length: SC::LENGTH, + object_property_table: PropertyTableInner::default(), + object_storage: Vec::default(), + object: constructor.constructor(), + prototype_property_table: PropertyTableInner::default(), + prototype_storage: Vec::default(), + prototype: constructor.prototype(), + __proto__: Some(intrinsics.constructors().function().prototype()), + inherits: Some(intrinsics.constructors().object().prototype()), + attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + } + } + fn no_proto(self) -> BuiltInBuilder<'ctx, Callable> { BuiltInBuilder { intrinsics: self.intrinsics, @@ -697,26 +975,6 @@ impl BuiltInBuilder<'_, T> { self } - /// Adds a new static accessor property to the builtin object. - fn static_accessor( - self, - key: K, - get: Option, - set: Option, - attribute: Attribute, - ) -> Self - where - K: Into, - { - let property = PropertyDescriptor::builder() - .maybe_get(get) - .maybe_set(set) - .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.object.borrow_mut().insert(key, property); - self - } - /// Specify the `[[Prototype]]` internal field of the builtin object. /// /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. @@ -763,41 +1021,6 @@ impl BuiltInBuilder<'_, Callable> { self.kind.kind.prototype.borrow_mut().insert(key, property); self } - - /// Adds new accessor property to the constructor's prototype. - fn accessor( - self, - key: K, - get: Option, - set: Option, - attribute: Attribute, - ) -> Self - where - K: Into, - { - let property = PropertyDescriptor::builder() - .maybe_get(get) - .maybe_set(set) - .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.kind.kind.prototype.borrow_mut().insert(key, property); - self - } - - /// Specifies the parent prototype which objects created by this constructor inherit from. - /// - /// Default is `Object.prototype`. - #[allow(clippy::missing_const_for_fn)] - fn inherits(mut self, prototype: JsPrototype) -> Self { - self.kind.kind.inherits = prototype; - self - } - - /// Specifies the property attributes of the prototype's "constructor" property. - const fn constructor_attributes(mut self, attributes: Attribute) -> Self { - self.kind.kind.attributes = attributes; - self - } } impl BuiltInBuilder<'_, Callable> { diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index ce5a636feef..fd609c4de8e 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -50,7 +50,7 @@ impl IntrinsicObject for Number { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_property(utf16!("EPSILON"), f64::EPSILON, attribute) .static_property( utf16!("MAX_SAFE_INTEGER"), diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 90050cfba2c..3e406546f6c 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -55,7 +55,7 @@ impl IntrinsicObject for Object { .name("set __proto__") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .inherits(None) .accessor( utf16!("__proto__"), diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 30fd1d1feb2..517e18f1fd9 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -315,7 +315,7 @@ impl IntrinsicObject for Promise { .name("get [Symbol.species]") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_method(Self::all, "all", 1) .static_method(Self::all_settled, "allSettled", 1) .static_method(Self::any, "any", 1) diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 813269f75ae..4baf7d431a7 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -80,7 +80,7 @@ impl IntrinsicObject for RegExp { let get_source = BuiltInBuilder::callable(intrinsics, Self::get_source) .name("get source") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_accessor( JsSymbol::species(), Some(get_species), diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 79497e61b6a..f0d174dc13d 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -55,7 +55,7 @@ impl IntrinsicObject for Set { .name("values") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_accessor( JsSymbol::species(), Some(get_species), diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index d21cfee0bc4..c75430643ab 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -70,7 +70,7 @@ impl IntrinsicObject for String { let symbol_iterator = JsSymbol::iterator(); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .property(utf16!("length"), 0, attribute) .static_method(Self::raw, "raw", 1) .static_method(Self::from_char_code, "fromCharCode", 1) diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index 2a9439d6087..2ba837761e8 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -119,7 +119,7 @@ impl IntrinsicObject for Symbol { .name("get description") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_method(Self::for_, "for", 1) .static_method(Self::key_for, "keyFor", 1) .static_property(utf16!("asyncIterator"), symbol_async_iterator, attribute) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index acd242d5736..14ef860b4bc 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -54,7 +54,7 @@ macro_rules! typed_array { .name("get [Symbol.species]") .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .prototype(intrinsics.constructors().typed_array().constructor()) .inherits(Some(intrinsics.constructors().typed_array().prototype())) .static_accessor( @@ -260,7 +260,7 @@ impl IntrinsicObject for TypedArray { .length(0) .build(); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .static_accessor( JsSymbol::species(), Some(get_species), diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs index 80067294875..4cbf3f94277 100644 --- a/boa_engine/src/builtins/weak/weak_ref.rs +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -162,6 +162,8 @@ mod tests { TestAction::inspect_context(|context| { context.clear_kept_objects(); boa_gc::force_collect(); + boa_gc::force_collect(); + boa_gc::force_collect(); }), TestAction::assert_eq("ptr.deref()", JsValue::undefined()), ]); diff --git a/boa_engine/src/builtins/weak_map/mod.rs b/boa_engine/src/builtins/weak_map/mod.rs index 950be2fea66..4c98200f52d 100644 --- a/boa_engine/src/builtins/weak_map/mod.rs +++ b/boa_engine/src/builtins/weak_map/mod.rs @@ -32,7 +32,7 @@ impl IntrinsicObject for WeakMap { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .property( JsSymbol::to_string_tag(), Self::NAME, diff --git a/boa_engine/src/builtins/weak_set/mod.rs b/boa_engine/src/builtins/weak_set/mod.rs index a851b9ed94e..a8effa448b1 100644 --- a/boa_engine/src/builtins/weak_set/mod.rs +++ b/boa_engine/src/builtins/weak_set/mod.rs @@ -29,7 +29,7 @@ impl IntrinsicObject for WeakSet { fn init(intrinsics: &Intrinsics) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - BuiltInBuilder::from_standard_constructor::(intrinsics) + BuiltInBuilder::from_standard_constructor_with_prototype::(intrinsics) .property( JsSymbol::to_string_tag(), Self::NAME, diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 4631297d5e9..c22edc2ba6d 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -89,6 +89,13 @@ impl Shape { } } + pub(crate) const fn as_unique(&self) -> Option<&UniqueShape> { + if let Inner::Unique(shape) = &self.inner { + return Some(shape); + } + None + } + /// Create an insert property transitions returning the new transitioned [`Shape`]. /// /// NOTE: This assumes that there is no property with the given key! diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs index 763b9f26fe0..fbafb21ac64 100644 --- a/boa_engine/src/object/shape/property_table.rs +++ b/boa_engine/src/object/shape/property_table.rs @@ -60,6 +60,11 @@ impl PropertyTableInner { let value = self.map.insert(key, (index, slot)); debug_assert!(value.is_none()); } + + pub(crate) fn shrink_to_fit(&mut self) { + self.map.shrink_to_fit(); + self.keys.shrink_to_fit(); + } } /// Represents an ordered property table, that maps [`PropertyTable`] to [`Slot`]. diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index ef456e5ba6d..071043573b1 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -44,6 +44,15 @@ impl UniqueShape { } } + pub(crate) fn override_internal( + &self, + property_table: PropertyTableInner, + prototype: JsPrototype, + ) { + *self.inner.property_table.borrow_mut() = property_table; + *self.inner.prototype.borrow_mut() = prototype; + } + /// Get the prototype of the [`UniqueShape`]. pub(crate) fn prototype(&self) -> JsPrototype { self.inner.prototype.borrow().clone() From 236bffea1dd576526b3cb0a6535eadf5e3b0b6a6 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Tue, 4 Apr 2023 01:00:16 +0200 Subject: [PATCH 11/20] Fix ci fail --- boa_engine/src/object/shape/property_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs index fbafb21ac64..58eefebf935 100644 --- a/boa_engine/src/object/shape/property_table.rs +++ b/boa_engine/src/object/shape/property_table.rs @@ -81,7 +81,7 @@ impl PropertyTable { } /// Add a property to the [`PropertyTable`] or deep clone it, - /// if there already is property or the property [`SlotAttributes`] are not the same. + /// if there already is a property or the property has attributes that are not the same. pub(crate) fn add_property_deep_clone_if_needed( &self, key: PropertyKey, From a2b945530b90678d990ef0afdf0482b1351d546e Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Wed, 5 Apr 2023 21:51:46 +0200 Subject: [PATCH 12/20] Use flags in shared shape --- boa_engine/src/builtins/mod.rs | 8 -- boa_engine/src/object/shape/property_table.rs | 5 -- .../src/object/shape/shared_shape/mod.rs | 83 +++++++++++++------ 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index cf67485125c..b98f5ad1b9e 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -750,10 +750,6 @@ impl BuiltInConstructorWithPrototype<'_> { self = self.property(CONSTRUCTOR, object, attributes); { - // Remove excess space. - self.prototype_property_table.shrink_to_fit(); - self.prototype_storage.shrink_to_fit(); - let mut prototype = self.prototype.borrow_mut(); prototype .properties_mut() @@ -770,10 +766,6 @@ impl BuiltInConstructorWithPrototype<'_> { debug_assert_eq!(prototype_old_storage.len(), 0); } - // Remove excess space. - self.object_property_table.shrink_to_fit(); - self.object_storage.shrink_to_fit(); - let mut object = self.object.borrow_mut(); object.data = ObjectData::function(function); object diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs index 58eefebf935..ecd2aba167a 100644 --- a/boa_engine/src/object/shape/property_table.rs +++ b/boa_engine/src/object/shape/property_table.rs @@ -60,11 +60,6 @@ impl PropertyTableInner { let value = self.map.insert(key, (index, slot)); debug_assert!(value.is_none()); } - - pub(crate) fn shrink_to_fit(&mut self) { - self.map.shrink_to_fit(); - self.keys.shrink_to_fit(); - } } /// Represents an ordered property table, that maps [`PropertyTable`] to [`Slot`]. diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index b18d058f2ba..dd1143687a9 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod template; use std::{collections::hash_map::RandomState, hash::Hash}; +use bitflags::bitflags; use boa_gc::{empty_trace, Finalize, Gc, Trace}; use indexmap::IndexMap; @@ -28,21 +29,55 @@ unsafe impl Trace for TransitionKey { empty_trace!(); } -/// Represents the transition type of a [`SharedShape`]. -#[derive(Debug, Finalize, Clone, Copy, PartialEq, Eq)] -pub(crate) enum TransitionType { - /// Inserts a new property. - Insert, +const INSERT_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0000; +const CONFIGURE_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0001; +const PROTOTYPE_TRANSITION_TYPE: u8 = 0b0000_0010; - /// Change existing property attributes. - Configure, +// Reserved for future use! +#[allow(unused)] +const RESEREVED_TRANSITION_TYPE: u8 = 0b0000_0011; - /// Change prototype. - Prototype, +bitflags! { + /// Flags of a shape. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Finalize)] + pub struct ShapeFlags: u8 { + /// Represents the transition type of a [`SharedShape`]. + const TRANSITION_TYPE = 0b0000_0011; + } +} + +impl Default for ShapeFlags { + fn default() -> Self { + Self::empty() + } +} + +impl ShapeFlags { + // NOTE: Remove type bits and set the new ones. + fn insert_property_transition_from(previous: ShapeFlags) -> Self { + previous.difference(Self::TRANSITION_TYPE) + | Self::from_bits_truncate(INSERT_PROPERTY_TRANSITION_TYPE) + } + fn configure_property_transition_from(previous: ShapeFlags) -> Self { + previous.difference(Self::TRANSITION_TYPE) + | Self::from_bits_truncate(CONFIGURE_PROPERTY_TRANSITION_TYPE) + } + fn prototype_transition_from(previous: ShapeFlags) -> Self { + previous.difference(Self::TRANSITION_TYPE) + | Self::from_bits_truncate(PROTOTYPE_TRANSITION_TYPE) + } + + const fn is_insert_transition_type(self) -> bool { + self.intersection(Self::TRANSITION_TYPE).bits() == INSERT_PROPERTY_TRANSITION_TYPE + } + const fn is_prototype_transition_type(self) -> bool { + self.intersection(Self::TRANSITION_TYPE).bits() == CONFIGURE_PROPERTY_TRANSITION_TYPE + } } -// SAFETY: This is safe because nothing needs tracing. -unsafe impl Trace for TransitionType { +// SAFETY: Non of the member of this struct are garbage collected, +// so this should be fine. +unsafe impl Trace for ShapeFlags { empty_trace!(); } @@ -68,11 +103,11 @@ struct Inner { /// [`None`] if it is the root shape. previous: Option, - /// The transition type this [`SharedShape`] is. - transition_type: TransitionType, - /// How many transitions have happened from the root node. transition_count: u16, + + /// Flags about the shape. + flags: ShapeFlags, } /// Represents a shared object shape. @@ -114,9 +149,9 @@ impl SharedShape { .expect("There should be a property"); (key.clone(), *slot) } - /// Get the transition type of the [`SharedShape`]. - fn transition_type(&self) -> TransitionType { - self.inner.transition_type + /// Get the flags of the shape. + fn flags(&self) -> ShapeFlags { + self.inner.flags } /// Getter for the [`ForwardTransition`] field. fn forward_transitions(&self) -> &ForwardTransition { @@ -146,7 +181,7 @@ impl SharedShape { property_count: 0, property_table: PropertyTable::default(), previous: None, - transition_type: TransitionType::Insert, + flags: ShapeFlags::default(), transition_count: 0, }) } @@ -164,8 +199,8 @@ impl SharedShape { property_table: self.property_table().clone(), property_count: self.property_count(), previous: Some(self.clone()), - transition_type: TransitionType::Prototype, transition_count: self.transition_count() + 1, + flags: ShapeFlags::prototype_transition_from(self.flags()), }; let new_shape = Self::new(new_inner_shape); @@ -195,8 +230,8 @@ impl SharedShape { property_table, property_count: self.property_count() + 1, previous: Some(self.clone()), - transition_type: TransitionType::Insert, transition_count: self.transition_count() + 1, + flags: ShapeFlags::insert_property_transition_from(self.flags()), }; let new_shape = Self::new(new_inner_shape); @@ -243,8 +278,8 @@ impl SharedShape { property_table, property_count: self.property_count(), previous: Some(self.clone()), - transition_type: TransitionType::Configure, transition_count: self.transition_count() + 1, + flags: ShapeFlags::configure_property_transition_from(self.flags()), }; let shape = Self::new(inner_shape); @@ -335,7 +370,7 @@ impl SharedShape { }; // We only take the latest prototype change it, if it exists. - if current_shape.transition_type() == TransitionType::Prototype { + if current_shape.flags().is_prototype_transition_type() { if prototype.is_none() { prototype = Some(current_shape.prototype().clone()); } @@ -347,9 +382,7 @@ impl SharedShape { let (current_property_key, slot) = current_shape.property(); - if current_shape.transition_type() == TransitionType::Insert - && ¤t_property_key == key - { + if current_shape.flags().is_insert_transition_type() && ¤t_property_key == key { let base = if let Some(base) = current_shape.previous() { base.clone() } else { From 6726ba7bf6860bb5dee08cf10244068c1594c6b2 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Wed, 5 Apr 2023 22:16:18 +0200 Subject: [PATCH 13/20] Unify function shapes --- boa_engine/src/context/intrinsics.rs | 65 +++++++++---------- .../src/object/shape/shared_shape/template.rs | 18 ++++- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 15e5b4cb126..b559c3a69cf 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -975,56 +975,53 @@ pub(crate) struct ObjectTemplates { impl ObjectTemplates { pub(crate) fn new(root_shape: &SharedShape, constructors: &StandardConstructors) -> Self { // pre-initialize used shapes. - let ordinary_object = ObjectTemplate::new(root_shape, constructors.object().prototype()); - let mut array = ObjectTemplate::new(root_shape, constructors.array().prototype()); - + let ordinary_object = + ObjectTemplate::with_prototype(root_shape, constructors.object().prototype()); + let mut array = ObjectTemplate::new(root_shape); let length_property_key: PropertyKey = "length".into(); - array.data_property( + array.property( length_property_key.clone(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); - - let number = ObjectTemplate::new(root_shape, constructors.number().prototype()); - let symbol = ObjectTemplate::new(root_shape, constructors.symbol().prototype()); - let bigint = ObjectTemplate::new(root_shape, constructors.bigint().prototype()); - let boolean = ObjectTemplate::new(root_shape, constructors.boolean().prototype()); - let mut string = ObjectTemplate::new(root_shape, constructors.string().prototype()); - string.data_property( + array.set_prototype(constructors.array().prototype()); + + let number = ObjectTemplate::with_prototype(root_shape, constructors.number().prototype()); + let symbol = ObjectTemplate::with_prototype(root_shape, constructors.symbol().prototype()); + let bigint = ObjectTemplate::with_prototype(root_shape, constructors.bigint().prototype()); + let boolean = + ObjectTemplate::with_prototype(root_shape, constructors.boolean().prototype()); + let mut string = ObjectTemplate::new(root_shape); + string.property( length_property_key.clone(), Attribute::READONLY | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); + string.set_prototype(constructors.string().prototype()); - // TODO: maybe construct it name, length, then prototype to create the least amount of branching. let name_property_key: PropertyKey = "name".into(); - let mut function = ObjectTemplate::new(root_shape, constructors.function().prototype()); - function.data_property( - name_property_key.clone(), - Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - function.data_property( - length_property_key.clone(), - Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ); - - let mut async_function = - ObjectTemplate::new(root_shape, constructors.async_function().prototype()); - async_function.data_property( + let mut function = ObjectTemplate::new(root_shape); + function.property( name_property_key, Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); - async_function.data_property( + function.property( length_property_key.clone(), Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); + let mut async_function = function.clone(); let mut function_with_prototype = function.clone(); - function_with_prototype.data_property( + + function_with_prototype.property( PROTOTYPE.into(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); + function.set_prototype(constructors.function().prototype()); + function_with_prototype.set_prototype(constructors.function().prototype()); + async_function.set_prototype(constructors.async_function().prototype()); + let mut function_prototype = ordinary_object.clone(); - function_prototype.data_property( + function_prototype.property( CONSTRUCTOR.into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); @@ -1033,7 +1030,7 @@ impl ObjectTemplates { // // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - unmapped_arguments.data_property( + unmapped_arguments.property( length_property_key, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); @@ -1041,7 +1038,7 @@ impl ObjectTemplates { // // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { // // [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, // // [[Configurable]]: true }). - unmapped_arguments.data_property( + unmapped_arguments.property( JsSymbol::iterator().into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); @@ -1051,7 +1048,7 @@ impl ObjectTemplates { // // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, // // [[Configurable]]: false }). - unmapped_arguments.accessor_property( + unmapped_arguments.accessor( "callee".into(), true, true, @@ -1060,17 +1057,17 @@ impl ObjectTemplates { // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - mapped_arguments.data_property( + mapped_arguments.property( "callee".into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); let mut iterator_result = ordinary_object.clone(); - iterator_result.data_property( + iterator_result.property( "value".into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); - iterator_result.data_property( + iterator_result.property( "done".into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); diff --git a/boa_engine/src/object/shape/shared_shape/template.rs b/boa_engine/src/object/shape/shared_shape/template.rs index f3229c6849d..5a271dc5c1a 100644 --- a/boa_engine/src/object/shape/shared_shape/template.rs +++ b/boa_engine/src/object/shape/shared_shape/template.rs @@ -19,7 +19,14 @@ pub(crate) struct ObjectTemplate { } impl ObjectTemplate { - pub(crate) fn new(root_shape: &SharedShape, prototype: JsObject) -> Self { + pub(crate) fn new(root_shape: &SharedShape) -> Self { + Self { + shape: root_shape.clone(), + storage_len: 0, + } + } + + pub(crate) fn with_prototype(root_shape: &SharedShape, prototype: JsObject) -> Self { let shape = root_shape.change_prototype_transition(Some(prototype)); Self { shape, @@ -31,7 +38,12 @@ impl ObjectTemplate { self.shape.has_prototype(prototype) } - pub(crate) fn data_property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { + pub(crate) fn set_prototype(&mut self, prototype: JsObject) -> &mut Self { + self.shape = self.shape.change_prototype_transition(Some(prototype)); + self + } + + pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { // TOOD: We don't support indexed keys. debug_assert!(!matches!(&key, PropertyKey::Index(_))); @@ -44,7 +56,7 @@ impl ObjectTemplate { self } - pub(crate) fn accessor_property( + pub(crate) fn accessor( &mut self, key: PropertyKey, get: bool, From 97e2dd07fe4685f48ae11dcaa978421abdd22b29 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Wed, 5 Apr 2023 22:53:47 +0200 Subject: [PATCH 14/20] Improve create function from prototype --- boa_engine/src/context/intrinsics.rs | 14 +++ boa_engine/src/vm/code_block.rs | 108 +++++++++-------------- boa_engine/src/vm/opcode/get/function.rs | 10 +-- 3 files changed, 61 insertions(+), 71 deletions(-) diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index b559c3a69cf..0d03b0337ff 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -970,6 +970,9 @@ pub(crate) struct ObjectTemplates { function: ObjectTemplate, async_function: ObjectTemplate, + + function_without_proto: ObjectTemplate, + function_with_prototype_without_proto: ObjectTemplate, } impl ObjectTemplates { @@ -1008,6 +1011,7 @@ impl ObjectTemplates { Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); + let function_without_proto = function.clone(); let mut async_function = function.clone(); let mut function_with_prototype = function.clone(); @@ -1016,6 +1020,8 @@ impl ObjectTemplates { Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, ); + let function_with_prototype_without_proto = function_with_prototype.clone(); + function.set_prototype(constructors.function().prototype()); function_with_prototype.set_prototype(constructors.function().prototype()); async_function.set_prototype(constructors.async_function().prototype()); @@ -1087,6 +1093,8 @@ impl ObjectTemplates { function_prototype, function, async_function, + function_without_proto, + function_with_prototype_without_proto, } } @@ -1132,4 +1140,10 @@ impl ObjectTemplates { pub(crate) const fn async_function(&self) -> &ObjectTemplate { &self.async_function } + pub(crate) const fn function_without_proto(&self) -> &ObjectTemplate { + &self.function_without_proto + } + pub(crate) const fn function_with_prototype_without_proto(&self) -> &ObjectTemplate { + &self.function_with_prototype_without_proto + } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 948643ce47d..a0c6aa14de5 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -12,10 +12,7 @@ use crate::{ context::intrinsics::StandardConstructors, environments::{BindingLocator, CompileTimeEnvironment}, error::JsNativeError, - object::{ - internal_methods::get_prototype_from_constructor, JsObject, ObjectData, CONSTRUCTOR, - PROTOTYPE, - }, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PROTOTYPE}, property::PropertyDescriptor, string::utf16, vm::CallFrame, @@ -555,38 +552,20 @@ pub(crate) fn create_function_object( method: bool, context: &mut Context<'_>, ) -> JsObject { - let _timer = Profiler::global().start_event("JsVmFunction::new", "vm"); + let _timer = Profiler::global().start_event("create_function_object", "vm"); - let function_prototype = if let Some(prototype) = prototype { - prototype - } else if r#async { - context - .intrinsics() - .constructors() - .async_function() - .prototype() - } else { - context.intrinsics().constructors().function().prototype() + let Some(prototype) = prototype else { + // fast path + return create_function_object_fast(code, r#async, arrow, method, context); }; - let name_property = PropertyDescriptor::builder() - .value( - context - .interner() - .resolve_expect(code.name) - .into_common::(false), - ) - .writable(false) - .enumerable(false) - .configurable(true) - .build(); + let name: JsValue = context + .interner() + .resolve_expect(code.name) + .into_common::(false) + .into(); - let length_property = PropertyDescriptor::builder() - .value(code.length) - .writable(false) - .enumerable(false) - .configurable(true) - .build(); + let length: JsValue = code.length.into(); let function = if r#async { let promise_capability = PromiseCapability::new( @@ -614,51 +593,48 @@ pub(crate) fn create_function_object( } }; - let constructor = - JsObject::from_proto_and_data(function_prototype, ObjectData::function(function)); + let data = ObjectData::function(function); - let constructor_property = PropertyDescriptor::builder() - .value(constructor.clone()) - .writable(true) - .enumerable(false) - .configurable(true) - .build(); + let templates = context.intrinsics().templates(); - constructor - .define_property_or_throw(utf16!("length"), length_property, context) - .expect("failed to define the length property of the function"); - constructor - .define_property_or_throw(utf16!("name"), name_property, context) - .expect("failed to define the name property of the function"); - - if !r#async && !arrow && !method { - let prototype = JsObject::with_object_proto(context); - prototype - .define_property_or_throw(CONSTRUCTOR, constructor_property, context) - .expect("failed to define the constructor property of the function"); - - let prototype_property = PropertyDescriptor::builder() - .value(prototype) - .writable(true) - .enumerable(false) - .configurable(false) - .build(); - constructor - .define_property_or_throw(PROTOTYPE, prototype_property, context) - .expect("failed to define the prototype property of the function"); - } + let (mut template, storage, constructor_prototype) = if r#async || arrow || method { + ( + templates.function_without_proto().clone(), + vec![name, length], + None, + ) + } else { + let constructor_prototype = templates + .function_prototype() + .create(ObjectData::ordinary(), vec![JsValue::undefined()]); - constructor + let template = templates.function_with_prototype_without_proto(); + + ( + template.clone(), + vec![name, length, constructor_prototype.clone().into()], + Some(constructor_prototype), + ) + }; + + template.set_prototype(prototype); + + let contructor = template.create(data, storage); + + if let Some(constructor_prototype) = &constructor_prototype { + constructor_prototype.borrow_mut().properties_mut().storage[0] = contructor.clone().into(); + } + contructor } -pub(crate) fn create_function_object_opcode( +pub(crate) fn create_function_object_fast( code: Gc, r#async: bool, arrow: bool, method: bool, context: &mut Context<'_>, ) -> JsObject { - let _timer = Profiler::global().start_event("JsVmFunction::new", "vm"); + let _timer = Profiler::global().start_event("create_function_object_fast", "vm"); let name: JsValue = context .interner() diff --git a/boa_engine/src/vm/opcode/get/function.rs b/boa_engine/src/vm/opcode/get/function.rs index 20fb2f34d20..dd32a0d04e3 100644 --- a/boa_engine/src/vm/opcode/get/function.rs +++ b/boa_engine/src/vm/opcode/get/function.rs @@ -1,5 +1,5 @@ use crate::{ - vm::{code_block::create_function_object_opcode, opcode::Operation, CompletionType}, + vm::{code_block::create_function_object_fast, opcode::Operation, CompletionType}, Context, JsResult, }; @@ -18,7 +18,7 @@ impl Operation for GetArrowFunction { let index = context.vm.read::(); context.vm.read::(); let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object_opcode(code, false, true, false, context); + let function = create_function_object_fast(code, false, true, false, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -39,7 +39,7 @@ impl Operation for GetAsyncArrowFunction { let index = context.vm.read::(); context.vm.read::(); let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object_opcode(code, true, true, false, context); + let function = create_function_object_fast(code, true, true, false, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -60,7 +60,7 @@ impl Operation for GetFunction { let index = context.vm.read::(); let method = context.vm.read::() != 0; let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object_opcode(code, false, false, method, context); + let function = create_function_object_fast(code, false, false, method, context); context.vm.push(function); Ok(CompletionType::Normal) } @@ -81,7 +81,7 @@ impl Operation for GetFunctionAsync { let index = context.vm.read::(); let method = context.vm.read::() != 0; let code = context.vm.frame().code_block.functions[index as usize].clone(); - let function = create_function_object_opcode(code, true, false, method, context); + let function = create_function_object_fast(code, true, false, method, context); context.vm.push(function); Ok(CompletionType::Normal) } From a8280afba560714e7f4601ab7c60526d2111ac97 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Thu, 6 Apr 2023 00:00:05 +0200 Subject: [PATCH 15/20] Add `.shape` module --- boa_cli/src/debug/mod.rs | 7 ++ boa_cli/src/debug/shape.rs | 66 +++++++++++++++++++ boa_engine/src/builtins/escape/mod.rs | 6 +- boa_engine/src/object/mod.rs | 36 ++++++---- boa_engine/src/object/shape/mod.rs | 25 ++++++- .../src/object/shape/shared_shape/mod.rs | 5 ++ boa_engine/src/object/shape/unique_shape.rs | 5 ++ docs/boa_object.md | 39 +++++++++++ 8 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 boa_cli/src/debug/shape.rs diff --git a/boa_cli/src/debug/mod.rs b/boa_cli/src/debug/mod.rs index 48a02a0bd28..050c29fce44 100644 --- a/boa_cli/src/debug/mod.rs +++ b/boa_cli/src/debug/mod.rs @@ -7,10 +7,12 @@ mod function; mod gc; mod object; mod optimizer; +mod shape; fn create_boa_object(context: &mut Context<'_>) -> JsObject { let function_module = function::create_object(context); let object_module = object::create_object(context); + let shape_module = shape::create_object(context); let optimizer_module = optimizer::create_object(context); let gc_module = gc::create_object(context); @@ -25,6 +27,11 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject { object_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) + .property( + "shape", + shape_module, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .property( "optimizer", optimizer_module, diff --git a/boa_cli/src/debug/shape.rs b/boa_cli/src/debug/shape.rs new file mode 100644 index 00000000000..167bc5b3c47 --- /dev/null +++ b/boa_cli/src/debug/shape.rs @@ -0,0 +1,66 @@ +use boa_engine::{ + js_string, object::ObjectInitializer, Context, JsArgs, JsNativeError, JsObject, JsResult, + JsValue, NativeFunction, +}; + +fn get_object(args: &[JsValue], position: usize) -> JsResult<&JsObject> { + let value = args.get_or_undefined(position); + + let Some(object) = value.as_object() else { + return Err(JsNativeError::typ() + .with_message(format!("expected object in argument position {position}, got {}", value.type_of())) + .into()); + }; + + Ok(object) +} + +/// Returns object's shape pointer in memory. +fn id(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult { + let object = get_object(args, 0)?; + let object = object.borrow(); + let shape = object.shape(); + Ok(format!("0x{:X}", shape.to_addr_usize()).into()) +} + +/// Returns object's shape type. +fn r#type(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult { + let object = get_object(args, 0)?; + let object = object.borrow(); + let shape = object.shape(); + + Ok(if shape.is_shared() { + js_string!("shared") + } else { + js_string!("unique") + } + .into()) +} + +/// Returns object's shape type. +fn same(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult { + let lhs = get_object(args, 0)?; + let rhs = get_object(args, 1)?; + + let lhs_shape_ptr = { + let object = lhs.borrow(); + let shape = object.shape(); + shape.to_addr_usize() + }; + + let rhs_shape_ptr = { + let object = rhs.borrow(); + let shape = object.shape(); + shape.to_addr_usize() + }; + + Ok(JsValue::new(lhs_shape_ptr == rhs_shape_ptr)) +} + +pub(super) fn create_object(context: &mut Context<'_>) -> JsObject { + ObjectInitializer::new(context) + .function(NativeFunction::from_fn_ptr(id), "id", 1) + .function(NativeFunction::from_fn_ptr(r#type), "type", 1) + .function(NativeFunction::from_fn_ptr(same), "same", 2) + .build() +} diff --git a/boa_engine/src/builtins/escape/mod.rs b/boa_engine/src/builtins/escape/mod.rs index df5d1fdb6c4..92fcf441e27 100644 --- a/boa_engine/src/builtins/escape/mod.rs +++ b/boa_engine/src/builtins/escape/mod.rs @@ -22,8 +22,7 @@ pub(crate) struct Escape; impl IntrinsicObject for Escape { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(escape) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, escape) .name(Self::NAME) .length(1) .build(); @@ -95,8 +94,7 @@ pub(crate) struct Unescape; impl IntrinsicObject for Unescape { fn init(intrinsics: &Intrinsics) { - BuiltInBuilder::with_intrinsic::(intrinsics) - .callable(unescape) + BuiltInBuilder::callable_with_intrinsic::(intrinsics, unescape) .name(Self::NAME) .length(1) .build(); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 46b2b3d3cac..e767cecf785 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -8,21 +8,24 @@ pub use operations::IntegrityLevel; pub use property_map::*; use thin_vec::ThinVec; -use self::internal_methods::{ - arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS, - array::ARRAY_EXOTIC_INTERNAL_METHODS, - bound_function::{ - BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, +use self::{ + internal_methods::{ + arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS, + array::ARRAY_EXOTIC_INTERNAL_METHODS, + bound_function::{ + BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, + }, + function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, + global::GLOBAL_INTERNAL_METHODS, + integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, + proxy::{ + PROXY_EXOTIC_INTERNAL_METHODS_ALL, PROXY_EXOTIC_INTERNAL_METHODS_BASIC, + PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL, + }, + string::STRING_EXOTIC_INTERNAL_METHODS, + InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }, - function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, - global::GLOBAL_INTERNAL_METHODS, - integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, - proxy::{ - PROXY_EXOTIC_INTERNAL_METHODS_ALL, PROXY_EXOTIC_INTERNAL_METHODS_BASIC, - PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL, - }, - string::STRING_EXOTIC_INTERNAL_METHODS, - InternalObjectMethods, ORDINARY_INTERNAL_METHODS, + shape::Shape, }; #[cfg(feature = "intl")] use crate::builtins::intl::{ @@ -790,6 +793,11 @@ impl Default for Object { } impl Object { + /// Returns the shape of the object. + pub const fn shape(&self) -> &Shape { + &self.properties.shape + } + /// Returns the kind of the object. #[inline] pub const fn kind(&self) -> &ObjectKind { diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index c22edc2ba6d..c23560743d2 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -89,6 +89,18 @@ impl Shape { } } + /// Returns `true` if it's a shared shape, `false` otherwise. + #[inline] + pub const fn is_shared(&self) -> bool { + matches!(self.inner, Inner::Shared(_)) + } + + /// Returns `true` if it's a unique shape, `false` otherwise. + #[inline] + pub const fn is_unique(&self) -> bool { + matches!(self.inner, Inner::Unique(_)) + } + pub(crate) const fn as_unique(&self) -> Option<&UniqueShape> { if let Inner::Unique(shape) = &self.inner { return Some(shape); @@ -187,10 +199,21 @@ impl Shape { } } - pub(crate) fn keys(&self) -> Vec { + /// + #[inline] + pub fn keys(&self) -> Vec { match &self.inner { Inner::Shared(shape) => shape.keys(), Inner::Unique(shape) => shape.keys(), } } + + /// Return location in memory of the [`Shape`]. + #[inline] + pub fn to_addr_usize(&self) -> usize { + match &self.inner { + Inner::Shared(shape) => shape.to_addr_usize(), + Inner::Unique(shape) => shape.to_addr_usize(), + } + } } diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index dd1143687a9..94d2f2d054f 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -460,4 +460,9 @@ impl SharedShape { .clone_count(self.property_count()), ) } + + pub(crate) fn to_addr_usize(&self) -> usize { + let ptr: *const _ = self.inner.as_ref(); + ptr as usize + } } diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index 071043573b1..1e7f8365f1b 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -233,4 +233,9 @@ impl UniqueShape { pub(crate) fn keys(&self) -> Vec { self.property_table().borrow().keys() } + + pub(crate) fn to_addr_usize(&self) -> usize { + let ptr: *const _ = self.inner.as_ref(); + ptr as usize + } } diff --git a/docs/boa_object.md b/docs/boa_object.md index 52c7c843927..aa40f698612 100644 --- a/docs/boa_object.md +++ b/docs/boa_object.md @@ -148,3 +148,42 @@ Optimizer { 2 >> ``` + +## Module `$boa.shape` + +This module contains helpful functions for getting information about a shape of an object. + +### Function `$boa.shape.id(object)` + +Returns the pointer of the object's shape in memory as a string encoded in hexadecimal fomrat. + +```JavaScript +$boa.shape.id(Number) // '0x7FC35A073868' +$boa.shape.id({}) // '0x7FC35A046258' +``` + +### Function `$boa.shape.type(object)` + +Returns the object's shape type. + +```JavaScript +$boa.shape.type({x: 3}) // 'shared' +$boa.shape.type(Number) // 'unique' +``` + +### Function `$boa.shape.same(o1, o2)` + +Returns `true` if both objects have the same shape. + +```JavaScript +// The values of the properties are not important! +let o1 = { x: 10 } +let o2 = {} +$boa.shape.same(o1, o2) // false + +o2.x = 20 +$boa.shape.same(o1, o2) // true + +o2.y = 200 +$boa.shape.same(o1, o2) // false +``` From 26887d4d5100ca125c935494634ea543bf34cd58 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Thu, 6 Apr 2023 01:48:51 +0200 Subject: [PATCH 16/20] Fix panics --- boa_engine/src/object/shape/shared_shape/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index 94d2f2d054f..49c8dc03f0b 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -56,22 +56,22 @@ impl ShapeFlags { // NOTE: Remove type bits and set the new ones. fn insert_property_transition_from(previous: ShapeFlags) -> Self { previous.difference(Self::TRANSITION_TYPE) - | Self::from_bits_truncate(INSERT_PROPERTY_TRANSITION_TYPE) + | Self::from_bits_retain(INSERT_PROPERTY_TRANSITION_TYPE) } fn configure_property_transition_from(previous: ShapeFlags) -> Self { previous.difference(Self::TRANSITION_TYPE) - | Self::from_bits_truncate(CONFIGURE_PROPERTY_TRANSITION_TYPE) + | Self::from_bits_retain(CONFIGURE_PROPERTY_TRANSITION_TYPE) } fn prototype_transition_from(previous: ShapeFlags) -> Self { previous.difference(Self::TRANSITION_TYPE) - | Self::from_bits_truncate(PROTOTYPE_TRANSITION_TYPE) + | Self::from_bits_retain(PROTOTYPE_TRANSITION_TYPE) } const fn is_insert_transition_type(self) -> bool { self.intersection(Self::TRANSITION_TYPE).bits() == INSERT_PROPERTY_TRANSITION_TYPE } const fn is_prototype_transition_type(self) -> bool { - self.intersection(Self::TRANSITION_TYPE).bits() == CONFIGURE_PROPERTY_TRANSITION_TYPE + self.intersection(Self::TRANSITION_TYPE).bits() == PROTOTYPE_TRANSITION_TYPE } } From f4d5026e70e9cc5e7f7d74298d693db3bf164cd2 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Thu, 6 Apr 2023 02:16:51 +0200 Subject: [PATCH 17/20] Fix funtion property order --- boa_engine/src/builtins/array/mod.rs | 6 +- boa_engine/src/builtins/mod.rs | 28 +++++----- boa_engine/src/context/intrinsics.rs | 4 +- boa_engine/src/object/mod.rs | 2 +- boa_engine/src/object/property_map.rs | 10 ++-- boa_engine/src/object/shape/mod.rs | 10 ++-- boa_engine/src/object/shape/property_table.rs | 48 ++++++++-------- .../src/object/shape/shared_shape/mod.rs | 17 +++--- .../src/object/shape/shared_shape/template.rs | 56 +++++++++++++------ boa_engine/src/object/shape/slot.rs | 27 ++++----- boa_engine/src/object/shape/unique_shape.rs | 2 +- boa_engine/src/property/mod.rs | 16 +++--- boa_engine/src/vm/code_block.rs | 10 ++-- docs/shapes.md | 4 +- 14 files changed, 131 insertions(+), 109 deletions(-) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index aa5e2f8fe6e..096759fb4bd 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -320,7 +320,11 @@ impl Array { .intrinsics() .templates() .array() - .create_with_index_properties(ObjectData::array(), vec![JsValue::new(length)], elements) + .create_with_indexed_properties( + ObjectData::array(), + vec![JsValue::new(length)], + elements, + ) } /// Utility function for concatenating array objects. diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index b98f5ad1b9e..cc9b0f1afa3 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -98,7 +98,7 @@ use crate::{ native_function::{NativeFunction, NativeFunctionPointer}, object::{ shape::{ - property_table::PropertyTableInner, shared_shape::SharedShape, slot::SlotAttribute, + property_table::PropertyTableInner, shared_shape::SharedShape, slot::SlotAttributes, }, FunctionBinding, JsFunction, JsObject, JsPrototype, ObjectData, CONSTRUCTOR, PROTOTYPE, }, @@ -595,7 +595,7 @@ impl BuiltInConstructorWithPrototype<'_> { .is_none()); self.object_property_table.insert( binding.binding, - SlotAttribute::WRITABLE | SlotAttribute::CONFIGURABLE, + SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, ); self.object_storage.push(function.into()); self @@ -611,7 +611,7 @@ impl BuiltInConstructorWithPrototype<'_> { debug_assert!(self.object_property_table.map.get(&key).is_none()); self.object_property_table - .insert(key, SlotAttribute::from_bits_truncate(attribute.bits())); + .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); self.object_storage.push(value.into()); self } @@ -627,10 +627,10 @@ impl BuiltInConstructorWithPrototype<'_> { where K: Into, { - let mut attributes = SlotAttribute::from_bits_truncate(attribute.bits()); - debug_assert!(!attributes.contains(SlotAttribute::WRITABLE)); - attributes.set(SlotAttribute::GET, get.is_some()); - attributes.set(SlotAttribute::SET, set.is_some()); + let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); + debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); + attributes.set(SlotAttributes::GET, get.is_some()); + attributes.set(SlotAttributes::SET, set.is_some()); let key = key.into(); @@ -669,7 +669,7 @@ impl BuiltInConstructorWithPrototype<'_> { .is_none()); self.prototype_property_table.insert( binding.binding, - SlotAttribute::WRITABLE | SlotAttribute::CONFIGURABLE, + SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, ); self.prototype_storage.push(function.into()); self @@ -685,7 +685,7 @@ impl BuiltInConstructorWithPrototype<'_> { debug_assert!(self.prototype_property_table.map.get(&key).is_none()); self.prototype_property_table - .insert(key, SlotAttribute::from_bits_truncate(attribute.bits())); + .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); self.prototype_storage.push(value.into()); self } @@ -701,10 +701,10 @@ impl BuiltInConstructorWithPrototype<'_> { where K: Into, { - let mut attributes = SlotAttribute::from_bits_truncate(attribute.bits()); - debug_assert!(!attributes.contains(SlotAttribute::WRITABLE)); - attributes.set(SlotAttribute::GET, get.is_some()); - attributes.set(SlotAttribute::SET, set.is_some()); + let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); + debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); + attributes.set(SlotAttributes::GET, get.is_some()); + attributes.set(SlotAttributes::SET, set.is_some()); let key = key.into(); @@ -815,7 +815,7 @@ impl BuiltInCallable<'_> { let object = self.intrinsics.templates().function().create( ObjectData::function(function), - vec![JsValue::new(self.name), JsValue::new(self.length)], + vec![JsValue::new(self.length), JsValue::new(self.name)], ); JsFunction::from_object_unchecked(object) diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 0d03b0337ff..2d1a3640315 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -1003,11 +1003,11 @@ impl ObjectTemplates { let name_property_key: PropertyKey = "name".into(); let mut function = ObjectTemplate::new(root_shape); function.property( - name_property_key, + length_property_key.clone(), Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); function.property( - length_property_key.clone(), + name_property_key, Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index e767cecf785..992b0f96a3d 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -2028,7 +2028,7 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> { pub fn build(self) -> JsFunction { let object = self.context.intrinsics().templates().function().create( ObjectData::function(self.function), - vec![self.name.into(), self.length.into()], + vec![self.length.into(), self.name.into()], ); JsFunction::from_object_unchecked(object) diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index 11877636696..a7fd066d66e 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -2,7 +2,7 @@ use super::{ shape::{ property_table::PropertyTableInner, shared_shape::TransitionKey, - slot::{Slot, SlotAttribute}, + slot::{Slot, SlotAttributes}, ChangeTransitionAction, Shape, UniqueShape, }, JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey, @@ -276,11 +276,11 @@ impl PropertyMap { /// Get the property with the given key from the [`PropertyMap`]. #[must_use] - pub fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor { + pub(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor { let index = index as usize; let mut builder = PropertyDescriptor::builder() - .configurable(attributes.contains(SlotAttribute::CONFIGURABLE)) - .enumerable(attributes.contains(SlotAttribute::ENUMERABLE)); + .configurable(attributes.contains(SlotAttributes::CONFIGURABLE)) + .enumerable(attributes.contains(SlotAttributes::ENUMERABLE)); if attributes.is_accessor_descriptor() { if attributes.has_get() { builder = builder.get(self.storage[index].clone()); @@ -289,7 +289,7 @@ impl PropertyMap { builder = builder.set(self.storage[index + 1].clone()); } } else { - builder = builder.writable(attributes.contains(SlotAttribute::WRITABLE)); + builder = builder.writable(attributes.contains(SlotAttributes::WRITABLE)); builder = builder.value(self.storage[index].clone()); } builder.build() diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index c23560743d2..9982e004af4 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod property_table; pub(crate) mod shared_shape; pub(crate) mod slot; -mod unique_shape; +pub(crate) mod unique_shape; pub use shared_shape::SharedShape; pub(crate) use unique_shape::UniqueShape; @@ -182,8 +182,8 @@ impl Shape { } } - /// Get the property of the [`Shape`]. - pub(crate) fn prototype(&self) -> JsPrototype { + /// Get the [`JsPrototype`] of the [`Shape`]. + pub fn prototype(&self) -> JsPrototype { match &self.inner { Inner::Shared(shape) => shape.prototype(), Inner::Unique(shape) => shape.prototype(), @@ -192,14 +192,14 @@ impl Shape { /// Lookup a property in the shape #[inline] - pub fn lookup(&self, key: &PropertyKey) -> Option { + pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { match &self.inner { Inner::Shared(shape) => shape.lookup(key), Inner::Unique(shape) => shape.lookup(key), } } - /// + /// Returns the keys of the [`Shape`], in insertion order. #[inline] pub fn keys(&self) -> Vec { match &self.inner { diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs index ecd2aba167a..cb2d7513042 100644 --- a/boa_engine/src/object/shape/property_table.rs +++ b/boa_engine/src/object/shape/property_table.rs @@ -3,7 +3,7 @@ use std::{cell::RefCell, rc::Rc}; use rustc_hash::FxHashMap; use crate::{ - object::shape::slot::{Slot, SlotAttribute}, + object::shape::slot::{Slot, SlotAttributes}, property::PropertyKey, }; @@ -15,22 +15,24 @@ pub(crate) struct PropertyTableInner { } impl PropertyTableInner { + /// Returns all the keys, in insertion order. pub(crate) fn keys(&self) -> Vec { - self.keys_count(self.keys.len() as u32) + self.keys_cloned_n(self.keys.len() as u32) } - pub(crate) fn keys_count(&self, count: u32) -> Vec { - let count = count as usize; + /// Returns `n` cloned keys, in insertion order. + pub(crate) fn keys_cloned_n(&self, n: u32) -> Vec { + let n = n as usize; self.keys .iter() - .take(count) + .take(n) .map(|(key, _)| key) .filter(|key| matches!(key, PropertyKey::String(_))) .chain( self.keys .iter() - .take(count) + .take(n) .map(|(key, _)| key) .filter(|key| matches!(key, PropertyKey::Symbol(_))), ) @@ -38,22 +40,23 @@ impl PropertyTableInner { .collect() } - pub(crate) fn clone_count(&self, count: u32) -> Self { - let count = count as usize; + /// Returns a new table with `n` cloned properties. + pub(crate) fn clone_count(&self, n: u32) -> Self { + let n = n as usize; - let mut keys = Vec::with_capacity(count); + let mut keys = Vec::with_capacity(n); let mut map = FxHashMap::default(); - for (key, slot) in self.keys.iter().take(count) { - let index = keys.len() as u32; + for (index, (key, slot)) in self.keys.iter().take(n).enumerate() { keys.push((key.clone(), *slot)); - map.insert(key.clone(), (index, *slot)); + map.insert(key.clone(), (index as u32, *slot)); } Self { map, keys } } - pub(crate) fn insert(&mut self, key: PropertyKey, attributes: SlotAttribute) { + /// Insert a property entry into the table. + pub(crate) fn insert(&mut self, key: PropertyKey, attributes: SlotAttributes) { let slot = Slot::from_previous(self.keys.last().map(|x| x.1), attributes); let index = self.keys.len() as u32; self.keys.push((key.clone(), slot)); @@ -71,6 +74,7 @@ pub(crate) struct PropertyTable { } impl PropertyTable { + /// Returns the inner representation of a [`PropertyTable`]. pub(super) fn inner(&self) -> &RefCell { &self.inner } @@ -80,15 +84,9 @@ impl PropertyTable { pub(crate) fn add_property_deep_clone_if_needed( &self, key: PropertyKey, - attributes: SlotAttribute, + attributes: SlotAttributes, property_count: u32, ) -> Self { - // TODO: possible optimization if we are the only ones holding a reference to self we can add the property directly. - // TOOD: possible optimization if we already have the property in the exact position we want it to be with the same properties, - // then just return self, this might happen, if a branch is created than after not being used gets gc collected we still have the - // properties here. - // TODO: possible optimization, figure out if there **always** are as many properties as there are strong references. If so truncate - // on drop Rc ref drop, maybe? { let mut inner = self.inner.borrow_mut(); if (property_count as usize) == inner.keys.len() && !inner.map.contains_key(&key) { @@ -106,10 +104,10 @@ impl PropertyTable { this } - /// Deep clone the secified amount from the [`PropertyTable`] in insertion order. - pub(crate) fn deep_clone(&self, count: u32) -> Self { + /// Deep clone the [`PropertyTable`] in insertion order with the first n properties. + pub(crate) fn deep_clone(&self, n: u32) -> Self { Self { - inner: Rc::new(RefCell::new(self.inner.borrow().clone_count(count))), + inner: Rc::new(RefCell::new(self.inner.borrow().clone_count(n))), } } @@ -120,11 +118,11 @@ impl PropertyTable { } } - /// Change the attribue of a property. + /// Change the attributes of a property. pub(crate) fn set_attributes_at_index( &self, key: &PropertyKey, - property_attributes: SlotAttribute, + property_attributes: SlotAttributes, ) { let mut inner = self.inner.borrow_mut(); let Some((index, slot)) = inner.map.get_mut(key) else { diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index 49c8dc03f0b..0908bc1235f 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -12,15 +12,15 @@ use crate::{object::JsPrototype, property::PropertyKey, JsObject}; use self::forward_transition::ForwardTransition; use super::{ - property_table::PropertyTable, slot::SlotAttribute, ChangeTransition, ChangeTransitionAction, + property_table::PropertyTable, slot::SlotAttributes, ChangeTransition, ChangeTransitionAction, Slot, UniqueShape, }; -/// +/// Represent a [`SharedShape`] property transition. #[derive(Debug, Finalize, Clone, PartialEq, Eq, Hash)] pub(crate) struct TransitionKey { pub(crate) property_key: PropertyKey, - pub(crate) attributes: SlotAttribute, + pub(crate) attributes: SlotAttributes, } // SAFETY: Non of the member of this struct are garbage collected, @@ -140,7 +140,7 @@ impl SharedShape { pub fn prototype(&self) -> JsPrototype { self.inner.prototype.clone() } - /// Get the property this [`SharedShape`] referes to. + /// Get the property this [`SharedShape`] refers to. pub(crate) fn property(&self) -> (PropertyKey, Slot) { let inner = self.property_table().inner().borrow(); let (key, slot) = inner @@ -157,7 +157,7 @@ impl SharedShape { fn forward_transitions(&self) -> &ForwardTransition { &self.inner.forward_transitions } - /// Check if the prototype has the given prototype. + /// Check if the shape has the given prototype. pub fn has_prototype(&self, prototype: &JsObject) -> bool { self.inner .prototype @@ -357,10 +357,10 @@ impl SharedShape { ) -> ( SharedShape, Option, - IndexMap, + IndexMap, ) { let mut prototype = None; - let mut transitions: IndexMap = + let mut transitions: IndexMap = IndexMap::default(); let mut current = Some(self); @@ -447,7 +447,7 @@ impl SharedShape { /// Gets all keys first strings then symbols in creation order. pub(crate) fn keys(&self) -> Vec { let property_table = self.property_table().inner().borrow(); - property_table.keys_count(self.property_count()) + property_table.keys_cloned_n(self.property_count()) } /// Returns a new [`UniqueShape`] with the properties of the [`SharedShape`]. @@ -461,6 +461,7 @@ impl SharedShape { ) } + /// Return location in memory of the [`UniqueShape`]. pub(crate) fn to_addr_usize(&self) -> usize { let ptr: *const _ = self.inner.as_ref(); ptr as usize diff --git a/boa_engine/src/object/shape/shared_shape/template.rs b/boa_engine/src/object/shape/shared_shape/template.rs index 5a271dc5c1a..76ce369de92 100644 --- a/boa_engine/src/object/shape/shared_shape/template.rs +++ b/boa_engine/src/object/shape/shared_shape/template.rs @@ -3,7 +3,7 @@ use thin_vec::ThinVec; use crate::{ object::{ - shape::{slot::SlotAttribute, Shape}, + shape::{slot::SlotAttributes, Shape}, JsObject, Object, ObjectData, PropertyMap, }, property::{Attribute, PropertyKey}, @@ -12,6 +12,8 @@ use crate::{ use super::{SharedShape, TransitionKey}; +/// Represent a template of an objects properties and prototype. +/// This is used to construct as many objects as needed from a predefined [`SharedShape`]. #[derive(Debug, Clone)] pub(crate) struct ObjectTemplate { shape: SharedShape, @@ -19,6 +21,7 @@ pub(crate) struct ObjectTemplate { } impl ObjectTemplate { + /// Create a new [`ObjectTemplate`] pub(crate) fn new(root_shape: &SharedShape) -> Self { Self { shape: root_shape.clone(), @@ -26,6 +29,7 @@ impl ObjectTemplate { } } + /// Create and [`ObjectTemplate`] with a prototype. pub(crate) fn with_prototype(root_shape: &SharedShape, prototype: JsObject) -> Self { let shape = root_shape.change_prototype_transition(Some(prototype)); Self { @@ -34,20 +38,27 @@ impl ObjectTemplate { } } + /// Check if the shape has a specific, prototype. pub(crate) fn has_prototype(&self, prototype: &JsObject) -> bool { self.shape.has_prototype(prototype) } + /// Set the prototype of the [`ObjectTemplate`]. + /// + /// This assumes that the prototype has not been set yet. pub(crate) fn set_prototype(&mut self, prototype: JsObject) -> &mut Self { self.shape = self.shape.change_prototype_transition(Some(prototype)); self } + /// Add a data property to the [`ObjectTemplate`]. + /// + /// This assumes that the property with the given key was not previously set + /// and that it's a string or symbol. pub(crate) fn property(&mut self, key: PropertyKey, attributes: Attribute) -> &mut Self { - // TOOD: We don't support indexed keys. debug_assert!(!matches!(&key, PropertyKey::Index(_))); - let attributes = SlotAttribute::from_bits_truncate(attributes.bits()); + let attributes = SlotAttributes::from_bits_truncate(attributes.bits()); self.shape = self.shape.insert_property_transition(TransitionKey { property_key: key, attributes, @@ -56,6 +67,10 @@ impl ObjectTemplate { self } + /// Add a accessor property to the [`ObjectTemplate`]. + /// + /// This assumes that the property with the given key was not previously set + /// and that it's a string or symbol. pub(crate) fn accessor( &mut self, key: PropertyKey, @@ -67,18 +82,18 @@ impl ObjectTemplate { debug_assert!(!matches!(&key, PropertyKey::Index(_))); let attributes = { - let mut result = SlotAttribute::empty(); + let mut result = SlotAttributes::empty(); result.set( - SlotAttribute::CONFIGURABLE, + SlotAttributes::CONFIGURABLE, attributes.contains(Attribute::CONFIGURABLE), ); result.set( - SlotAttribute::ENUMERABLE, + SlotAttributes::ENUMERABLE, attributes.contains(Attribute::ENUMERABLE), ); - result.set(SlotAttribute::GET, get); - result.set(SlotAttribute::SET, set); + result.set(SlotAttributes::GET, get); + result.set(SlotAttributes::SET, set); result }; @@ -91,16 +106,14 @@ impl ObjectTemplate { self } - pub(crate) fn create_with_index_properties( - &self, - data: ObjectData, - storage: Vec, - elements: ThinVec, - ) -> JsObject { + /// Create an object from the [`ObjectTemplate`] + /// + /// The storage must match the properties provided. + pub(crate) fn create(&self, data: ObjectData, storage: Vec) -> JsObject { let mut object = Object { data, extensible: true, - properties: PropertyMap::new(Shape::shared(self.shape.clone()), elements), + properties: PropertyMap::new(Shape::shared(self.shape.clone()), ThinVec::default()), private_elements: ThinVec::new(), }; @@ -111,11 +124,20 @@ impl ObjectTemplate { Gc::new(GcRefCell::new(object)).into() } - pub(crate) fn create(&self, data: ObjectData, storage: Vec) -> JsObject { + /// Create an object from the [`ObjectTemplate`] + /// + /// The storage must match the properties provided. It does not apply to + /// the indexed propeties. + pub(crate) fn create_with_indexed_properties( + &self, + data: ObjectData, + storage: Vec, + elements: ThinVec, + ) -> JsObject { let mut object = Object { data, extensible: true, - properties: PropertyMap::new(Shape::shared(self.shape.clone()), ThinVec::default()), + properties: PropertyMap::new(Shape::shared(self.shape.clone()), elements), private_elements: ThinVec::new(), }; diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index ecff6cc1098..702ff06d0ff 100644 --- a/boa_engine/src/object/shape/slot.rs +++ b/boa_engine/src/object/shape/slot.rs @@ -4,7 +4,7 @@ pub(crate) type SlotIndex = u32; bitflags! { /// Attributes of a slot. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub struct SlotAttribute: u8 { + pub(crate) struct SlotAttributes: u8 { const WRITABLE = 0b0000_0001; const ENUMERABLE = 0b0000_0010; const CONFIGURABLE = 0b0000_0100; @@ -13,29 +13,26 @@ bitflags! { } } -impl SlotAttribute { - pub const fn is_accessor_descriptor(self) -> bool { +impl SlotAttributes { + pub(crate) const fn is_accessor_descriptor(self) -> bool { self.contains(Self::GET) || self.contains(Self::SET) } - pub const fn is_data_descriptor(self) -> bool { - !self.is_accessor_descriptor() - } - pub const fn has_get(self) -> bool { + pub(crate) const fn has_get(self) -> bool { self.contains(Self::GET) } - pub const fn has_set(self) -> bool { + pub(crate) const fn has_set(self) -> bool { self.contains(Self::SET) } /// Check if slot type width matches, this can only happens, /// if they are both accessors, or both data properties. - pub const fn width_match(self, other: Self) -> bool { + pub(crate) const fn width_match(self, other: Self) -> bool { self.is_accessor_descriptor() == other.is_accessor_descriptor() } /// Get the width of the slot. - pub fn width(self) -> u32 { + pub(crate) fn width(self) -> u32 { // accessor take 2 positions in the storage to accomodate for the `get` and `set` fields. 1 + u32::from(self.is_accessor_descriptor()) } @@ -43,12 +40,12 @@ impl SlotAttribute { /// Represents an [`u32`] index and a [`SlotAttribute`] field of an element in a dense storage. /// -/// Slots can be diffent width depending on its attributes, accessors properties have width `2`, +/// Slots can have different width depending on its attributes, accessors properties have width `2`, /// while data properties have width `1`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Slot { - pub index: SlotIndex, - pub attributes: SlotAttribute, +pub(crate) struct Slot { + pub(crate) index: SlotIndex, + pub(crate) attributes: SlotAttributes, } impl Slot { @@ -62,7 +59,7 @@ impl Slot { /// This is needed because slots do not have the same width. pub(crate) fn from_previous( previous_slot: Option, - new_attributes: SlotAttribute, + new_attributes: SlotAttributes, ) -> Self { // If there was no previous slot then return 0 as the index. let Some(previous_slot) = previous_slot else { diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index 1e7f8365f1b..5e052fb02c7 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -119,7 +119,6 @@ impl UniqueShape { /// Does a property lookup on the [`UniqueShape`] returning the [`Slot`] where it's /// located or [`None`] otherwise. pub(crate) fn lookup(&self, key: &PropertyKey) -> Option { - // println!("Unique: lookup {key}"); let property_table = self.property_table().borrow(); if let Some((_, slot)) = property_table.map.get(key) { return Some(*slot); @@ -234,6 +233,7 @@ impl UniqueShape { self.property_table().borrow().keys() } + /// Return location in memory of the [`UniqueShape`]. pub(crate) fn to_addr_usize(&self) -> usize { let ptr: *const _ = self.inner.as_ref(); ptr as usize diff --git a/boa_engine/src/property/mod.rs b/boa_engine/src/property/mod.rs index 83b5b6ac813..f0f7bf26afe 100644 --- a/boa_engine/src/property/mod.rs +++ b/boa_engine/src/property/mod.rs @@ -17,7 +17,7 @@ mod attribute; -use crate::{js_string, object::shape::slot::SlotAttribute, JsString, JsSymbol, JsValue}; +use crate::{js_string, object::shape::slot::SlotAttributes, JsString, JsSymbol, JsValue}; use boa_gc::{Finalize, Trace}; use std::fmt; @@ -339,15 +339,15 @@ impl PropertyDescriptor { } } - pub(crate) fn to_slot_attributes(&self) -> SlotAttribute { - let mut attributes = SlotAttribute::empty(); - attributes.set(SlotAttribute::CONFIGURABLE, self.expect_configurable()); - attributes.set(SlotAttribute::ENUMERABLE, self.expect_enumerable()); + pub(crate) fn to_slot_attributes(&self) -> SlotAttributes { + let mut attributes = SlotAttributes::empty(); + attributes.set(SlotAttributes::CONFIGURABLE, self.expect_configurable()); + attributes.set(SlotAttributes::ENUMERABLE, self.expect_enumerable()); if self.is_data_descriptor() { - attributes.set(SlotAttribute::WRITABLE, self.expect_writable()); + attributes.set(SlotAttributes::WRITABLE, self.expect_writable()); } else { - attributes.set(SlotAttribute::GET, self.get().is_some()); - attributes.set(SlotAttribute::SET, self.set().is_some()); + attributes.set(SlotAttributes::GET, self.get().is_some()); + attributes.set(SlotAttributes::SET, self.set().is_some()); } attributes } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index a0c6aa14de5..0ce5b84b7a2 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -600,7 +600,7 @@ pub(crate) fn create_function_object( let (mut template, storage, constructor_prototype) = if r#async || arrow || method { ( templates.function_without_proto().clone(), - vec![name, length], + vec![length, name], None, ) } else { @@ -612,7 +612,7 @@ pub(crate) fn create_function_object( ( template.clone(), - vec![name, length, constructor_prototype.clone().into()], + vec![length, name, constructor_prototype.clone().into()], Some(constructor_prototype), ) }; @@ -677,13 +677,13 @@ pub(crate) fn create_function_object_fast( .intrinsics() .templates() .async_function() - .create(data, vec![name, length]) + .create(data, vec![length, name]) } else if arrow || method { context .intrinsics() .templates() .function() - .create(data, vec![name, length]) + .create(data, vec![length, name]) } else { let prototype = context .intrinsics() @@ -695,7 +695,7 @@ pub(crate) fn create_function_object_fast( .intrinsics() .templates() .function_with_prototype() - .create(data, vec![name, length, prototype.clone().into()]); + .create(data, vec![length, name, prototype.clone().into()]); prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); diff --git a/docs/shapes.md b/docs/shapes.md index 93fa38d76cf..dd9af8db401 100644 --- a/docs/shapes.md +++ b/docs/shapes.md @@ -92,7 +92,7 @@ Let's define a getter and setter `y` ```js // What the getter/setter are not important! -Object.defineProperty(obj, "y", { +Object.defineProperty(o, "y", { enumerable: true, configurable: true, get: function () { @@ -118,7 +118,7 @@ flowchart LR InsertX(S2: Insert Shape\nProperty: 'x') --> |Property Count: 1| PropertyTable InsertX -->|Previous| ObjectPrototype - InsertY(S2: Insert Shape\nProperty: 'y') --> |Property Count: 2| PropertyTable + InsertY(S3: Insert Shape\nProperty: 'y') --> |Property Count: 2| PropertyTable InsertY:::New -->|Previous| InsertX O(Object o\nElement 0: JsValue: 100\nElement 1: JsValue: func\nElement 2: JsValue: func) --> InsertY From d09a813d003a06b18531eb473ed0a8ec86d7a6b5 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Wed, 22 Mar 2023 01:11:58 +0100 Subject: [PATCH 18/20] Implement basic inline caching --- boa_engine/src/builtins/weak/weak_ref.rs | 2 - .../declaration/declaration_pattern.rs | 15 ++- .../src/bytecompiler/expression/assign.rs | 12 +- boa_engine/src/bytecompiler/expression/mod.rs | 4 +- .../src/bytecompiler/expression/update.rs | 12 +- boa_engine/src/bytecompiler/mod.rs | 62 +++++++--- boa_engine/src/object/internal_methods/mod.rs | 111 +++++++++++++++++- .../src/object/internal_methods/proxy.rs | 4 + boa_engine/src/object/shape/mod.rs | 1 + boa_engine/src/vm/code_block.rs | 85 ++++++++++++-- boa_engine/src/vm/flowgraph/mod.rs | 24 +++- boa_engine/src/vm/mod.rs | 27 ++++- boa_engine/src/vm/opcode/get/property.rs | 42 ++++++- boa_engine/src/vm/opcode/mod.rs | 2 +- .../src/vm/opcode/set/class_prototype.rs | 1 + 15 files changed, 347 insertions(+), 57 deletions(-) diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs index 4cbf3f94277..80067294875 100644 --- a/boa_engine/src/builtins/weak/weak_ref.rs +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -162,8 +162,6 @@ mod tests { TestAction::inspect_context(|context| { context.clear_kept_objects(); boa_gc::force_collect(); - boa_gc::force_collect(); - boa_gc::force_collect(); }), TestAction::assert_eq("ptr.deref()", JsValue::undefined()), ]); diff --git a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs index ea655f87095..7a75a7e9441 100644 --- a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs +++ b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs @@ -38,8 +38,9 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*name).into()); + + self.emit_get_property_by_name(literal_index); } PropertyName::Computed(node) => { self.compile_expr(node, true); @@ -109,8 +110,9 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*name).into()); + + self.emit_get_property_by_name(literal_index); } PropertyName::Computed(node) => { self.compile_expr(node, true); @@ -148,8 +150,9 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*name).into()); + + self.emit_get_property_by_name(literal_index); } PropertyName::Computed(node) => { self.compile_expr(node, true); diff --git a/boa_engine/src/bytecompiler/expression/assign.rs b/boa_engine/src/bytecompiler/expression/assign.rs index c046d670815..13d2c2a3a6d 100644 --- a/boa_engine/src/bytecompiler/expression/assign.rs +++ b/boa_engine/src/bytecompiler/expression/assign.rs @@ -76,12 +76,12 @@ impl ByteCompiler<'_, '_> { Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); + let literal_index = self.get_or_insert_name((*name).into()); self.compile_expr(access.target(), true); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(literal_index); if short_circuit { pop_count = 2; early_exit = Some(self.emit_opcode_with_operand(opcode)); @@ -91,7 +91,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(opcode); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit(Opcode::SetPropertyByName, &[literal_index]); if !use_expr { self.emit_opcode(Opcode::Pop); } @@ -139,13 +139,13 @@ impl ByteCompiler<'_, '_> { } PropertyAccess::Super(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); + let literal_index = self.get_or_insert_name((*name).into()); self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::This); self.emit_opcode(Opcode::Swap); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(literal_index); if short_circuit { pop_count = 2; early_exit = Some(self.emit_opcode_with_operand(opcode)); @@ -155,7 +155,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(opcode); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit(Opcode::SetPropertyByName, &[literal_index]); if !use_expr { self.emit_opcode(Opcode::Pop); } diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index 4c285e71878..16d21167825 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -200,8 +200,8 @@ impl ByteCompiler<'_, '_> { self.emit(Opcode::Dup, &[]); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*field).into()); + self.emit_get_property_by_name(literal_index); } PropertyAccessField::Expr(field) => { self.compile_expr(field, true); diff --git a/boa_engine/src/bytecompiler/expression/update.rs b/boa_engine/src/bytecompiler/expression/update.rs index 1249c6a372c..e387f84dd72 100644 --- a/boa_engine/src/bytecompiler/expression/update.rs +++ b/boa_engine/src/bytecompiler/expression/update.rs @@ -40,19 +40,19 @@ impl ByteCompiler<'_, '_> { Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); + let literal_index = self.get_or_insert_name((*name).into()); self.compile_expr(access.target(), true); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(literal_index); self.emit_opcode(opcode); if post { self.emit_opcode(Opcode::RotateRight); self.emit_u8(4); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit(Opcode::SetPropertyByName, &[literal_index]); if post { self.emit_opcode(Opcode::Pop); } @@ -94,20 +94,20 @@ impl ByteCompiler<'_, '_> { } PropertyAccess::Super(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); + let literal_index = self.get_or_insert_name((*name).into()); self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::This); self.emit_opcode(Opcode::Swap); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(literal_index); self.emit_opcode(opcode); if post { self.emit_opcode(Opcode::RotateRight); self.emit_u8(3); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit(Opcode::SetPropertyByName, &[literal_index]); if post { self.emit_opcode(Opcode::Pop); } diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 84868ca95fc..907c314a9d9 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -9,10 +9,12 @@ mod module; mod statement; mod utils; +use std::cell::Cell; + use crate::{ builtins::function::ThisMode, environments::{BindingLocator, CompileTimeEnvironment}, - vm::{BindingOpcode, CodeBlock, Opcode}, + vm::{BindingOpcode, CodeBlock, InlineCache, Opcode}, Context, JsBigInt, JsString, JsValue, }; use boa_ast::{ @@ -234,7 +236,7 @@ pub struct ByteCompiler<'b, 'host> { pub(crate) params: FormalParameterList, /// Bytecode - pub(crate) bytecode: Vec, + pub(crate) bytecode: Vec>, /// Literals pub(crate) literals: Vec, @@ -272,6 +274,8 @@ pub struct ByteCompiler<'b, 'host> { /// When the execution of the parameter expressions throws an error, we do not need to pop the function environment. pub(crate) function_environment_push_location: u32, + pub(crate) ic: Vec, + literals_map: FxHashMap, names_map: FxHashMap, private_names_map: FxHashMap, @@ -313,6 +317,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { is_class_constructor: false, class_field_initializer_name: None, function_environment_push_location: 0, + ic: Vec::default(), literals_map: FxHashMap::default(), names_map: FxHashMap::default(), @@ -446,16 +451,32 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { } } + fn emit_get_property_by_name(&mut self, literal_index: u32) { + let ic_index = self.ic.len() as u32; + self.ic.push(InlineCache::new(literal_index)); + + self.emit_opcode(Opcode::GetPropertyByName); + + self.emit_u32(ic_index); + self.emit_usize(0); + self.emit_u32(0); + self.emit_u8(0); + } + + fn emit_usize(&mut self, value: usize) { + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); + } + fn emit_u64(&mut self, value: u64) { - self.bytecode.extend(value.to_ne_bytes()); + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); } fn emit_u32(&mut self, value: u32) { - self.bytecode.extend(value.to_ne_bytes()); + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); } fn emit_u16(&mut self, value: u16) { - self.bytecode.extend(value.to_ne_bytes()); + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); } fn emit_opcode(&mut self, opcode: Opcode) { @@ -463,7 +484,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { } fn emit_u8(&mut self, value: u8) { - self.bytecode.push(value); + self.bytecode.push(Cell::new(value)); } fn emit_push_integer(&mut self, value: i32) { @@ -550,11 +571,11 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { let Label { index } = label; let index = index as usize; - let bytes = target.to_ne_bytes(); + let bytes = target.to_ne_bytes().map(Cell::new); // This is done to avoid unneeded bounds checks. assert!(self.bytecode.len() > index + U32_SIZE && usize::MAX - U32_SIZE >= index); - self.bytecode[index + 1..=index + U32_SIZE].copy_from_slice(bytes.as_slice()); + self.bytecode[index + 1..=index + U32_SIZE].clone_from_slice(bytes.as_slice()); } fn patch_jump(&mut self, label: Label) { @@ -572,9 +593,9 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); + let literal_index = self.get_or_insert_name((*name).into()); self.compile_expr(access.target(), true); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(literal_index); } PropertyAccessField::Expr(expr) => { self.compile_expr(access.target(), true); @@ -589,9 +610,10 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { } PropertyAccess::Super(access) => match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); + let literal_index = self.get_or_insert_name((*field).into()); self.emit_opcode(Opcode::Super); - self.emit(Opcode::GetPropertyByName, &[index]); + + self.emit_get_property_by_name(literal_index); } PropertyAccessField::Expr(expr) => { self.emit_opcode(Opcode::Super); @@ -823,8 +845,9 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { self.emit_opcode(Opcode::Dup); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*field).into()); + + self.emit_get_property_by_name(literal_index); } PropertyAccessField::Expr(field) => { self.compile_expr(field, true); @@ -843,8 +866,9 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { self.emit_opcode(Opcode::Super); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*field).into()); + + self.emit_get_property_by_name(literal_index); } PropertyAccessField::Expr(expr) => { self.compile_expr(expr, true); @@ -928,8 +952,9 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { self.emit_opcode(Opcode::Dup); match field { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + let literal_index = self.get_or_insert_name((*name).into()); + + self.emit_get_property_by_name(literal_index); } PropertyAccessField::Expr(expr) => { self.compile_expr(expr, true); @@ -1327,6 +1352,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { is_class_constructor: self.is_class_constructor, class_field_initializer_name: self.class_field_initializer_name, function_environment_push_location: self.function_environment_push_location, + ic: self.ic.into_boxed_slice(), #[cfg(feature = "trace")] trace: std::cell::Cell::new(false), } diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 37976b2abd8..2bdf3aab716 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -5,7 +5,7 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -use super::{JsPrototype, PROTOTYPE}; +use super::{shape::slot::Slot, JsPrototype, PROTOTYPE}; use crate::{ context::intrinsics::{StandardConstructor, StandardConstructors}, object::JsObject, @@ -104,6 +104,28 @@ impl JsObject { func(self, key, context) } + /// Internal method `[[GetOwnProperty]]` + /// + /// Get the specified property of this object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p + pub(crate) fn __get_own_property_slot__( + &self, + slot: Slot, + context: &mut Context<'_>, + ) -> JsResult { + let _timer = Profiler::global().start_event("Object::__get_own_property_slot__", "object"); + let func = self + .borrow() + .data + .internal_methods + .__get_own_property_slot__; + func(self, slot, context) + } + /// Internal method `[[DefineOwnProperty]]` /// /// Define a new property of this object. @@ -160,6 +182,25 @@ impl JsObject { func(self, key, receiver, context) } + /// Internal method `[[Get]]` + /// + /// Get the specified property of this object or its prototype. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver + pub(crate) fn __get_slot__( + &self, + slot: Slot, + receiver: &JsValue, + context: &mut Context<'_>, + ) -> JsResult { + let _timer = Profiler::global().start_event("Object::__get__", "object"); + let func = self.borrow().data.internal_methods.__get_slot__; + func(self, slot, receiver, context) + } + /// Internal method `[[Set]]` /// /// Set the specified property of this object or its prototype to the provided value. @@ -286,6 +327,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj __own_property_keys__: ordinary_own_property_keys, __call__: None, __construct__: None, + __get_own_property_slot__: ordinary_get_own_property_slot, + __get_slot__: ordinary_get_slot, }; /// The internal representation of the internal methods of a `JsObject`. @@ -316,6 +359,10 @@ pub(crate) struct InternalObjectMethods { Option) -> JsResult>, pub(crate) __construct__: Option) -> JsResult>, + + pub(crate) __get_own_property_slot__: + fn(&JsObject, Slot, &mut Context<'_>) -> JsResult, + pub(crate) __get_slot__: fn(&JsObject, Slot, &JsValue, &mut Context<'_>) -> JsResult, } /// Abstract operation `OrdinaryGetPrototypeOf`. @@ -452,6 +499,36 @@ pub(crate) fn ordinary_get_own_property( Ok(obj.borrow().properties.get(key)) } +/// Abstract operation `OrdinaryGetOwnProperty`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty +#[allow(clippy::unnecessary_wraps)] +pub(crate) fn ordinary_get_own_property_slot( + obj: &JsObject, + slot: Slot, + _context: &mut Context<'_>, +) -> JsResult { + let _timer = Profiler::global().start_event("Object::ordinary_get_own_property", "object"); + // 1. Assert: IsPropertyKey(P) is true. + // 2. If O does not have an own property with key P, return undefined. + // 3. Let D be a newly created Property Descriptor with no fields. + // 4. Let X be O's own property whose key is P. + // 5. If X is a data property, then + // a. Set D.[[Value]] to the value of X's [[Value]] attribute. + // b. Set D.[[Writable]] to the value of X's [[Writable]] attribute. + // 6. Else, + // a. Assert: X is an accessor property. + // b. Set D.[[Get]] to the value of X's [[Get]] attribute. + // c. Set D.[[Set]] to the value of X's [[Set]] attribute. + // 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. + // 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute. + // 9. Return D. + Ok(obj.borrow().properties.get_storage(slot)) +} + /// Abstract operation `OrdinaryDefineOwnProperty`. /// /// More information: @@ -554,6 +631,38 @@ pub(crate) fn ordinary_get( } } +/// Abstract operation `OrdinaryGet`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-ordinaryget +pub(crate) fn ordinary_get_slot( + obj: &JsObject, + slot: Slot, + receiver: &JsValue, + context: &mut Context<'_>, +) -> JsResult { + let _timer = Profiler::global().start_event("Object::ordinary_get", "object"); + + let desc = obj.__get_own_property_slot__(slot, context)?; + + match desc.kind() { + // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]]. + DescriptorKind::Data { + value: Some(value), .. + } => Ok(value.clone()), + // 5. Assert: IsAccessorDescriptor(desc) is true. + // 6. Let getter be desc.[[Get]]. + DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { + // 8. Return ? Call(getter, Receiver). + get.call(receiver, &[], context) + } + // 7. If getter is undefined, return undefined. + _ => Ok(JsValue::undefined()), + } +} + /// Abstract operation `OrdinarySet`. /// /// More information: diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 6874d8d5d9d..453bfa94b62 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -30,6 +30,10 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods = __own_property_keys__: proxy_exotic_own_property_keys, __call__: None, __construct__: None, + __get_own_property_slot__: |_, _, _| { + todo!("Not implemented __get_own_property_slot__ not implemented") + }, + __get_slot__: |_, _, _, _| todo!("Not implemented proxy __get_slot__"), }; pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 9982e004af4..c82d5a9c00a 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -62,6 +62,7 @@ pub struct Shape { inner: Inner, } +/// The default [`Shape`] type is [`UniqueShape`]. impl Default for Shape { fn default() -> Self { Shape::unique(UniqueShape::default()) diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 0ce5b84b7a2..27ca6fe156e 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -12,7 +12,10 @@ use crate::{ context::intrinsics::StandardConstructors, environments::{BindingLocator, CompileTimeEnvironment}, error::JsNativeError, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PROTOTYPE}, + object::{ + internal_methods::get_prototype_from_constructor, shape::Shape, JsObject, ObjectData, + PROTOTYPE, + }, property::PropertyDescriptor, string::utf16, vm::CallFrame, @@ -25,7 +28,7 @@ use boa_ast::{ use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_interner::Sym; use boa_profiler::Profiler; -use std::{collections::VecDeque, mem::size_of}; +use std::{cell::Cell, collections::VecDeque, mem::size_of}; use thin_vec::ThinVec; #[cfg(any(feature = "trace", feature = "flowgraph"))] @@ -55,6 +58,25 @@ unsafe impl Readable for i64 {} unsafe impl Readable for f32 {} unsafe impl Readable for f64 {} +unsafe impl Readable for usize {} + +/// TODO: doc +#[derive(Clone, Debug, Trace, Finalize)] +#[allow(dead_code)] +pub struct InlineCache { + pub(crate) literal_index: u32, + pub(crate) shape: GcRefCell>, +} + +impl InlineCache { + pub(crate) const fn new(literal_index: u32) -> Self { + Self { + shape: GcRefCell::new(None), + literal_index, + } + } +} + /// The internal representation of a JavaScript function. /// /// A `CodeBlock` is generated for each function compiled by the @@ -83,7 +105,8 @@ pub struct CodeBlock { pub(crate) params: FormalParameterList, /// Bytecode - pub(crate) bytecode: Box<[u8]>, + #[unsafe_ignore_trace] + pub(crate) bytecode: Box<[Cell]>, /// Literals pub(crate) literals: Box<[JsValue]>, @@ -126,10 +149,13 @@ pub struct CodeBlock { /// When the execution of the parameter expressions throws an error, we do not need to pop the function environment. pub(crate) function_environment_push_location: u32, + /// inline caching + pub(crate) ic: Box<[InlineCache]>, + #[cfg(feature = "trace")] /// Trace instruction execution to `stdout`. #[unsafe_ignore_trace] - pub(crate) trace: std::cell::Cell, + pub(crate) trace: Cell, } impl CodeBlock { @@ -155,6 +181,7 @@ impl CodeBlock { is_class_constructor: false, class_field_initializer_name: None, function_environment_push_location: 0, + ic: Box::default(), #[cfg(feature = "trace")] trace: std::cell::Cell::new(false), } @@ -203,13 +230,37 @@ impl CodeBlock { unsafe { self.read_unchecked(offset) } } + /// TODO: doc + pub(crate) fn write_usize(&self, offset: usize, value: usize) { + let bytes = value.to_ne_bytes(); + for (bytecode, byte) in self.bytecode[offset..offset + size_of::()] + .iter() + .zip(bytes.into_iter()) + { + bytecode.set(byte); + } + } + + /// TODO: doc + pub(crate) fn write_u32(&self, offset: usize, value: u32) { + let bytes = value.to_ne_bytes(); + for (bytecode, byte) in self.bytecode[offset..offset + size_of::()] + .iter() + .zip(bytes.into_iter()) + { + bytecode.set(byte); + } + } + /// Get the operands after the `Opcode` pointed to by `pc` as a `String`. /// Modifies the `pc` to point to the next instruction. /// /// Returns an empty `String` if no operands are present. #[cfg(any(feature = "trace", feature = "flowgraph"))] pub(crate) fn instruction_operands(&self, pc: &mut usize, interner: &Interner) -> String { - let opcode: Opcode = self.bytecode[*pc].try_into().expect("invalid opcode"); + use crate::object::shape::slot::SlotAttributes; + + let opcode: Opcode = self.bytecode[*pc].get().try_into().expect("invalid opcode"); *pc += size_of::(); match opcode { Opcode::SetFunctionName => { @@ -322,8 +373,26 @@ impl CodeBlock { interner.resolve_expect(self.bindings[operand as usize].name().sym()), ) } - Opcode::GetPropertyByName - | Opcode::GetMethod + Opcode::GetPropertyByName => { + let operand = self.read::(*pc); + *pc += size_of::(); + let shape_ptr = self.read::(*pc); + *pc += size_of::(); + let slot_index = self.read::(*pc); + *pc += size_of::(); + let slot_attributes = self.read::(*pc); + *pc += size_of::(); + + let slot_attributes = + SlotAttributes::from_bits(slot_attributes).expect("should not fail!"); + + let ic = &self.ic[operand as usize]; + format!( + "{operand:04}: '{}', Shape: {shape_ptr}, Slot: index: {slot_index}, attributes {slot_attributes:?}", + interner.resolve_expect(self.names[ic.literal_index as usize].sym()), + ) + } + Opcode::GetMethod | Opcode::SetPropertyByName | Opcode::DefineOwnPropertyByName | Opcode::DefineClassStaticMethodByName @@ -490,7 +559,7 @@ impl ToInternedString for CodeBlock { let mut pc = 0; let mut count = 0; while pc < self.bytecode.len() { - let opcode: Opcode = self.bytecode[pc].try_into().expect("invalid opcode"); + let opcode: Opcode = self.bytecode[pc].get().try_into().expect("invalid opcode"); let opcode = opcode.as_str(); let previous_pc = pc; let operands = self.instruction_operands(&mut pc, interner); diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index bbeb1038a08..deb5eab32a7 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -32,7 +32,7 @@ impl CodeBlock { let mut pc = 0; while pc < self.bytecode.len() { - let opcode: Opcode = self.bytecode[pc].try_into().expect("invalid opcode"); + let opcode: Opcode = self.bytecode[pc].get().try_into().expect("invalid opcode"); let opcode_str = opcode.as_str(); let previous_pc = pc; @@ -380,8 +380,26 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::GetPropertyByName - | Opcode::GetMethod + Opcode::GetPropertyByName => { + // TODO: Implement better trace format + let operand = self.read::(pc); + pc += size_of::(); + let _shape_ptr = self.read::(pc); + pc += size_of::(); + let _slot_index = self.read::(pc); + pc += size_of::(); + let _slot_attributes = self.read::(pc); + pc += size_of::(); + + let ic = &self.ic[operand as usize]; + let label = format!( + "{opcode_str} '{}'", + interner.resolve_expect(self.names[ic.literal_index as usize].sym()), + ); + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); + } + Opcode::GetMethod | Opcode::SetPropertyByName | Opcode::DefineOwnPropertyByName | Opcode::DefineClassStaticMethodByName diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 2884f8e002a..c18552dd862 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -27,7 +27,11 @@ mod opcode; #[cfg(feature = "flowgraph")] pub mod flowgraph; -pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; +pub use { + call_frame::CallFrame, + code_block::{CodeBlock, InlineCache}, + opcode::Opcode, +}; pub(crate) use { call_frame::GeneratorResumeKind, @@ -88,6 +92,26 @@ impl Vm { value } + #[track_caller] + pub(crate) fn read_and_get_location(&mut self) -> (T, usize) { + let location = self.frame().pc; + let value = self.frame().code_block.read::(location); + self.frame_mut().pc += size_of::(); + (value, location) + } + + pub(crate) fn write_usize(&self, location: usize, value: usize) { + self.frame().code_block.write_usize(location, value); + } + + pub(crate) fn write_u32(&self, location: usize, value: u32) { + self.frame().code_block.write_u32(location, value); + } + + pub(crate) fn write_u8(&self, location: usize, value: u8) { + self.frame().code_block.bytecode[location].set(value); + } + /// Retrieves the VM frame /// /// # Panics @@ -127,6 +151,7 @@ impl Context<'_> { let opcode: Opcode = { let _timer = Profiler::global().start_event("Opcode retrieval", "vm"); let opcode = self.vm.frame().code_block.bytecode[self.vm.frame().pc] + .get() .try_into() .expect("could not convert code at PC to opcode"); self.vm.frame_mut().pc += 1; diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs index b5de8900fbd..84d88059738 100644 --- a/boa_engine/src/vm/opcode/get/property.rs +++ b/boa_engine/src/vm/opcode/get/property.rs @@ -1,5 +1,6 @@ use crate::{ js_string, + object::shape::slot::{Slot, SlotAttributes}, property::PropertyKey, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, @@ -17,7 +18,10 @@ impl Operation for GetPropertyByName { const INSTRUCTION: &'static str = "INST - GetPropertyByName"; fn execute(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); + let ic_index = context.vm.read::(); + let (shape_ptr, shape_ptr_location) = context.vm.read_and_get_location::(); + let (slot_index, slot_index_location) = context.vm.read_and_get_location::(); + let (slot_attributes, slot_attributes_locations) = context.vm.read_and_get_location::(); let value = context.vm.pop(); let object = if let Some(object) = value.as_object() { @@ -26,9 +30,41 @@ impl Operation for GetPropertyByName { value.to_object(context)? }; - let name = context.vm.frame().code_block.names[index as usize]; + // Use inline cache. + if shape_ptr == object.borrow().properties().shape.to_addr_usize() { + let slot = Slot { + index: slot_index, + attributes: SlotAttributes::from_bits_truncate(slot_attributes), + }; + let result = object.__get_slot__(slot, &value, context)?; + context.vm.push(result); + return Ok(CompletionType::Normal); + } + + let ic = &context.vm.frame().code_block.ic[ic_index as usize]; + let name = context.vm.frame().code_block.names[ic.literal_index as usize]; let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into(); - let result = object.__get__(&key, value, context)?; + + // Check if it can be cached, currently only direct/own properties. + let shape = object.borrow().properties().shape.clone(); + let Some(slot) = shape.lookup(&key) else { + let result = object.__get__(&key, value, context)?; + context.vm.push(result); + return Ok(CompletionType::Normal); + }; + + // Write inline to bytecode inline. + context + .vm + .write_usize(shape_ptr_location, shape.to_addr_usize()); + context.vm.write_u32(slot_index_location, slot.index); + context + .vm + .write_u8(slot_attributes_locations, slot.attributes.bits()); + + *ic.shape.borrow_mut() = Some(shape); + + let result = object.__get_slot__(slot, &value, context)?; context.vm.push(result); Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index f1cdded9f5e..70bc14724ae 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -712,7 +712,7 @@ generate_impl! { /// /// Like `object.name` /// - /// Operands: name_index: `u32` + /// Operands: inline_cache_index: `u32`, shape_ptr: usize, slot_index: u32, slot_attributes: u8 /// /// Stack: object **=>** value GetPropertyByName, diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index 9327e757d3b..a88fbcd663b 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -37,6 +37,7 @@ impl Operation for SetClassPrototype { prototype, ObjectData::ordinary(), ); + let class = context.vm.pop(); { From 5020d030b123016e8000fc1cfe97000b85507b82 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Sat, 8 Apr 2023 17:34:20 +0200 Subject: [PATCH 19/20] ... --- boa_engine/src/object/shape/slot.rs | 3 ++ boa_engine/src/vm/opcode/get/property.rs | 49 +++++++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index 702ff06d0ff..0ac65f3de34 100644 --- a/boa_engine/src/object/shape/slot.rs +++ b/boa_engine/src/object/shape/slot.rs @@ -10,6 +10,9 @@ bitflags! { const CONFIGURABLE = 0b0000_0100; const GET = 0b0000_1000; const SET = 0b0001_0000; + + const DIRECT = 0b0010_0000; + const NOT_CACHABLE = 0b0100_0000; } } diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs index 84d88059738..36a6d40dcc9 100644 --- a/boa_engine/src/vm/opcode/get/property.rs +++ b/boa_engine/src/vm/opcode/get/property.rs @@ -30,13 +30,36 @@ impl Operation for GetPropertyByName { value.to_object(context)? }; + if slot_attributes & SlotAttributes::NOT_CACHABLE.bits() != 0 { + let ic = &context.vm.frame().code_block.ic[ic_index as usize]; + let name = context.vm.frame().code_block.names[ic.literal_index as usize]; + let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into(); + let result = object.__get__(&key, value, context)?; + context.vm.push(result); + return Ok(CompletionType::Normal); + } + // Use inline cache. if shape_ptr == object.borrow().properties().shape.to_addr_usize() { let slot = Slot { index: slot_index, - attributes: SlotAttributes::from_bits_truncate(slot_attributes), + attributes: SlotAttributes::from_bits_retain(slot_attributes), + }; + let mut result = if slot.attributes.contains(SlotAttributes::DIRECT) { + object.borrow().properties().storage[slot.index as usize].clone() + } else { + let prototype = object + .borrow() + .properties() + .shape + .prototype() + .expect("prototype should have value"); + let prototype = prototype.borrow(); + prototype.properties().storage[slot.index as usize].clone() }; - let result = object.__get_slot__(slot, &value, context)?; + if slot.attributes.has_get() { + result = result.call(&value, &[], context)?; + } context.vm.push(result); return Ok(CompletionType::Normal); } @@ -45,9 +68,24 @@ impl Operation for GetPropertyByName { let name = context.vm.frame().code_block.names[ic.literal_index as usize]; let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into(); - // Check if it can be cached, currently only direct/own properties. + // Check if it can be cached. let shape = object.borrow().properties().shape.clone(); - let Some(slot) = shape.lookup(&key) else { + let (slot, result) = if let Some(mut slot) = shape.lookup(&key) { + slot.attributes |= SlotAttributes::DIRECT; + let result = object.borrow().properties().storage[slot.index as usize].clone() + (slot, result) + } else if let Some(prototype) = shape.prototype() + { + let Some(slot) = prototype.borrow().properties().shape.lookup(&key) else { + let result = object.__get__(&key, value, context)?; + context.vm.push(result); + return Ok(CompletionType::Normal); + }; + + slot.attributes |= SlotAttributes::PROTOTYPE; + + (slot, result) + } else { let result = object.__get__(&key, value, context)?; context.vm.push(result); return Ok(CompletionType::Normal); @@ -64,8 +102,7 @@ impl Operation for GetPropertyByName { *ic.shape.borrow_mut() = Some(shape); - let result = object.__get_slot__(slot, &value, context)?; - + let result = object.__get__(&key, value, context)?; context.vm.push(result); Ok(CompletionType::Normal) } From 604489f5ec059ea3fbba54fb8559c8f22364d6d6 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Sat, 8 Apr 2023 21:07:39 +0200 Subject: [PATCH 20/20] inline cache prototype --- boa_engine/src/vm/opcode/get/property.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs index 36a6d40dcc9..69f71a156ec 100644 --- a/boa_engine/src/vm/opcode/get/property.rs +++ b/boa_engine/src/vm/opcode/get/property.rs @@ -31,6 +31,7 @@ impl Operation for GetPropertyByName { }; if slot_attributes & SlotAttributes::NOT_CACHABLE.bits() != 0 { + // TODO: passible optimization, inline sym into bytecode, since it fits in the shape pointer. let ic = &context.vm.frame().code_block.ic[ic_index as usize]; let name = context.vm.frame().code_block.names[ic.literal_index as usize]; let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into(); @@ -70,21 +71,12 @@ impl Operation for GetPropertyByName { // Check if it can be cached. let shape = object.borrow().properties().shape.clone(); - let (slot, result) = if let Some(mut slot) = shape.lookup(&key) { + let slot = if let Some(mut slot) = shape.lookup(&key) { slot.attributes |= SlotAttributes::DIRECT; - let result = object.borrow().properties().storage[slot.index as usize].clone() - (slot, result) - } else if let Some(prototype) = shape.prototype() - { - let Some(slot) = prototype.borrow().properties().shape.lookup(&key) else { - let result = object.__get__(&key, value, context)?; - context.vm.push(result); - return Ok(CompletionType::Normal); - }; - - slot.attributes |= SlotAttributes::PROTOTYPE; - - (slot, result) + slot + } else if let Some(mut slot) = shape.prototype().and_then(|prototype| prototype.borrow().properties().shape.lookup(&key)) { + slot.attributes.set(SlotAttributes::DIRECT, false); + slot } else { let result = object.__get__(&key, value, context)?; context.vm.push(result);