Browse Source

Implement `Hidden classes` (#2723)

This PR implements `Hidden Classes`, I named them as `Shapes` (like Spidermonkey does), calling them maps like v8 seems confusing because there already is a JS builtin, likewise with `Hidden classes` since there are already classes in JS.

There are two types of shapes:  `shared` shapes that create the transition tree, and are shared between objects, this is mainly intended for user defined objects this makes more sense because shapes can create transitions trees, doing that for the builtins seems wasteful (unless users wanted  to creating an object with the same property names and the same property attributes in the same order... which seems unlikely). That's why I added  `unique` shapes, only one object has it. This is similar to previous solution, but this architecture enables us to use inline caching.

There will probably be a performance hit until we implement inline caching.

There still a lot of work that needs to be done, on this:

- [x] Move Property Attributes to shape
- [x] Move Prototype to shape
- [x] ~~Move extensible flag  to shape~~,  On further evaluation this doesn't give any benefit (at least right now), since it isn't used by inline caching also adding one more transition.
- [x] Implement delete for unique shapes.
- [x] If the chain is too long we should probably convert it into a `unique` shape
    - [x] Figure out threshold ~~(maybe more that 256 properties ?)~~ curently set to an arbitrary number (`1024`)
- [x] Implement shared property table between shared shapes
- [x] Add  code Document
- [x] Varying size storage for  properties (get+set = 2, data = 1)
- [x] Add shapes to more object:
    - [x] ordinary object
    - [x] Arrays
    - [x] Functions
    - [x] Other builtins
- [x] Add `shapes.md` doc explaining shapes in depth with mermaid diagrams :)
- [x] Add `$boa.shape` module
- [x] `$boa.shape.id(o)`
- [x] `$boa.shape.type(o)`
- [x] `$boa.shape.same(o1, o2)`
- [x] add doc to `boa_object.md`
pull/2872/head
Haled Odat 1 year ago
parent
commit
ed37448175
  1. 7
      boa_cli/src/debug/mod.rs
  2. 66
      boa_cli/src/debug/shape.rs
  3. 6
      boa_engine/benches/full.rs
  4. 3
      boa_engine/src/builtins/array/array_iterator.rs
  5. 80
      boa_engine/src/builtins/array/mod.rs
  6. 9
      boa_engine/src/builtins/array_buffer/mod.rs
  7. 6
      boa_engine/src/builtins/boolean/mod.rs
  8. 12
      boa_engine/src/builtins/dataview/mod.rs
  9. 12
      boa_engine/src/builtins/date/mod.rs
  10. 6
      boa_engine/src/builtins/error/aggregate.rs
  11. 6
      boa_engine/src/builtins/error/eval.rs
  12. 6
      boa_engine/src/builtins/error/mod.rs
  13. 6
      boa_engine/src/builtins/error/range.rs
  14. 6
      boa_engine/src/builtins/error/reference.rs
  15. 6
      boa_engine/src/builtins/error/syntax.rs
  16. 6
      boa_engine/src/builtins/error/type.rs
  17. 6
      boa_engine/src/builtins/error/uri.rs
  18. 6
      boa_engine/src/builtins/escape/mod.rs
  19. 3
      boa_engine/src/builtins/eval/mod.rs
  20. 172
      boa_engine/src/builtins/function/arguments.rs
  21. 24
      boa_engine/src/builtins/function/mod.rs
  22. 15
      boa_engine/src/builtins/intl/collator/mod.rs
  23. 9
      boa_engine/src/builtins/intl/date_time_format.rs
  24. 21
      boa_engine/src/builtins/intl/list_format/mod.rs
  25. 50
      boa_engine/src/builtins/intl/locale/mod.rs
  26. 6
      boa_engine/src/builtins/intl/options.rs
  27. 3
      boa_engine/src/builtins/intl/segmenter/iterator.rs
  28. 6
      boa_engine/src/builtins/intl/segmenter/mod.rs
  29. 3
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  30. 14
      boa_engine/src/builtins/iterable/mod.rs
  31. 3
      boa_engine/src/builtins/map/map_iterator.rs
  32. 15
      boa_engine/src/builtins/map/mod.rs
  33. 482
      boa_engine/src/builtins/mod.rs
  34. 12
      boa_engine/src/builtins/number/globals.rs
  35. 6
      boa_engine/src/builtins/number/mod.rs
  36. 3
      boa_engine/src/builtins/object/for_in_iterator.rs
  37. 15
      boa_engine/src/builtins/object/mod.rs
  38. 6
      boa_engine/src/builtins/promise/mod.rs
  39. 6
      boa_engine/src/builtins/proxy/mod.rs
  40. 36
      boa_engine/src/builtins/regexp/mod.rs
  41. 3
      boa_engine/src/builtins/regexp/regexp_string_iterator.rs
  42. 21
      boa_engine/src/builtins/set/mod.rs
  43. 3
      boa_engine/src/builtins/set/set_iterator.rs
  44. 12
      boa_engine/src/builtins/string/mod.rs
  45. 3
      boa_engine/src/builtins/string/string_iterator.rs
  46. 6
      boa_engine/src/builtins/symbol/mod.rs
  47. 35
      boa_engine/src/builtins/typed_array/mod.rs
  48. 12
      boa_engine/src/builtins/uri/mod.rs
  49. 8
      boa_engine/src/builtins/weak/weak_ref.rs
  50. 3
      boa_engine/src/builtins/weak_map/mod.rs
  51. 3
      boa_engine/src/builtins/weak_set/mod.rs
  52. 7
      boa_engine/src/class.rs
  53. 340
      boa_engine/src/context/intrinsics.rs
  54. 12
      boa_engine/src/context/mod.rs
  55. 6
      boa_engine/src/error.rs
  56. 3
      boa_engine/src/object/builtins/jsarraybuffer.rs
  57. 3
      boa_engine/src/object/builtins/jsdataview.rs
  58. 9
      boa_engine/src/object/builtins/jsdate.rs
  59. 6
      boa_engine/src/object/builtins/jsmap.rs
  60. 6
      boa_engine/src/object/builtins/jspromise.rs
  61. 3
      boa_engine/src/object/builtins/jsproxy.rs
  62. 23
      boa_engine/src/object/internal_methods/integer_indexed.rs
  63. 35
      boa_engine/src/object/internal_methods/mod.rs
  64. 21
      boa_engine/src/object/internal_methods/string.rs
  65. 53
      boa_engine/src/object/jsobject.rs
  66. 100
      boa_engine/src/object/mod.rs
  67. 549
      boa_engine/src/object/property_map.rs
  68. 217
      boa_engine/src/object/shape/mod.rs
  69. 149
      boa_engine/src/object/shape/property_table.rs
  70. 58
      boa_engine/src/object/shape/shared_shape/forward_transition.rs
  71. 469
      boa_engine/src/object/shape/shared_shape/mod.rs
  72. 141
      boa_engine/src/object/shape/shared_shape/template.rs
  73. 77
      boa_engine/src/object/shape/slot.rs
  74. 241
      boa_engine/src/object/shape/unique_shape.rs
  75. 2
      boa_engine/src/property/attribute/mod.rs
  76. 17
      boa_engine/src/property/mod.rs
  77. 6
      boa_engine/src/realm.rs
  78. 11
      boa_engine/src/value/conversions/serde_json.rs
  79. 26
      boa_engine/src/value/display.rs
  80. 2
      boa_engine/src/value/hash.rs
  81. 82
      boa_engine/src/value/mod.rs
  82. 1
      boa_engine/src/value/tests.rs
  83. 211
      boa_engine/src/vm/code_block.rs
  84. 10
      boa_engine/src/vm/opcode/get/function.rs
  85. 10
      boa_engine/src/vm/opcode/push/array.rs
  86. 8
      boa_engine/src/vm/opcode/push/object.rs
  87. 6
      boa_engine/src/vm/opcode/set/class_prototype.rs
  88. 3
      boa_engine/src/vm/opcode/set/property.rs
  89. 39
      docs/boa_object.md
  90. 220
      docs/shapes.md

7
boa_cli/src/debug/mod.rs

@ -8,10 +8,12 @@ mod gc;
mod object;
mod optimizer;
mod realm;
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);
let realm_module = realm::create_object(context);
@ -27,6 +29,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,

66
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<JsValue> {
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<JsValue> {
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<JsValue> {
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()
}

6
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))
});
}

3
boa_engine/src/builtins/array/array_iterator.rs

@ -84,7 +84,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.root_shape(),
context.intrinsics().objects().iterator_prototypes().array(),
ObjectData::array_iterator(Self::new(array, kind)),
);

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

@ -47,16 +47,15 @@ impl IntrinsicObject for Array {
let symbol_iterator = JsSymbol::iterator();
let symbol_unscopables = JsSymbol::unscopables();
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
let values_function = BuiltInBuilder::with_object(
let values_function = BuiltInBuilder::callable_with_object(
realm,
realm.intrinsics().objects().array_prototype_values().into(),
Self::values,
)
.callable(Self::values)
.name("values")
.build();
@ -217,17 +216,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())
@ -253,6 +253,16 @@ impl Array {
.with_message("array exceeded max size")
.into());
}
// Fast path:
if prototype.is_none() {
return Ok(context
.intrinsics()
.templates()
.array()
.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]] »).
@ -260,7 +270,26 @@ 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());
// 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.root_shape(),
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(
@ -290,27 +319,24 @@ 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
.intrinsics()
.templates()
.array()
.create_with_indexed_properties(
ObjectData::array(),
vec![JsValue::new(length)],
elements,
)
}
/// Utility function for concatenating array objects.

9
boa_engine/src/builtins/array_buffer/mod.rs

@ -53,13 +53,11 @@ impl IntrinsicObject for ArrayBuffer {
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
let get_byte_length = BuiltInBuilder::new(realm)
.callable(Self::get_byte_length)
let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length)
.name("get byteLength")
.build();
@ -355,7 +353,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.root_shape(),
prototype,
ObjectData::array_buffer(Self {
array_buffer_data: Some(block),

6
boa_engine/src/builtins/boolean/mod.rs

@ -68,7 +68,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.root_shape(),
prototype,
ObjectData::boolean(data),
);
Ok(boolean.into())
}

12
boa_engine/src/builtins/dataview/mod.rs

@ -35,18 +35,15 @@ impl IntrinsicObject for DataView {
fn init(realm: &Realm) {
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_buffer = BuiltInBuilder::new(realm)
.callable(Self::get_buffer)
let get_buffer = BuiltInBuilder::callable(realm, Self::get_buffer)
.name("get buffer")
.build();
let get_byte_length = BuiltInBuilder::new(realm)
.callable(Self::get_byte_length)
let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length)
.name("get byteLength")
.build();
let get_byte_offset = BuiltInBuilder::new(realm)
.callable(Self::get_byte_offset)
let get_byte_offset = BuiltInBuilder::callable(realm, Self::get_byte_offset)
.name("get byteOffset")
.build();
@ -194,7 +191,8 @@ impl BuiltInConstructor for DataView {
.into());
}
let obj = JsObject::from_proto_and_data(
let obj = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::data_view(Self {
// 11. Set O.[[ViewedArrayBuffer]] to buffer.

12
boa_engine/src/builtins/date/mod.rs

@ -96,14 +96,12 @@ impl IntrinsicObject for Date {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let to_utc_string = BuiltInBuilder::new(realm)
.callable(Self::to_utc_string)
let to_utc_string = BuiltInBuilder::callable(realm, Self::to_utc_string)
.name("toUTCString")
.length(0)
.build();
let to_primitive = BuiltInBuilder::new(realm)
.callable(Self::to_primitive)
let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive)
.name("[Symbol.toPrimitive]")
.length(1)
.build();
@ -289,7 +287,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.root_shape(),
prototype,
ObjectData::date(dv),
);
// 8. Return O.
Ok(obj.into())

6
boa_engine/src/builtins/error/aggregate.rs

@ -83,7 +83,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Aggregate),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(1);

6
boa_engine/src/builtins/error/eval.rs

@ -82,7 +82,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Eval),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

6
boa_engine/src/builtins/error/mod.rs

@ -177,7 +177,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Error),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

6
boa_engine/src/builtins/error/range.rs

@ -80,7 +80,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Range),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

6
boa_engine/src/builtins/error/reference.rs

@ -82,7 +82,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Reference),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

6
boa_engine/src/builtins/error/syntax.rs

@ -85,7 +85,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Syntax),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

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

@ -90,7 +90,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Type),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

6
boa_engine/src/builtins/error/uri.rs

@ -81,7 +81,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.root_shape(),
prototype,
ObjectData::error(ErrorKind::Uri),
);
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);

6
boa_engine/src/builtins/escape/mod.rs

@ -23,8 +23,7 @@ pub(crate) struct Escape;
impl IntrinsicObject for Escape {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(escape)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, escape)
.name(Self::NAME)
.length(1)
.build();
@ -96,8 +95,7 @@ pub(crate) struct Unescape;
impl IntrinsicObject for Unescape {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(unescape)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, unescape)
.name(Self::NAME)
.length(1)
.build();

3
boa_engine/src/builtins/eval/mod.rs

@ -31,8 +31,7 @@ impl IntrinsicObject for Eval {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(Self::eval)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, Self::eval)
.name(Self::NAME)
.length(1)
.build();

172
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,42 @@ 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(),
ObjectData::arguments(Self::Unmapped),
);
// 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");
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,
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 +193,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.intrinsics().templates().mapped_arguments().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

24
boa_engine/src/builtins/function/mod.rs

@ -442,17 +442,7 @@ impl IntrinsicObject for BuiltInFunctionObject {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event("function", "init");
BuiltInBuilder::with_object(
realm,
realm.intrinsics().constructors().function().prototype(),
)
.callable(Self::prototype)
.name("")
.length(0)
.build();
let has_instance = BuiltInBuilder::new(realm)
.callable(Self::has_instance)
let has_instance = BuiltInBuilder::callable(realm, Self::has_instance)
.name("[Symbol.iterator]")
.length(1)
.build();
@ -478,6 +468,15 @@ impl IntrinsicObject for BuiltInFunctionObject {
Attribute::CONFIGURABLE,
)
.build();
let prototype = realm.intrinsics().constructors().function().prototype();
BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype)
.name("")
.length(0)
.build();
prototype.set_prototype(Some(realm.intrinsics().constructors().object().prototype()));
}
fn get(intrinsics: &Intrinsics) -> JsObject {
@ -1098,7 +1097,8 @@ impl BoundFunction {
// 8. Set obj.[[BoundThis]] to boundThis.
// 9. Set obj.[[BoundArguments]] to boundArgs.
// 10. Return obj.
Ok(JsObject::from_proto_and_data(
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
ObjectData::bound_function(
Self {

15
boa_engine/src/builtins/intl/collator/mod.rs

@ -163,8 +163,7 @@ impl IntrinsicObject for Collator {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let compare = BuiltInBuilder::new(realm)
.callable(Self::compare)
let compare = BuiltInBuilder::callable(realm, Self::compare)
.name("get compare")
.build();
@ -364,7 +363,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.root_shape(),
prototype,
ObjectData::collator(Self {
locale,
@ -509,10 +509,11 @@ impl Collator {
})?;
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
let options = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
);
let options = context
.intrinsics()
.templates()
.ordinary_object()
.create(ObjectData::ordinary(), vec![]);
// 4. For each row of Table 4, except the header row, in table order, do
// a. Let p be the Property value of the current row.

9
boa_engine/src/builtins/intl/date_time_format.rs

@ -122,7 +122,8 @@ impl BuiltInConstructor for DateTimeFormat {
// « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[Weekday]],
// [[Era]], [[Year]], [[Month]], [[Day]], [[DayPeriod]], [[Hour]], [[Minute]], [[Second]],
// [[FractionalSecondDigits]], [[TimeZoneName]], [[HourCycle]], [[Pattern]], [[BoundFormat]] »).
let date_time_format = JsObject::from_proto_and_data(
let date_time_format = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::date_time_format(Box::new(Self {
initialized_date_time_format: true,
@ -192,7 +193,11 @@ pub(crate) fn to_date_time_options(
} else {
Some(options.to_object(context)?)
};
let options = JsObject::from_proto_and_data(options, ObjectData::ordinary());
let options = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
options,
ObjectData::ordinary(),
);
// 3. Let needDefaults be true.
let mut need_defaults = true;

21
boa_engine/src/builtins/intl/list_format/mod.rs

@ -147,7 +147,8 @@ impl BuiltInConstructor for ListFormat {
// 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormat.prototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]], [[Templates]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::list_format, context)?;
let list_format = JsObject::from_proto_and_data(
let list_format = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::list_format(Self {
formatter: context
@ -358,10 +359,11 @@ impl ListFormat {
// 4. For each Record { [[Type]], [[Value]] } part in parts, do
for (n, part) in parts.0.into_iter().enumerate() {
// a. Let O be OrdinaryObjectCreate(%Object.prototype%).
let o = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
);
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(ObjectData::ordinary(), vec![]);
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
o.create_data_property_or_throw(utf16!("type"), part.typ(), context)
@ -410,10 +412,11 @@ impl ListFormat {
})?;
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
let options = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
);
let options = context
.intrinsics()
.templates()
.ordinary_object()
.create(ObjectData::ordinary(), vec![]);
// 4. For each row of Table 11, except the header row, in table order, do
// a. Let p be the Property value of the current row.

50
boa_engine/src/builtins/intl/locale/mod.rs

@ -35,53 +35,43 @@ impl IntrinsicObject for Locale {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let base_name = BuiltInBuilder::new(realm)
.callable(Self::base_name)
let base_name = BuiltInBuilder::callable(realm, Self::base_name)
.name("get baseName")
.build();
let calendar = BuiltInBuilder::new(realm)
.callable(Self::calendar)
let calendar = BuiltInBuilder::callable(realm, Self::calendar)
.name("get calendar")
.build();
let case_first = BuiltInBuilder::new(realm)
.callable(Self::case_first)
let case_first = BuiltInBuilder::callable(realm, Self::case_first)
.name("get caseFirst")
.build();
let collation = BuiltInBuilder::new(realm)
.callable(Self::collation)
let collation = BuiltInBuilder::callable(realm, Self::collation)
.name("get collation")
.build();
let hour_cycle = BuiltInBuilder::new(realm)
.callable(Self::hour_cycle)
let hour_cycle = BuiltInBuilder::callable(realm, Self::hour_cycle)
.name("get hourCycle")
.build();
let numeric = BuiltInBuilder::new(realm)
.callable(Self::numeric)
let numeric = BuiltInBuilder::callable(realm, Self::numeric)
.name("get numeric")
.build();
let numbering_system = BuiltInBuilder::new(realm)
.callable(Self::numbering_system)
let numbering_system = BuiltInBuilder::callable(realm, Self::numbering_system)
.name("get numberingSystem")
.build();
let language = BuiltInBuilder::new(realm)
.callable(Self::language)
let language = BuiltInBuilder::callable(realm, Self::language)
.name("get language")
.build();
let script = BuiltInBuilder::new(realm)
.callable(Self::script)
let script = BuiltInBuilder::callable(realm, Self::script)
.name("get script")
.build();
let region = BuiltInBuilder::new(realm)
.callable(Self::region)
let region = BuiltInBuilder::callable(realm, Self::region)
.name("get region")
.build();
@ -391,7 +381,11 @@ impl BuiltInConstructor for Locale {
// 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, "%Locale.prototype%", internalSlotsList).
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::locale, context)?;
let locale = JsObject::from_proto_and_data(prototype, ObjectData::locale(tag));
let locale = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::locale(tag),
);
// 37. Return locale.
Ok(locale.into())
@ -429,7 +423,12 @@ impl Locale {
// 4. Return ! Construct(%Locale%, maximal).
let prototype = context.intrinsics().constructors().locale().prototype();
Ok(JsObject::from_proto_and_data(prototype, ObjectData::locale(loc)).into())
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::locale(loc),
)
.into())
}
/// [`Intl.Locale.prototype.minimize ( )`][spec]
@ -462,7 +461,12 @@ impl Locale {
// 4. Return ! Construct(%Locale%, minimal).
let prototype = context.intrinsics().constructors().locale().prototype();
Ok(JsObject::from_proto_and_data(prototype, ObjectData::locale(loc)).into())
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::locale(loc),
)
.into())
}
/// [`Intl.Locale.prototype.toString ( )`][spec].

6
boa_engine/src/builtins/intl/options.rs

@ -244,7 +244,11 @@ pub(super) fn coerce_options_to_object(
// If options is undefined, then
if options.is_undefined() {
// a. Return OrdinaryObjectCreate(null).
return Ok(JsObject::from_proto_and_data(None, ObjectData::ordinary()));
return Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
None,
ObjectData::ordinary(),
));
}
// 2. Return ? ToObject(options).

3
boa_engine/src/builtins/intl/segmenter/iterator.rs

@ -87,7 +87,8 @@ impl SegmentIterator {
// 4. Set iterator.[[IteratedString]] to string.
// 5. Set iterator.[[IteratedStringNextSegmentCodeUnitIndex]] to 0.
// 6. Return iterator.
JsObject::from_proto_and_data(
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()

6
boa_engine/src/builtins/intl/segmenter/mod.rs

@ -163,7 +163,11 @@ impl BuiltInConstructor for Segmenter {
let proto =
get_prototype_from_constructor(new_target, StandardConstructors::segmenter, context)?;
let segmenter = JsObject::from_proto_and_data(proto, ObjectData::segmenter(segmenter));
let segmenter = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
ObjectData::segmenter(segmenter),
);
// 14. Return segmenter.
Ok(segmenter.into())

3
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -65,7 +65,8 @@ impl AsyncFromSyncIterator {
) -> IteratorRecord {
// 1. Let asyncIterator be OrdinaryObjectCreate(%AsyncFromSyncIteratorPrototype%, « [[SyncIteratorRecord]] »).
// 2. Set asyncIterator.[[SyncIteratorRecord]] to syncIteratorRecord.
let async_iterator = JsObject::from_proto_and_data(
let async_iterator = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()

14
boa_engine/src/builtins/iterable/mod.rs

@ -5,7 +5,7 @@ use crate::{
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::JsObject,
object::{JsObject, ObjectData},
realm::Realm,
symbol::JsSymbol,
Context, JsResult, JsValue,
@ -201,14 +201,14 @@ 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.intrinsics());
// 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
obj.create_data_property_or_throw(js_string!("value"), value, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
// 4. Perform ! CreateDataPropertyOrThrow(obj, "done", done).
obj.create_data_property_or_throw(js_string!("done"), done, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
let obj = context
.intrinsics()
.templates()
.iterator_result()
.create(ObjectData::ordinary(), vec![value, done.into()]);
// 5. Return obj.
obj.into()
}

3
boa_engine/src/builtins/map/map_iterator.rs

@ -85,7 +85,8 @@ impl MapIterator {
map_iteration_kind: kind,
lock,
};
let map_iterator = JsObject::from_proto_and_data(
let map_iterator = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context.intrinsics().objects().iterator_prototypes().map(),
ObjectData::map_iterator(iter),
);

15
boa_engine/src/builtins/map/mod.rs

@ -41,18 +41,15 @@ impl IntrinsicObject for Map {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
let get_size = BuiltInBuilder::new(realm)
.callable(Self::get_size)
let get_size = BuiltInBuilder::callable(realm, Self::get_size)
.name("get size")
.build();
let entries_function = BuiltInBuilder::new(realm)
.callable(Self::entries)
let entries_function = BuiltInBuilder::callable(realm, Self::entries)
.name("entries")
.build();
@ -136,7 +133,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.root_shape(),
prototype,
ObjectData::map(OrderedMap::new()),
);
// 4. If iterable is either undefined or null, return map.
let iterable = match args.get_or_undefined(0) {

482
boa_engine/src/builtins/mod.rs

@ -95,8 +95,9 @@ use crate::{
js_string,
native_function::{NativeFunction, NativeFunctionPointer},
object::{
FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR,
PROTOTYPE,
shape::{property_table::PropertyTableInner, slot::SlotAttributes},
FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, ObjectKind,
CONSTRUCTOR, PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
@ -584,17 +585,17 @@ struct BuiltInBuilder<'ctx, Kind> {
}
impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Unique {
object: Object::default(),
data: ObjectData::ordinary(),
},
kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(),
}
}
// fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
// BuiltInBuilder {
// realm,
// object: BuiltInObjectInitializer::Unique {
// object: Object::default(),
// data: ObjectData::ordinary(),
// },
// kind: OrdinaryObject,
// prototype: realm.intrinsics().constructors().object().prototype(),
// }
// }
fn with_intrinsic<I: IntrinsicObject>(
realm: &'ctx Realm,
@ -606,82 +607,45 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
prototype: realm.intrinsics().constructors().object().prototype(),
}
}
fn with_object(realm: &'ctx Realm, object: JsObject) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(object),
kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(),
}
}
}
impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
fn callable(
self,
function: NativeFunctionPointer,
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder {
realm: self.realm,
object: self.object,
kind: Callable {
function,
name: js_string!(""),
length: 0,
kind: OrdinaryFunction,
realm: self.realm.clone(),
},
prototype: self
.realm
.intrinsics()
.constructors()
.function()
.prototype(),
}
}
struct BuiltInConstructorWithPrototype<'ctx> {
realm: &'ctx Realm,
function: NativeFunctionPointer,
name: JsString,
length: usize,
object_property_table: PropertyTableInner,
object_storage: Vec<JsValue>,
object: JsObject,
prototype_property_table: PropertyTableInner,
prototype_storage: Vec<JsValue>,
prototype: JsObject,
__proto__: JsPrototype,
inherits: Option<JsObject>,
attributes: Attribute,
}
impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
fn from_standard_constructor<SC: BuiltInConstructor>(
realm: &'ctx Realm,
) -> BuiltInBuilder<'ctx, Callable<Constructor>> {
let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors());
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(constructor.constructor()),
kind: Callable {
function: SC::constructor,
name: js_string!(SC::NAME),
length: SC::LENGTH,
kind: Constructor {
prototype: constructor.prototype(),
inherits: Some(realm.intrinsics().constructors().object().prototype()),
attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE,
},
realm: realm.clone(),
},
prototype: realm.intrinsics().constructors().function().prototype(),
}
#[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
}
fn no_proto(self) -> BuiltInBuilder<'ctx, Callable<ConstructorNoProto>> {
BuiltInBuilder {
realm: self.realm,
object: self.object,
kind: Callable {
function: self.kind.function,
name: self.kind.name,
length: self.kind.length,
kind: ConstructorNoProto,
realm: self.realm.clone(),
},
prototype: self.prototype,
}
/// Specify the name of the constructor function.
///
/// Default is `""`
fn name<N: Into<JsString>>(mut self, name: N) -> Self {
self.name = name.into();
self
}
}
impl<T> BuiltInBuilder<'_, T> {
/// Adds a new static method to the builtin object.
fn static_method<B>(
mut self,
@ -693,20 +657,21 @@ impl<T> BuiltInBuilder<'_, T> {
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = BuiltInBuilder::new(self.realm)
.callable(function)
let function = BuiltInBuilder::callable(self.realm, function)
.name(binding.name)
.length(length)
.build();
self.object.insert(
debug_assert!(self
.object_property_table
.map
.get(&binding.binding)
.is_none());
self.object_property_table.insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE,
);
self.object_storage.push(function.into());
self
}
@ -716,12 +681,12 @@ impl<T> BuiltInBuilder<'_, T> {
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.object.insert(key, property);
let key = key.into();
debug_assert!(self.object_property_table.map.get(&key).is_none());
self.object_property_table
.insert(key, SlotAttributes::from_bits_truncate(attribute.bits()));
self.object_storage.push(value.into());
self
}
@ -736,12 +701,19 @@ impl<T> BuiltInBuilder<'_, T> {
where
K: Into<PropertyKey>,
{
let property = PropertyDescriptor::builder()
.maybe_get(get)
.maybe_set(set)
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.object.insert(key, property);
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();
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
}
@ -749,53 +721,52 @@ impl<T> BuiltInBuilder<'_, T> {
///
/// Default is `Function.prototype` for constructors and `Object.prototype` for statics.
fn prototype(mut self, prototype: JsObject) -> Self {
self.prototype = prototype;
self.__proto__ = Some(prototype);
self
}
}
impl BuiltInBuilder<'_, Callable<Constructor>> {
/// Adds a new method to the constructor's prototype.
fn method<B>(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self
fn method<B>(mut self, function: NativeFunctionPointer, binding: B, length: usize) -> Self
where
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = BuiltInBuilder::new(self.realm)
.callable(function)
let function = BuiltInBuilder::callable(self.realm, function)
.name(binding.name)
.length(length)
.build();
self.kind.kind.prototype.borrow_mut().insert(
debug_assert!(self
.prototype_property_table
.map
.get(&binding.binding)
.is_none());
self.prototype_property_table.insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE,
);
self.prototype_storage.push(function.into());
self
}
/// Adds a new data property to the constructor's prototype.
fn property<K, V>(self, key: K, value: V, attribute: Attribute) -> Self
fn property<K, V>(mut self, key: K, value: V, attribute: Attribute) -> Self
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.kind.kind.prototype.borrow_mut().insert(key, property);
let key = key.into();
debug_assert!(self.prototype_property_table.map.get(&key).is_none());
self.prototype_property_table
.insert(key, SlotAttributes::from_bits_truncate(attribute.bits()));
self.prototype_storage.push(value.into());
self
}
/// Adds new accessor property to the constructor's prototype.
fn accessor<K>(
self,
mut self,
key: K,
get: Option<JsFunction>,
set: Option<JsFunction>,
@ -804,12 +775,19 @@ impl BuiltInBuilder<'_, Callable<Constructor>> {
where
K: Into<PropertyKey>,
{
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);
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();
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
}
@ -818,13 +796,259 @@ impl BuiltInBuilder<'_, Callable<Constructor>> {
/// Default is `Object.prototype`.
#[allow(clippy::missing_const_for_fn)]
fn inherits(mut self, prototype: JsPrototype) -> Self {
self.kind.kind.inherits = prototype;
self.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.attributes = attributes;
self
}
fn build(mut self) {
let function = function::Function::new(
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: (true).then_some(function::ConstructorKind::Base),
},
self.realm.clone(),
);
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);
{
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);
}
let mut object = self.object.borrow_mut();
*object.kind_mut() = ObjectKind::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);
}
fn build_without_prototype(mut self) {
let function = function::Function::new(
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: (true).then_some(function::ConstructorKind::Base),
},
self.realm.clone(),
);
let length = self.length;
let name = self.name.clone();
self = self.static_property("length", length, Attribute::CONFIGURABLE);
self = self.static_property("name", name, Attribute::CONFIGURABLE);
let mut object = self.object.borrow_mut();
*object.kind_mut() = ObjectKind::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> {
realm: &'ctx Realm,
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<N: Into<JsString>>(mut self, name: N) -> Self {
self.name = name.into();
self
}
fn build(self) -> JsFunction {
let function = function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: None,
};
let function = function::Function::new(function, self.realm.clone());
let object = self.realm.intrinsics().templates().function().create(
ObjectData::function(function),
vec![JsValue::new(self.length), JsValue::new(self.name)],
);
JsFunction::from_object_unchecked(object)
}
}
impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
fn callable(realm: &'ctx Realm, function: NativeFunctionPointer) -> BuiltInCallable<'ctx> {
BuiltInCallable {
realm,
function,
length: 0,
name: js_string!(""),
}
}
fn callable_with_intrinsic<I: IntrinsicObject>(
realm: &'ctx Realm,
function: NativeFunctionPointer,
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
kind: Callable {
function,
name: js_string!(""),
length: 0,
kind: OrdinaryFunction,
realm: realm.clone(),
},
prototype: realm.intrinsics().constructors().function().prototype(),
}
}
fn callable_with_object(
realm: &'ctx Realm,
object: JsObject,
function: NativeFunctionPointer,
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(object),
kind: Callable {
function,
name: js_string!(""),
length: 0,
kind: OrdinaryFunction,
realm: realm.clone(),
},
prototype: realm.intrinsics().constructors().function().prototype(),
}
}
}
impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
fn from_standard_constructor<SC: BuiltInConstructor>(
realm: &'ctx Realm,
) -> BuiltInConstructorWithPrototype<'ctx> {
let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors());
BuiltInConstructorWithPrototype {
realm,
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(realm.intrinsics().constructors().function().prototype()),
inherits: Some(realm.intrinsics().constructors().object().prototype()),
attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
}
}
}
impl<T> BuiltInBuilder<'_, T> {
/// Adds a new static method to the builtin object.
fn static_method<B>(
mut self,
function: NativeFunctionPointer,
binding: B,
length: usize,
) -> Self
where
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = BuiltInBuilder::callable(self.realm, function)
.name(binding.name)
.length(length)
.build();
self.object.insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
);
self
}
/// Adds a new static data property to the builtin object.
fn static_property<K, V>(mut self, key: K, value: V, attribute: Attribute) -> Self
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let property = PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());
self.object.insert(key, property);
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.prototype = prototype;
self
}
}

12
boa_engine/src/builtins/number/globals.rs

@ -38,8 +38,7 @@ pub(crate) struct IsFinite;
impl IntrinsicObject for IsFinite {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(is_finite)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, is_finite)
.name(Self::NAME)
.length(1)
.build();
@ -85,8 +84,7 @@ pub(crate) struct IsNaN;
impl IntrinsicObject for IsNaN {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(is_nan)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, is_nan)
.name(Self::NAME)
.length(1)
.build();
@ -227,8 +225,7 @@ pub(crate) struct ParseInt;
impl IntrinsicObject for ParseInt {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(parse_int)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, parse_int)
.name(Self::NAME)
.length(2)
.build();
@ -301,8 +298,7 @@ pub(crate) struct ParseFloat;
impl IntrinsicObject for ParseFloat {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(parse_float)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, parse_float)
.name(Self::NAME)
.length(1)
.build();

6
boa_engine/src/builtins/number/mod.rs

@ -121,7 +121,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.root_shape(),
prototype,
ObjectData::number(data),
);
Ok(this.into())
}
}

3
boa_engine/src/builtins/object/for_in_iterator.rs

@ -77,7 +77,8 @@ impl ForInIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context<'_>) -> JsObject {
JsObject::from_proto_and_data(
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()

15
boa_engine/src/builtins/object/mod.rs

@ -48,13 +48,11 @@ impl IntrinsicObject for Object {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let legacy_proto_getter = BuiltInBuilder::new(realm)
.callable(Self::legacy_proto_getter)
let legacy_proto_getter = BuiltInBuilder::callable(realm, Self::legacy_proto_getter)
.name("get __proto__")
.build();
let legacy_setter_proto = BuiltInBuilder::new(realm)
.callable(Self::legacy_proto_setter)
let legacy_setter_proto = BuiltInBuilder::callable(realm, Self::legacy_proto_setter)
.name("set __proto__")
.build();
@ -142,7 +140,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.root_shape(),
prototype,
ObjectData::ordinary(),
);
return Ok(object.into());
}
@ -419,7 +421,8 @@ impl Object {
let properties = args.get_or_undefined(1);
let obj = match prototype {
JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data(
JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype.as_object().cloned(),
ObjectData::ordinary(),
),

6
boa_engine/src/builtins/promise/mod.rs

@ -311,8 +311,7 @@ impl IntrinsicObject for Promise {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
@ -384,7 +383,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.root_shape(),
promise,
// 4. Set promise.[[PromiseState]] to pending.
// 5. Set promise.[[PromiseFulfillReactions]] to a new empty List.

6
boa_engine/src/builtins/proxy/mod.rs

@ -36,9 +36,8 @@ impl IntrinsicObject for Proxy {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.no_proto()
.static_method(Self::revocable, "revocable", 2)
.build();
.build_without_prototype();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
@ -126,7 +125,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.root_shape(),
context.intrinsics().constructors().object().prototype(),
ObjectData::proxy(
Self::new(target.clone(), handler.clone()),

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

@ -51,47 +51,37 @@ impl IntrinsicObject for RegExp {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_has_indices = BuiltInBuilder::new(realm)
.callable(Self::get_has_indices)
let get_has_indices = BuiltInBuilder::callable(realm, Self::get_has_indices)
.name("get hasIndices")
.build();
let get_global = BuiltInBuilder::new(realm)
.callable(Self::get_global)
let get_global = BuiltInBuilder::callable(realm, Self::get_global)
.name("get global")
.build();
let get_ignore_case = BuiltInBuilder::new(realm)
.callable(Self::get_ignore_case)
let get_ignore_case = BuiltInBuilder::callable(realm, Self::get_ignore_case)
.name("get ignoreCase")
.build();
let get_multiline = BuiltInBuilder::new(realm)
.callable(Self::get_multiline)
let get_multiline = BuiltInBuilder::callable(realm, Self::get_multiline)
.name("get multiline")
.build();
let get_dot_all = BuiltInBuilder::new(realm)
.callable(Self::get_dot_all)
let get_dot_all = BuiltInBuilder::callable(realm, Self::get_dot_all)
.name("get dotAll")
.build();
let get_unicode = BuiltInBuilder::new(realm)
.callable(Self::get_unicode)
let get_unicode = BuiltInBuilder::callable(realm, Self::get_unicode)
.name("get unicode")
.build();
let get_sticky = BuiltInBuilder::new(realm)
.callable(Self::get_sticky)
let get_sticky = BuiltInBuilder::callable(realm, Self::get_sticky)
.name("get sticky")
.build();
let get_flags = BuiltInBuilder::new(realm)
.callable(Self::get_flags)
let get_flags = BuiltInBuilder::callable(realm, Self::get_flags)
.name("get flags")
.build();
let get_source = BuiltInBuilder::new(realm)
.callable(Self::get_source)
let get_source = BuiltInBuilder::callable(realm, Self::get_source)
.name("get source")
.build();
let regexp = BuiltInBuilder::from_standard_constructor::<Self>(realm)
@ -238,7 +228,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.root_shape(),
proto,
ObjectData::ordinary(),
);
// 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
obj.define_property_or_throw(

3
boa_engine/src/builtins/regexp/regexp_string_iterator.rs

@ -103,7 +103,8 @@ impl RegExpStringIterator {
// 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%).
let regexp_string_iterator = JsObject::from_proto_and_data(
let regexp_string_iterator = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()

21
boa_engine/src/builtins/set/mod.rs

@ -44,18 +44,15 @@ impl IntrinsicObject for Set {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
let size_getter = BuiltInBuilder::new(realm)
.callable(Self::size_getter)
let size_getter = BuiltInBuilder::callable(realm, Self::size_getter)
.name("get size")
.build();
let values_function = BuiltInBuilder::new(realm)
.callable(Self::values)
let values_function = BuiltInBuilder::callable(realm, Self::values)
.name("values")
.build();
@ -127,7 +124,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.root_shape(),
prototype,
ObjectData::set(OrderedSet::default()),
);
// 4. If iterable is either undefined or null, return set.
let iterable = args.get_or_undefined(0);
@ -173,7 +174,11 @@ impl Set {
let prototype =
prototype.unwrap_or_else(|| context.intrinsics().constructors().set().prototype());
JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::new()))
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::set(OrderedSet::new()),
)
}
/// Utility for constructing `Set` objects from an iterator of `JsValue`'s.

3
boa_engine/src/builtins/set/set_iterator.rs

@ -87,7 +87,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.root_shape(),
context.intrinsics().objects().iterator_prototypes().set(),
ObjectData::set_iterator(Self::new(set, kind, lock)),
);

12
boa_engine/src/builtins/string/mod.rs

@ -80,14 +80,12 @@ impl IntrinsicObject for String {
let symbol_iterator = JsSymbol::iterator();
let trim_start = BuiltInBuilder::new(realm)
.callable(Self::trim_start)
let trim_start = BuiltInBuilder::callable(realm, Self::trim_start)
.length(0)
.name("trimStart")
.build();
let trim_end = BuiltInBuilder::new(realm)
.callable(Self::trim_end)
let trim_end = BuiltInBuilder::callable(realm, Self::trim_end)
.length(0)
.name("trimEnd")
.build();
@ -255,7 +253,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.root_shape(),
prototype,
ObjectData::string(value),
);
// 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length),
// [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }).

3
boa_engine/src/builtins/string/string_iterator.rs

@ -60,7 +60,8 @@ impl IntrinsicObject for StringIterator {
impl StringIterator {
/// Create a new `StringIterator`.
pub fn create_string_iterator(string: JsString, context: &mut Context<'_>) -> JsObject {
JsObject::from_proto_and_data(
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()

6
boa_engine/src/builtins/symbol/mod.rs

@ -111,14 +111,12 @@ impl IntrinsicObject for Symbol {
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
let to_primitive = BuiltInBuilder::new(realm)
.callable(Self::to_primitive)
let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive)
.name("[Symbol.toPrimitive]")
.length(1)
.build();
let get_description = BuiltInBuilder::new(realm)
.callable(Self::get_description)
let get_description = BuiltInBuilder::callable(realm, Self::get_description)
.name("get description")
.build();

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

@ -51,8 +51,7 @@ macro_rules! typed_array {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = BuiltInBuilder::new(realm)
.callable(TypedArray::get_species)
let get_species = BuiltInBuilder::callable(realm, TypedArray::get_species)
.name("get [Symbol.species]")
.build();
@ -241,38 +240,31 @@ pub(crate) struct TypedArray;
impl IntrinsicObject for TypedArray {
fn init(realm: &Realm) {
let get_species = BuiltInBuilder::new(realm)
.callable(Self::get_species)
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name("get [Symbol.species]")
.build();
let get_buffer = BuiltInBuilder::new(realm)
.callable(Self::buffer)
let get_buffer = BuiltInBuilder::callable(realm, Self::buffer)
.name("get buffer")
.build();
let get_byte_length = BuiltInBuilder::new(realm)
.callable(Self::byte_length)
let get_byte_length = BuiltInBuilder::callable(realm, Self::byte_length)
.name("get byteLength")
.build();
let get_byte_offset = BuiltInBuilder::new(realm)
.callable(Self::byte_offset)
let get_byte_offset = BuiltInBuilder::callable(realm, Self::byte_offset)
.name("get byteOffset")
.build();
let get_length = BuiltInBuilder::new(realm)
.callable(Self::length)
let get_length = BuiltInBuilder::callable(realm, Self::length)
.name("get length")
.build();
let get_to_string_tag = BuiltInBuilder::new(realm)
.callable(Self::to_string_tag)
let get_to_string_tag = BuiltInBuilder::callable(realm, Self::to_string_tag)
.name("get [Symbol.toStringTag]")
.build();
let values_function = BuiltInBuilder::new(realm)
.callable(Self::values)
let values_function = BuiltInBuilder::callable(realm, Self::values)
.name("values")
.length(0)
.build();
@ -284,11 +276,6 @@ impl IntrinsicObject for TypedArray {
None,
Attribute::CONFIGURABLE,
)
.property(
utf16!("length"),
0,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)
.property(
JsSymbol::iterator(),
values_function,
@ -3205,7 +3192,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.root_shape(),
proto,
ObjectData::integer_indexed(indexed),
);
// 9. Return obj.
Ok(obj)

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

@ -82,8 +82,7 @@ pub(crate) struct DecodeUri;
impl IntrinsicObject for DecodeUri {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(decode_uri)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, decode_uri)
.name(Self::NAME)
.length(1)
.build();
@ -101,8 +100,7 @@ pub(crate) struct DecodeUriComponent;
impl IntrinsicObject for DecodeUriComponent {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(decode_uri_component)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, decode_uri_component)
.name(Self::NAME)
.length(1)
.build();
@ -124,8 +122,7 @@ pub(crate) struct EncodeUri;
impl IntrinsicObject for EncodeUri {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(encode_uri)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, encode_uri)
.name(Self::NAME)
.length(1)
.build();
@ -142,8 +139,7 @@ pub(crate) struct EncodeUriComponent;
impl IntrinsicObject for EncodeUriComponent {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.callable(encode_uri_component)
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, encode_uri_component)
.name(Self::NAME)
.length(1)
.build();

8
boa_engine/src/builtins/weak/weak_ref.rs

@ -80,7 +80,8 @@ impl BuiltInConstructor for WeakRef {
// 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRef.prototype%", « [[WeakRefTarget]] »).
// 5. Set weakRef.[[WeakRefTarget]] to target.
let weak_ref = JsObject::from_proto_and_data(
let weak_ref = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
get_prototype_from_constructor(new_target, StandardConstructors::weak_ref, context)?,
ObjectData::weak_ref(WeakGc::new(target.inner())),
);
@ -160,7 +161,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()),
]);
}

3
boa_engine/src/builtins/weak_map/mod.rs

@ -82,7 +82,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.root_shape(),
get_prototype_from_constructor(new_target, StandardConstructors::weak_map, context)?,
ObjectData::weak_map(boa_gc::WeakMap::new()),
);

3
boa_engine/src/builtins/weak_set/mod.rs

@ -78,7 +78,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.root_shape(),
get_prototype_from_constructor(new_target, StandardConstructors::weak_set, context)?,
ObjectData::weak_set(WeakMap::new()),
);

7
boa_engine/src/class.rs

@ -151,8 +151,11 @@ impl<T: Class> ClassConstructor for T {
.unwrap_or_else(|| class_prototype.clone());
let native_instance = Self::constructor(this, args, context)?;
let object_instance =
JsObject::from_proto_and_data(prototype, ObjectData::native_object(native_instance));
let object_instance = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::native_object(native_instance),
);
Ok(object_instance.into())
}
}

340
boa_engine/src/context/intrinsics.rs

@ -4,22 +4,40 @@ use boa_gc::{Finalize, Trace};
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.
///
/// `Intrinsics` is internally stored using a `Gc`, which makes it cheapily clonable
/// for multiple references to the same set of intrinsic objects.
#[derive(Debug, Default, Trace, Finalize)]
#[derive(Debug, Trace, Finalize)]
pub struct Intrinsics {
/// Cached standard constructors
pub(super) constructors: StandardConstructors,
/// Cached intrinsic objects
pub(super) objects: IntrinsicObjects,
/// Cached object templates.
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 {
@ -31,6 +49,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.
@ -950,3 +972,317 @@ impl IntrinsicObjects {
self.segments_prototype.clone()
}
}
/// Contains commonly used [`ObjectTemplate`]s.
#[derive(Debug, Trace, Finalize)]
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,
function_without_proto: ObjectTemplate,
function_with_prototype_without_proto: ObjectTemplate,
}
impl ObjectTemplates {
pub(crate) fn new(root_shape: &SharedShape, constructors: &StandardConstructors) -> Self {
// pre-initialize used shapes.
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.property(
length_property_key.clone(),
Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE,
);
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());
let name_property_key: PropertyKey = "name".into();
let mut function = ObjectTemplate::new(root_shape);
function.property(
length_property_key.clone(),
Attribute::READONLY | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
);
function.property(
name_property_key,
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();
function_with_prototype.property(
PROTOTYPE.into(),
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());
let mut function_prototype = ordinary_object.clone();
function_prototype.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.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.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(
"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.property(
"callee".into(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
let mut iterator_result = ordinary_object.clone();
iterator_result.property(
"value".into(),
Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE,
);
iterator_result.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,
function_without_proto,
function_with_prototype_without_proto,
}
}
/// Cached iterator result template.
///
/// Transitions:
///
/// 1. `__proto__`: `Object.prototype`
/// 2. `"done"`: (`WRITABLE`, `CONFIGURABLE`, `ENUMERABLE`)
/// 3. `"value"`: (`WRITABLE`, `CONFIGURABLE`, `ENUMERABLE`)
pub(crate) const fn iterator_result(&self) -> &ObjectTemplate {
&self.iterator_result
}
/// Cached ordinary object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Object.prototype`
pub(crate) const fn ordinary_object(&self) -> &ObjectTemplate {
&self.ordinary_object
}
/// Cached array object template.
///
/// Transitions:
///
/// 1. `"length"`: (`WRITABLE`, `PERMANENT`,`NON_ENUMERABLE`)
/// 2. `__proto__`: `Array.prototype`
pub(crate) const fn array(&self) -> &ObjectTemplate {
&self.array
}
/// Cached number object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Number.prototype`
pub(crate) const fn number(&self) -> &ObjectTemplate {
&self.number
}
/// Cached string object template.
///
/// Transitions:
///
/// 1. `"length"`: (`READONLY`, `PERMANENT`,`NON_ENUMERABLE`)
/// 2. `__proto__`: `String.prototype`
pub(crate) const fn string(&self) -> &ObjectTemplate {
&self.string
}
/// Cached symbol object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Symbol.prototype`
pub(crate) const fn symbol(&self) -> &ObjectTemplate {
&self.symbol
}
/// Cached bigint object template.
///
/// Transitions:
///
/// 1. `__proto__`: `BigInt.prototype`
pub(crate) const fn bigint(&self) -> &ObjectTemplate {
&self.bigint
}
/// Cached boolean object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Boolean.prototype`
pub(crate) const fn boolean(&self) -> &ObjectTemplate {
&self.boolean
}
/// Cached unmapped arguments object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Object.prototype`
/// 2. `"length"`: (`WRITABLE`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 3. `@@iterator`: (`WRITABLE`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 4. `get/set` `"callee"`: (`NON_ENUMERABLE`, `PERMANENT`)
pub(crate) const fn unmapped_arguments(&self) -> &ObjectTemplate {
&self.unmapped_arguments
}
/// Cached mapped arguments object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Object.prototype`
/// 2. `"length"`: (`WRITABLE`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 3. `@@iterator`: (`WRITABLE`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 4. `"callee"`: (`WRITABLE`, `NON_ENUMERABLE`, `CONFIGURABLE`)
pub(crate) const fn mapped_arguments(&self) -> &ObjectTemplate {
&self.mapped_arguments
}
/// Cached function object with `"prototype"` property template.
///
/// Transitions:
///
/// 1. `"length"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 2. `"name"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 3. `"prototype"`: (`WRITABLE`, `PERMANENT`, `NON_ENUMERABLE`)
/// 4. `__proto__`: `Function.prototype`
pub(crate) const fn function_with_prototype(&self) -> &ObjectTemplate {
&self.function_with_prototype
}
/// Cached constructor function object template.
///
/// Transitions:
///
/// 1. `__proto__`: `Object.prototype`
/// 2. `"contructor"`: (`WRITABLE`, `CONFIGURABLE`, `NON_ENUMERABLE`)
pub(crate) const fn function_prototype(&self) -> &ObjectTemplate {
&self.function_prototype
}
/// Cached function object property template.
///
/// Transitions:
///
/// 1. `"length"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 2. `"name"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 3. `__proto__`: `Function.prototype`
pub(crate) const fn function(&self) -> &ObjectTemplate {
&self.function
}
/// Cached function object property template.
///
/// Transitions:
///
/// 1. `"length"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 2. `"name"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 3. `__proto__`: `AsyncFunction.prototype`
pub(crate) const fn async_function(&self) -> &ObjectTemplate {
&self.async_function
}
/// Cached function object without `__proto__` template.
///
/// Transitions:
///
/// 1. `"length"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 2. `"name"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
pub(crate) const fn function_without_proto(&self) -> &ObjectTemplate {
&self.function_without_proto
}
/// Cached function object with `"prototype"` and without `__proto__` template.
///
/// Transitions:
///
/// 1. `"length"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 2. `"name"`: (`READONLY`, `NON_ENUMERABLE`, `CONFIGURABLE`)
/// 3. `"prototype"`: (`WRITABLE`, `PERMANENT`, `NON_ENUMERABLE`)
pub(crate) const fn function_with_prototype_without_proto(&self) -> &ObjectTemplate {
&self.function_with_prototype_without_proto
}
}

12
boa_engine/src/context/mod.rs

@ -22,7 +22,7 @@ use crate::{
class::{Class, ClassBuilder},
job::{JobQueue, NativeJob, SimpleJobQueue},
native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject},
object::{shape::SharedShape, FunctionObjectBuilder, JsObject},
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
@ -104,6 +104,7 @@ pub struct Context<'host> {
job_queue: MaybeShared<'host, dyn JobQueue>,
optimizer_options: OptimizerOptions,
root_shape: SharedShape,
}
impl std::fmt::Debug for Context<'_> {
@ -536,6 +537,10 @@ impl<'host> Context<'host> {
std::mem::replace(&mut self.realm, realm)
}
pub(crate) fn root_shape(&self) -> SharedShape {
self.root_shape.clone()
}
/// Gets the host hooks.
pub fn host_hooks(&self) -> MaybeShared<'host, dyn HostHooks> {
self.host_hooks.clone()
@ -700,11 +705,13 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> {
'hooks: 'host,
'queue: 'host,
{
let root_shape = SharedShape::root();
let host_hooks = self.host_hooks.unwrap_or_else(|| {
let hooks: &dyn HostHooks = &DefaultHooks;
hooks.into()
});
let realm = Realm::create(&*host_hooks);
let realm = Realm::create(&*host_hooks, &root_shape);
let vm = Vm::new(realm.environment().clone());
let mut context = Context {
@ -727,6 +734,7 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> {
queue.into()
}),
optimizer_options: OptimizerOptions::OPTIMIZE_ALL,
root_shape,
};
builtins::set_default_global_bindings(&mut context)?;

6
boa_engine/src/error.rs

@ -709,7 +709,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.root_shape(),
prototype,
ObjectData::error(tag),
);
o.create_non_enumerable_data_property_or_throw(utf16!("message"), &**message, context);

3
boa_engine/src/object/builtins/jsarraybuffer.rs

@ -101,7 +101,8 @@ impl JsArrayBuffer {
// 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.root_shape(),
prototype,
ObjectData::array_buffer(ArrayBuffer {
array_buffer_data: Some(block),

3
boa_engine/src/object/builtins/jsdataview.rs

@ -97,7 +97,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.root_shape(),
prototype,
ObjectData::data_view(DataView {
viewed_array_buffer: (**array_buffer).clone(),

9
boa_engine/src/object/builtins/jsdate.rs

@ -43,7 +43,8 @@ impl JsDate {
#[inline]
pub fn new(context: &mut Context<'_>) -> Self {
let prototype = context.intrinsics().constructors().date().prototype();
let inner = JsObject::from_proto_and_data(
let inner = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::date(Date::utc_now(&*context.host_hooks())),
);
@ -574,7 +575,11 @@ impl JsDate {
let date_time = Date::new(Some(date_time.naive_local().timestamp_millis()));
Ok(Self {
inner: JsObject::from_proto_and_data(prototype, ObjectData::date(date_time)),
inner: JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::date(date_time),
),
})
}
}

6
boa_engine/src/object/builtins/jsmap.rs

@ -185,7 +185,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.root_shape(),
prototype,
ObjectData::map(OrderedMap::new()),
)
}
/// Returns a new [`JsMapIterator`] object that yields the `[key, value]` pairs within the [`JsMap`] in insertion order.

6
boa_engine/src/object/builtins/jspromise.rs

@ -154,7 +154,8 @@ impl JsPromise {
where
F: FnOnce(&ResolvingFunctions, &mut Context<'_>) -> JsResult<JsValue>,
{
let promise = JsObject::from_proto_and_data(
let promise = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context.intrinsics().constructors().promise().prototype(),
ObjectData::promise(Promise::new()),
);
@ -201,7 +202,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.root_shape(),
context.intrinsics().constructors().promise().prototype(),
ObjectData::promise(Promise::new()),
);

3
boa_engine/src/object/builtins/jsproxy.rs

@ -518,7 +518,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.root_shape(),
context.intrinsics().constructors().object().prototype(),
ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor),
);

23
boa_engine/src/object/internal_methods/integer_indexed.rs

@ -273,30 +273,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)

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

@ -340,14 +340,12 @@ pub(crate) fn ordinary_set_prototype_of(
_: &mut Context<'_>,
) -> JsResult<bool> {
// 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]].
@ -375,7 +373,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.
@ -705,24 +703,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)

21
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)

53
boa_engine/src/object/jsobject.rs

@ -4,6 +4,7 @@
use super::{
internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS},
shape::{shared_shape::SharedShape, Shape},
JsPrototype, NativeObject, Object, PropertyMap,
};
use crate::{
@ -21,6 +22,7 @@ use std::{
collections::HashMap,
error::Error,
fmt::{self, Debug, Display},
hash::Hash,
result::Result as StdResult,
};
use thin_vec::ThinVec;
@ -108,8 +110,35 @@ impl JsObject {
inner: Gc::new(VTableObject {
object: GcRefCell::new(Object {
kind: data.kind,
properties: PropertyMap::default(),
prototype: prototype.into(),
properties: PropertyMap::from_prototype_unique_shape(prototype.into()),
extensible: true,
private_elements: ThinVec::new(),
}),
vtable: data.internal_methods,
}),
}
}
/// 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<O: Into<Option<Self>>>(
root_shape: SharedShape,
prototype: O,
data: ObjectData,
) -> Self {
Self {
inner: Gc::new(VTableObject {
object: GcRefCell::new(Object {
kind: data.kind,
properties: PropertyMap::from_prototype_with_shared_shape(
Shape::shared(root_shape),
prototype.into(),
),
extensible: true,
private_elements: ThinVec::new(),
}),
@ -314,8 +343,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.
@ -859,7 +888,7 @@ Cannot both specify accessors and a value or writable attribute",
/// Helper function for property insertion.
#[track_caller]
pub(crate) fn insert<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
pub(crate) fn insert<K, P>(&self, key: K, property: P) -> bool
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
@ -869,9 +898,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<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
/// 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<K, P>(&self, key: K, property: P) -> bool
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
@ -937,6 +966,14 @@ impl PartialEq for JsObject {
}
}
impl Eq for JsObject {}
impl Hash for JsObject {
fn hash<H: std::hash::Hasher>(&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;

100
boa_engine/src/object/mod.rs

@ -8,20 +8,23 @@ 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},
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},
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::{
@ -74,6 +77,7 @@ pub mod builtins;
mod jsobject;
mod operations;
mod property_map;
pub mod shape;
pub(crate) use builtins::*;
@ -97,6 +101,11 @@ pub const PROTOTYPE: &[u16] = utf16!("prototype");
/// A `None` values means that the prototype is the `null` value.
pub type JsPrototype = Option<JsObject>;
/// The internal storage of an object's property values.
///
/// The [`shape::Shape`] contains the property names and attributes.
pub(crate) type ObjectStorage = Vec<JsValue>;
/// This trait allows Rust types to be passed around as objects.
///
/// This is automatically implemented when a type implements `Any` and `Trace`.
@ -125,8 +134,6 @@ pub struct Object {
kind: ObjectKind,
/// The collection of properties contained in the object
properties: PropertyMap,
/// Instance prototype `__proto__`.
prototype: JsPrototype,
/// Whether it can have new properties added to it.
pub(crate) extensible: bool,
/// The `[[PrivateElements]]` internal slot.
@ -138,7 +145,6 @@ impl Default for Object {
Self {
kind: ObjectKind::Ordinary,
properties: PropertyMap::default(),
prototype: None,
extensible: true,
private_elements: ThinVec::new(),
}
@ -149,7 +155,6 @@ unsafe impl Trace for Object {
boa_gc::custom_trace!(this, {
mark(&this.kind);
mark(&this.properties);
mark(&this.prototype);
for (_, element) in &this.private_elements {
mark(element);
}
@ -838,6 +843,11 @@ impl Object {
&mut self.kind
}
/// 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 {
@ -1478,8 +1488,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.
@ -1491,12 +1501,12 @@ impl Object {
pub fn set_prototype<O: Into<JsPrototype>>(&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
}
}
@ -1682,9 +1692,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<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
/// If a field was already in the object with the same name, then `true` is returned
/// otherwise, `false` is returned.
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> bool
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
@ -1692,9 +1702,11 @@ impl Object {
self.properties.insert(&key.into(), property.into())
}
/// Helper function for property removal.
/// Helper function for property removal without checking if it's configurable.
///
/// Returns `true` if the property was removed, `false` otherwise.
#[inline]
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> {
pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool {
self.properties.remove(key)
}
@ -1848,28 +1860,19 @@ 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(),
ObjectData::function(Function::new(
FunctionKind::Native {
function: self.function,
constructor: self.constructor,
},
self.context.realm().clone(),
)),
let function = Function::new(
FunctionKind::Native {
function: self.function,
constructor: self.constructor,
},
self.context.realm().clone(),
);
let object = self.context.intrinsics().templates().function().create(
ObjectData::function(function),
vec![self.length.into(), self.name.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)
}
}
@ -1917,7 +1920,8 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> {
/// Create a new `ObjectBuilder` with custom [`NativeObject`] data.
pub fn with_native<T: NativeObject>(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.root_shape(),
context.intrinsics().constructors().object().prototype(),
ObjectData::native_object(data),
);
@ -2025,14 +2029,12 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
constructor_object: Object {
kind: ObjectKind::Ordinary,
properties: PropertyMap::default(),
prototype: None,
extensible: true,
private_elements: ThinVec::new(),
},
prototype: Object {
kind: ObjectKind::Ordinary,
properties: PropertyMap::default(),
prototype: None,
extensible: true,
private_elements: ThinVec::new(),
},

549
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, SlotAttributes},
ChangeTransitionAction, Shape, UniqueShape,
},
JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey,
};
use crate::{property::PropertyDescriptorBuilder, JsString, JsSymbol, JsValue};
use boa_gc::{custom_trace, Finalize, Trace};
use indexmap::IndexMap;
@ -59,6 +67,10 @@ impl Default for IndexedProperties {
}
impl IndexedProperties {
fn new(elements: ThinVec<JsValue>) -> Self {
Self::Dense(elements)
}
/// Get a property descriptor if it exists.
fn get(&self, key: u32) -> Option<PropertyDescriptor> {
match self {
@ -95,9 +107,9 @@ impl IndexedProperties {
}
/// Inserts a property descriptor with the specified key.
fn insert(&mut self, key: u32, property: PropertyDescriptor) -> Option<PropertyDescriptor> {
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
@ -118,19 +130,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
@ -139,37 +144,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<PropertyDescriptor> {
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
@ -178,10 +178,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.
@ -222,55 +222,185 @@ pub struct PropertyMap {
/// Properties stored with integers as keys.
indexed_properties: IndexedProperties,
/// Properties stored with `String`s a keys.
string_properties: OrderedHashMap<JsString>,
/// Properties stored with `Symbol`s a keys.
symbol_properties: OrderedHashMap<JsSymbol>,
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, elements: ThinVec<JsValue>) -> Self {
Self {
indexed_properties: IndexedProperties::new(elements),
shape,
storage: Vec::default(),
}
}
/// Construct a [`PropertyMap`] from with the given prototype with an unique [`Shape`].
#[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(),
}
}
/// Construct a [`PropertyMap`] from with the given prototype with a shared shape [`Shape`].
#[must_use]
#[inline]
pub fn from_prototype_with_shared_shape(mut root_shape: Shape, prototype: JsPrototype) -> Self {
root_shape = root_shape.change_prototype_transition(prototype);
Self {
indexed_properties: IndexedProperties::default(),
shape: root_shape,
storage: Vec::default(),
}
}
/// Get the property with the given key from the [`PropertyMap`].
#[must_use]
pub fn get(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
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(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor {
let index = index as usize;
let mut builder = PropertyDescriptor::builder()
.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());
}
if attributes.has_set() {
builder = builder.set(self.storage[index + 1].clone());
}
} else {
builder = builder.writable(attributes.contains(SlotAttributes::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<PropertyDescriptor> {
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);
// 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
.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<PropertyDescriptor> {
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.
@ -296,65 +426,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.
@ -382,42 +453,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;
}
false
}
}
@ -450,131 +497,6 @@ impl ExactSizeIterator for Iter<'_> {
}
}
impl FusedIterator for Iter<'_> {}
/// An iterator over the keys (`PropertyKey`) of an `Object`.
#[derive(Debug, Clone)]
pub struct Keys<'a>(Iter<'a>);
impl Iterator for Keys<'_> {
type Item = PropertyKey;
fn next(&mut self) -> Option<Self::Item> {
let (key, _) = self.0.next()?;
Some(key)
}
}
impl ExactSizeIterator for Keys<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for Keys<'_> {}
/// An iterator over the values (`Property`) of an `Object`.
#[derive(Debug, Clone)]
pub struct Values<'a>(Iter<'a>);
impl Iterator for Values<'_> {
type Item = PropertyDescriptor;
fn next(&mut self) -> Option<Self::Item> {
let (_, value) = self.0.next()?;
Some(value)
}
}
impl ExactSizeIterator for Values<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for Values<'_> {}
/// An iterator over the `Symbol` property entries of an `Object`
#[derive(Debug, Clone)]
pub struct SymbolProperties<'a>(indexmap::map::Iter<'a, JsSymbol, PropertyDescriptor>);
impl<'a> Iterator for SymbolProperties<'a> {
type Item = (&'a JsSymbol, &'a PropertyDescriptor);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for SymbolProperties<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for SymbolProperties<'_> {}
/// An iterator over the keys (`RcSymbol`) of an `Object`.
#[derive(Debug, Clone)]
pub struct SymbolPropertyKeys<'a>(indexmap::map::Keys<'a, JsSymbol, PropertyDescriptor>);
impl<'a> Iterator for SymbolPropertyKeys<'a> {
type Item = &'a JsSymbol;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for SymbolPropertyKeys<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for SymbolPropertyKeys<'_> {}
/// An iterator over the `Symbol` values (`Property`) of an `Object`.
#[derive(Debug, Clone)]
pub struct SymbolPropertyValues<'a>(indexmap::map::Values<'a, JsSymbol, PropertyDescriptor>);
impl<'a> Iterator for SymbolPropertyValues<'a> {
type Item = &'a PropertyDescriptor;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for SymbolPropertyValues<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for SymbolPropertyValues<'_> {}
/// An iterator over the indexed property entries of an `Object`.
#[derive(Debug, Clone)]
pub enum IndexProperties<'a> {
@ -713,86 +635,3 @@ impl ExactSizeIterator for IndexPropertyValues<'_> {
}
}
}
impl FusedIterator for IndexPropertyValues<'_> {}
/// An iterator over the `String` property entries of an `Object`
#[derive(Debug, Clone)]
pub struct StringProperties<'a>(indexmap::map::Iter<'a, JsString, PropertyDescriptor>);
impl<'a> Iterator for StringProperties<'a> {
type Item = (&'a JsString, &'a PropertyDescriptor);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for StringProperties<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for StringProperties<'_> {}
/// An iterator over the string keys (`RcString`) of an `Object`.
#[derive(Debug, Clone)]
pub struct StringPropertyKeys<'a>(indexmap::map::Keys<'a, JsString, PropertyDescriptor>);
impl<'a> Iterator for StringPropertyKeys<'a> {
type Item = &'a JsString;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for StringPropertyKeys<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for StringPropertyKeys<'_> {}
/// An iterator over the string values (`Property`) of an `Object`.
#[derive(Debug, Clone)]
pub struct StringPropertyValues<'a>(indexmap::map::Values<'a, JsString, PropertyDescriptor>);
impl<'a> Iterator for StringPropertyValues<'a> {
type Item = &'a PropertyDescriptor;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl ExactSizeIterator for StringPropertyValues<'_> {
#[inline]
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for StringPropertyValues<'_> {}

217
boa_engine/src/object/shape/mod.rs

@ -0,0 +1,217 @@
//! Implements object shapes.
pub(crate) mod property_table;
pub(crate) mod shared_shape;
pub(crate) mod slot;
pub(crate) mod unique_shape;
pub use shared_shape::SharedShape;
pub(crate) use unique_shape::UniqueShape;
use std::fmt::Debug;
use boa_gc::{Finalize, Trace};
use crate::property::PropertyKey;
use self::{shared_shape::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<T> {
/// 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),
}
/// Represents the shape of an object.
#[derive(Debug, Trace, Finalize, Clone)]
pub struct Shape {
inner: Inner,
}
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),
}
}
/// 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);
}
None
}
/// 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<Shape> {
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`].
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 [`JsPrototype`] of the [`Shape`].
pub fn prototype(&self) -> JsPrototype {
match &self.inner {
Inner::Shared(shape) => shape.prototype(),
Inner::Unique(shape) => shape.prototype(),
}
}
/// Lookup a property in the shape
#[inline]
pub(crate) fn lookup(&self, key: &PropertyKey) -> Option<Slot> {
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<PropertyKey> {
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(),
}
}
}

149
boa_engine/src/object/shape/property_table.rs

@ -0,0 +1,149 @@
use std::{cell::RefCell, rc::Rc};
use rustc_hash::FxHashMap;
use crate::{
object::shape::slot::{Slot, SlotAttributes},
property::PropertyKey,
};
/// The internal representation of [`PropertyTable`].
#[derive(Default, Debug, Clone)]
pub(crate) struct PropertyTableInner {
pub(crate) map: FxHashMap<PropertyKey, (u32, Slot)>,
pub(crate) keys: Vec<(PropertyKey, Slot)>,
}
impl PropertyTableInner {
/// Returns all the keys, in insertion order.
pub(crate) fn keys(&self) -> Vec<PropertyKey> {
self.keys_cloned_n(self.keys.len() as u32)
}
/// Returns `n` cloned keys, in insertion order.
pub(crate) fn keys_cloned_n(&self, n: u32) -> Vec<PropertyKey> {
let n = n as usize;
self.keys
.iter()
.take(n)
.map(|(key, _)| key)
.filter(|key| matches!(key, PropertyKey::String(_)))
.chain(
self.keys
.iter()
.take(n)
.map(|(key, _)| key)
.filter(|key| matches!(key, PropertyKey::Symbol(_))),
)
.cloned()
.collect()
}
/// 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(n);
let mut map = FxHashMap::default();
for (index, (key, slot)) in self.keys.iter().take(n).enumerate() {
keys.push((key.clone(), *slot));
map.insert(key.clone(), (index as u32, *slot));
}
Self { map, keys }
}
/// 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));
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<RefCell<PropertyTableInner>>,
}
impl PropertyTable {
/// Returns the inner representation of a [`PropertyTable`].
pub(super) fn inner(&self) -> &RefCell<PropertyTableInner> {
&self.inner
}
/// Add a property to the [`PropertyTable`] or deep clone it,
/// 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,
attributes: SlotAttributes,
property_count: u32,
) -> Self {
{
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 [`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(n))),
}
}
/// Deep clone the [`PropertyTable`].
pub(crate) fn deep_clone_all(&self) -> Self {
Self {
inner: Rc::new(RefCell::new((*self.inner.borrow()).clone())),
}
}
/// Change the attributes of a property.
pub(crate) fn set_attributes_at_index(
&self,
key: &PropertyKey,
property_attributes: SlotAttributes,
) {
let mut inner = self.inner.borrow_mut();
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`].
///
/// Panics:
///
/// If it is not in the [`PropertyTable`].
pub(crate) fn get_expect(&self, key: &PropertyKey) -> Slot {
let inner = self.inner.borrow();
let Some((_, slot)) = inner.map.get(key) else {
unreachable!("There should already be a property!")
};
*slot
}
}

58
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<T> = FxHashMap<T, WeakGc<SharedShapeInner>>;
/// The internal representation of [`ForwardTransition`].
#[derive(Default, Debug, Trace, Finalize)]
struct Inner {
properties: Option<Box<TransitionMap<TransitionKey>>>,
prototypes: Option<Box<TransitionMap<JsPrototype>>>,
}
/// 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<Inner>,
}
impl ForwardTransition {
/// Insert a property transition.
pub(super) fn insert_property(&self, key: TransitionKey, value: &Gc<SharedShapeInner>) {
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<SharedShapeInner>) {
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<WeakGc<SharedShapeInner>> {
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<WeakGc<SharedShapeInner>> {
let this = self.inner.borrow();
let Some(transitions) = this.prototypes.as_ref() else {
return None;
};
transitions.get(key).cloned()
}
}

469
boa_engine/src/object/shape/shared_shape/mod.rs

@ -0,0 +1,469 @@
mod forward_transition;
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;
use crate::{object::JsPrototype, property::PropertyKey, JsObject};
use self::forward_transition::ForwardTransition;
use super::{
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: SlotAttributes,
}
// SAFETY: Non of the member of this struct are garbage collected,
// so this should be fine.
unsafe impl Trace for TransitionKey {
empty_trace!();
}
const INSERT_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0000;
const CONFIGURE_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0001;
const PROTOTYPE_TRANSITION_TYPE: u8 = 0b0000_0010;
// Reserved for future use!
#[allow(unused)]
const RESEREVED_TRANSITION_TYPE: u8 = 0b0000_0011;
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_retain(INSERT_PROPERTY_TRANSITION_TYPE)
}
fn configure_property_transition_from(previous: ShapeFlags) -> Self {
previous.difference(Self::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_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() == PROTOTYPE_TRANSITION_TYPE
}
}
// SAFETY: Non of the member of this struct are garbage collected,
// so this should be fine.
unsafe impl Trace for ShapeFlags {
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<SharedShape>,
/// How many transitions have happened from the root node.
transition_count: u16,
/// Flags about the shape.
flags: ShapeFlags,
}
/// Represents a shared object shape.
#[derive(Debug, Trace, Finalize, Clone)]
pub struct SharedShape {
inner: Gc<Inner>,
}
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 fn transition_count(&self) -> u16 {
self.inner.transition_count
}
/// Getter for the previous field.
pub fn previous(&self) -> Option<&SharedShape> {
self.inner.previous.as_ref()
}
/// Get the prototype of the shape.
pub fn prototype(&self) -> JsPrototype {
self.inner.prototype.clone()
}
/// 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
.keys
.get(self.property_index() as usize)
.expect("There should be a property");
(key.clone(), *slot)
}
/// Get the flags of the shape.
fn flags(&self) -> ShapeFlags {
self.inner.flags
}
/// Getter for the [`ForwardTransition`] field.
fn forward_transitions(&self) -> &ForwardTransition {
&self.inner.forward_transitions
}
/// Check if the shape 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 {
Self {
inner: Gc::new(inner),
}
}
/// Create a root [`SharedShape`].
#[must_use]
pub fn root() -> Self {
Self::new(Inner {
forward_transitions: ForwardTransition::default(),
prototype: None,
property_count: 0,
property_table: PropertyTable::default(),
previous: None,
flags: ShapeFlags::default(),
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_count: self.transition_count() + 1,
flags: ShapeFlags::prototype_transition_from(self.flags()),
};
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_count: self.transition_count() + 1,
flags: ShapeFlags::insert_property_transition_from(self.flags()),
};
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<SharedShape> {
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) {
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(&key.property_key, 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_count: self.transition_count() + 1,
flags: ShapeFlags::configure_property_transition_from(self.flags()),
};
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<JsPrototype>,
IndexMap<PropertyKey, SlotAttributes>,
) {
let mut prototype = None;
let mut transitions: IndexMap<PropertyKey, SlotAttributes, RandomState> =
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.flags().is_prototype_transition_type() {
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.flags().is_insert_transition_type() && &current_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 &current_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<Slot> {
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<PropertyKey> {
let property_table = self.property_table().inner().borrow();
property_table.keys_cloned_n(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()),
)
}
/// 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
}
}

141
boa_engine/src/object/shape/shared_shape/template.rs

@ -0,0 +1,141 @@
use boa_gc::{Finalize, Trace};
use thin_vec::ThinVec;
use crate::{
object::{
shape::{slot::SlotAttributes, Shape},
JsObject, Object, ObjectData, PropertyMap,
},
property::{Attribute, PropertyKey},
JsValue,
};
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, Trace, Finalize)]
pub(crate) struct ObjectTemplate {
shape: SharedShape,
}
impl ObjectTemplate {
/// Create a new [`ObjectTemplate`]
pub(crate) fn new(root_shape: &SharedShape) -> Self {
Self {
shape: root_shape.clone(),
}
}
/// 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 { shape }
}
/// 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 {
debug_assert!(!matches!(&key, PropertyKey::Index(_)));
let attributes = SlotAttributes::from_bits_truncate(attributes.bits());
self.shape = self.shape.insert_property_transition(TransitionKey {
property_key: key,
attributes,
});
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,
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 = SlotAttributes::empty();
result.set(
SlotAttributes::CONFIGURABLE,
attributes.contains(Attribute::CONFIGURABLE),
);
result.set(
SlotAttributes::ENUMERABLE,
attributes.contains(Attribute::ENUMERABLE),
);
result.set(SlotAttributes::GET, get);
result.set(SlotAttributes::SET, set);
result
};
self.shape = self.shape.insert_property_transition(TransitionKey {
property_key: key,
attributes,
});
self
}
/// Create an object from the [`ObjectTemplate`]
///
/// The storage must match the properties provided.
pub(crate) fn create(&self, data: ObjectData, storage: Vec<JsValue>) -> JsObject {
let mut object = Object {
kind: data.kind,
extensible: true,
properties: PropertyMap::new(Shape::shared(self.shape.clone()), ThinVec::default()),
private_elements: ThinVec::new(),
};
object.properties.storage = storage;
JsObject::from_object_and_vtable(object, data.internal_methods)
}
/// 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<JsValue>,
elements: ThinVec<JsValue>,
) -> JsObject {
let mut object = Object {
kind: data.kind,
extensible: true,
properties: PropertyMap::new(Shape::shared(self.shape.clone()), elements),
private_elements: ThinVec::new(),
};
object.properties.storage = storage;
JsObject::from_object_and_vtable(object, data.internal_methods)
}
}

77
boa_engine/src/object/shape/slot.rs

@ -0,0 +1,77 @@
use bitflags::bitflags;
pub(crate) type SlotIndex = u32;
bitflags! {
/// Attributes of a slot.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct SlotAttributes: u8 {
const WRITABLE = 0b0000_0001;
const ENUMERABLE = 0b0000_0010;
const CONFIGURABLE = 0b0000_0100;
const GET = 0b0000_1000;
const SET = 0b0001_0000;
}
}
impl SlotAttributes {
pub(crate) const fn is_accessor_descriptor(self) -> bool {
self.contains(Self::GET) || self.contains(Self::SET)
}
pub(crate) const fn has_get(self) -> bool {
self.contains(Self::GET)
}
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(crate) const fn width_match(self, other: Self) -> bool {
self.is_accessor_descriptor() == other.is_accessor_descriptor()
}
/// Get the width of the slot.
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())
}
}
/// Represents an [`u32`] index and it's slot attributes of an element in a object storage.
///
/// 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(crate) struct Slot {
pub(crate) index: SlotIndex,
pub(crate) attributes: SlotAttributes,
}
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<Slot>,
new_attributes: SlotAttributes,
) -> 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,
}
}
}

241
boa_engine/src/object/shape/unique_shape.rs

@ -0,0 +1,241 @@
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<PropertyTableInner>,
/// The prototype of the shape.
prototype: GcRefCell<JsPrototype>,
}
/// 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<Inner>,
}
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),
}),
}
}
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()
}
/// Get the property table of the [`UniqueShape`].
pub(crate) fn property_table(&self) -> &RefCell<PropertyTableInner> {
&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<Slot> {
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<Shape> {
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;
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
} else {
// Accessor --> Data
ChangeTransitionAction::Remove
};
// 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, 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_slot)) = property_table.map.get_mut(key) else {
unreachable!("There should already be a property")
};
*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<PropertyKey> {
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
}
}

2
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;

17
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::SlotAttributes, JsString, JsSymbol, JsValue};
use boa_gc::{Finalize, Trace};
use std::{fmt, iter::FusedIterator};
@ -338,6 +338,19 @@ impl PropertyDescriptor {
self.configurable = Some(configurable);
}
}
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(SlotAttributes::WRITABLE, self.expect_writable());
} else {
attributes.set(SlotAttributes::GET, self.get().is_some());
attributes.set(SlotAttributes::SET, self.set().is_some());
}
attributes
}
}
/// A builder for [`PropertyDescriptor`].
@ -557,7 +570,7 @@ impl From<PropertyDescriptorBuilder> 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),

6
boa_engine/src/realm.rs

@ -9,7 +9,7 @@
use crate::{
context::{intrinsics::Intrinsics, HostHooks},
environments::DeclarativeEnvironment,
object::JsObject,
object::{shape::shared_shape::SharedShape, JsObject},
};
use boa_gc::{Finalize, Gc, Trace};
use boa_profiler::Profiler;
@ -39,10 +39,10 @@ struct Inner {
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::default();
let intrinsics = Intrinsics::new(root_shape);
let global_object = hooks.create_global_object(&intrinsics);
let global_this = hooks
.create_global_this(&intrinsics)

11
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,
};

26
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::<Vec<String>>()
}
result}
};
}

2
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),
}
}
}

82
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,40 @@ impl JsValue {
///
/// See: <https://tc39.es/ecma262/#sec-toobject>
pub fn to_object(&self, context: &mut Context<'_>) -> JsResult<JsObject> {
// 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
.intrinsics()
.templates()
.boolean()
.create(ObjectData::boolean(*boolean), Vec::default())),
Self::Integer(integer) => Ok(context
.intrinsics()
.templates()
.number()
.create(ObjectData::number(f64::from(*integer)), Vec::default())),
Self::Rational(rational) => Ok(context
.intrinsics()
.templates()
.number()
.create(ObjectData::number(*rational), Vec::default())),
Self::String(ref string) => Ok(context.intrinsics().templates().string().create(
ObjectData::string(string.clone()),
vec![string.len().into()],
)),
Self::Symbol(ref symbol) => Ok(context
.intrinsics()
.templates()
.symbol()
.create(ObjectData::symbol(symbol.clone()), Vec::default())),
Self::BigInt(ref bigint) => Ok(context
.intrinsics()
.templates()
.bigint()
.create(ObjectData::big_int(bigint.clone()), Vec::default())),
Self::Object(jsobject) => Ok(jsobject.clone()),
}
}

1
boa_engine/src/value/tests.rs

@ -1,3 +1,4 @@
use boa_macros::utf16;
use indoc::indoc;
use super::*;

211
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,
realm::Realm,
string::utf16,
@ -576,6 +573,14 @@ impl ToInternedString for CodeBlock {
}
/// Creates a new function object.
///
/// This is used in cases that the prototype is not known if it's [`None`] or [`Some`].
///
/// If the prototype given is [`None`] it will use [`create_function_object_fast`]. Otherwise
/// it will construct the function from template objects that have all the fields except the
/// prototype, and will perform a prototype transition change to set the prototype.
///
/// This is slower than direct object template construction that is done in [`create_function_object_fast`].
pub(crate) fn create_function_object(
code: Gc<CodeBlock>,
r#async: bool,
@ -584,38 +589,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::<JsString>(false),
)
.writable(false)
.enumerable(false)
.configurable(true)
.build();
let name: JsValue = context
.interner()
.resolve_expect(code.name)
.into_common::<JsString>(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 {
Function::new(
@ -642,41 +629,114 @@ 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");
let (mut template, storage, constructor_prototype) = if r#async || arrow || method {
(
templates.function_without_proto().clone(),
vec![length, name],
None,
)
} else {
let constructor_prototype = templates
.function_prototype()
.create(ObjectData::ordinary(), vec![JsValue::undefined()]);
if !r#async && !arrow && !method {
let prototype = JsObject::with_object_proto(context.intrinsics());
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 template = templates.function_with_prototype_without_proto();
(
template.clone(),
vec![length, name, 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
}
constructor
/// Creates a new function object.
///
/// This is prefered over [`create_function_object`] if prototype is [`None`],
/// because it constructs the funtion from a pre-initialized object template,
/// with all the properties and prototype set.
pub(crate) fn create_function_object_fast(
code: Gc<CodeBlock>,
r#async: bool,
arrow: bool,
method: bool,
context: &mut Context<'_>,
) -> JsObject {
let _timer = Profiler::global().start_event("create_function_object_fast", "vm");
let name: JsValue = context
.interner()
.resolve_expect(code.name)
.into_common::<JsString>(false)
.into();
let length: JsValue = code.length.into();
let function = if r#async {
FunctionKind::Async {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
}
} else {
FunctionKind::Ordinary {
code,
environments: context.vm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
}
};
let function = Function::new(function, context.realm().clone());
let data = ObjectData::function(function);
if r#async {
context
.intrinsics()
.templates()
.async_function()
.create(data, vec![length, name])
} else if arrow || method {
context
.intrinsics()
.templates()
.function()
.create(data, vec![length, name])
} else {
let prototype = context
.intrinsics()
.templates()
.function_prototype()
.create(ObjectData::ordinary(), vec![JsValue::undefined()]);
let constructor = context
.intrinsics()
.templates()
.function_with_prototype()
.create(data, vec![length, name, prototype.clone().into()]);
prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into();
constructor
}
}
/// Creates a new generator function object.
@ -722,7 +782,8 @@ pub(crate) fn create_generator_function_object(
.configurable(true)
.build();
let prototype = JsObject::from_proto_and_data(
let prototype = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
if r#async {
context.intrinsics().objects().async_generator()
} else {
@ -741,7 +802,8 @@ pub(crate) fn create_generator_function_object(
},
context.realm().clone(),
);
JsObject::from_proto_and_data(
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
function_prototype,
ObjectData::async_generator_function(function),
)
@ -755,7 +817,11 @@ pub(crate) fn create_generator_function_object(
},
context.realm().clone(),
);
JsObject::from_proto_and_data(function_prototype, ObjectData::generator_function(function))
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
function_prototype,
ObjectData::generator_function(function),
)
};
let prototype_property = PropertyDescriptor::builder()
@ -1086,7 +1152,8 @@ impl JsObject {
})
};
let generator = Self::from_proto_and_data(proto, data);
let generator =
Self::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data);
if async_ {
let gen_clone = generator.clone();
@ -1146,7 +1213,11 @@ impl JsObject {
StandardConstructors::object,
context,
)?;
Ok(Self::from_proto_and_data(prototype, ObjectData::ordinary()))
Ok(Self::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::ordinary(),
))
} else {
Err(JsNativeError::typ()
.with_message(
@ -1178,7 +1249,11 @@ impl JsObject {
StandardConstructors::object,
context,
)?;
let this = Self::from_proto_and_data(prototype, ObjectData::ordinary());
let this = Self::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::ordinary(),
);
this.initialize_instance_elements(self, context)?;

10
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_fast, opcode::Operation, CompletionType},
Context, JsResult,
};
@ -18,7 +18,7 @@ impl Operation for GetArrowFunction {
let index = context.vm.read::<u32>();
context.vm.read::<u8>();
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_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::<u32>();
context.vm.read::<u8>();
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_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::<u32>();
let method = context.vm.read::<u8>() != 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_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::<u32>();
let method = context.vm.read::<u8>() != 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_fast(code, true, false, method, context);
context.vm.push(function);
Ok(CompletionType::Normal)
}

10
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,11 @@ impl Operation for PushNewArray {
const INSTRUCTION: &'static str = "INST - PushNewArray";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let array = Array::array_create(0, None, context)
.expect("Array creation with 0 length should never fail");
let array = context
.intrinsics()
.templates()
.array()
.create(ObjectData::array(), vec![JsValue::new(0)]);
context.vm.push(array);
Ok(CompletionType::Normal)
}

8
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,11 @@ impl Operation for PushEmptyObject {
const INSTRUCTION: &'static str = "INST - PushEmptyObject";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let o = JsObject::with_object_proto(context.intrinsics());
let o = context
.intrinsics()
.templates()
.ordinary_object()
.create(ObjectData::ordinary(), Vec::default());
context.vm.push(o);
Ok(CompletionType::Normal)
}

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

@ -25,7 +25,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.root_shape(),
prototype,
ObjectData::ordinary(),
);
let class = context.vm.pop();
{

3
boa_engine/src/vm/opcode/set/property.rs

@ -78,7 +78,7 @@ impl Operation for SetPropertyByValue {
break 'fast_path;
}
let prototype = object_borrowed.prototype().clone();
let shape = object_borrowed.shape().clone();
if let Some(dense_elements) = object_borrowed
.properties_mut()
@ -93,6 +93,7 @@ impl Operation for SetPropertyByValue {
// Cannot use fast path if the [[prototype]] is a proxy object,
// because we have to the call prototypes [[set]] on non-existing property,
// and proxy objects can override [[set]].
let prototype = shape.prototype();
if prototype.map_or(false, |x| x.is_proxy()) {
break 'fast_path;
}

39
docs/boa_object.md

@ -201,3 +201,42 @@ let global = $boa.realm.create();
Object != global.Object; // true
```
## 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 format.
```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
```

220
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(<b>Root Shape</b>\n<b>Prototype:</i> <i>None</i>) -->| 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(<b>S0: Root Shape</b>\n<b>Prototype:</b> <i>None</i>) -->|Property Count: 0| PropertyTable(PropertyTable\n)
ObjectPrototype(<b>S1: Prototype Shape</b>\n<b>Prototype:</b> 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(<b>S0: Root Shape</b>\nPrototype: <i>None</i>) -->|Property Count: 0| PropertyTable(<b>PropertyTable</b>\nx: Slot 0, writable, configurable, enumerable)
ObjectPrototype(<b>S1: Prototype Shape</b>\n<b>Prototype:</b> <i>Object.prototype</i>) -->|Property Count: 0|PropertyTable
ObjectPrototype -->|Previous| Root
InsertX(<b>S2: Insert Shape</b>\n<b>Property:</b> '<i>x</i>') --> |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(<b>S0: Root Shape</b>\n<b>Prototype:</b> <i>None</i>) -->| Property Count: 0 | PropertyTable(<b>PropertyTable</b>\nx: Slot 0, writable, configurable, enumerable)
ObjectPrototype(<b>S1: Prototype Shape</b>\n<b>Prototype:</b> <i>Object.prototype</i>) -->| Property Count: 0 |PropertyTable
ObjectPrototype -->|Previous| Root
InsertX(<b>S2: Insert Shape</b>\n<b>Property:</b> '<i>x</i>') --> | Property Count: 1 | PropertyTable
InsertX -->|Previous| ObjectPrototype
O(<b>Object o</b>\n<b>Element 0:</b> JsValue: <i>100</i>)
O:::New --> InsertX
```
Let's define a getter and setter `y`
```js
// What the getter/setter are not important!
Object.defineProperty(o, "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(<b>S0: Root Shape</b>\n<b>Prototype:</b> <i>None</i>) -->|Property Count: 0| PropertyTable(<b>PropertyTable</b>\nx: Slot 0, writable, configurable, enumerable\ny: Slot 1, has_get, has_set, configurable, enumerable)
ObjectPrototype(<b>S1: Prototype Shape</b>\n<b>Prototype:</b> <i>Object.prototype</i>) -->| Property Count: 0 |PropertyTable
ObjectPrototype -->|Previous| Root
InsertX(<b>S2: Insert Shape</b>\n<b>Property:</b> '<i>x</i>') --> |Property Count: 1| PropertyTable
InsertX -->|Previous| ObjectPrototype
InsertY(<b>S3: Insert Shape</b>\n<b>Property:</b> '<i>y</i>') --> |Property Count: 2| PropertyTable
InsertY:::New -->|Previous| InsertX
O(<b>Object o\nElement 0:</b> JsValue: 100\n<b>Element 1:</b> JsValue: func\n<b>Element 2:</b> 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(<b>S0: Root Shape</b>\n<b>Prototype:</b> <i>None</i>) -->| Property Count: 0 | PropertyTable(<b>PropertyTable</b>\nx: Slot 0, writable, configurable, enumerable\ny: Slot 1, has_get, has_set, configurable, enumerable)
ObjectPrototype(<b>S1: Prototype Shape\nPrototype:</b> <i>Object.prototype</i>) -->| Property Count: 0 |PropertyTable
ObjectPrototype -->|Previous| Root
InsertX(<b>S2: Insert Shape\nProperty:</b> '<i>x</i>') --> | Property Count: 1 | PropertyTable
InsertX -->|Previous| ObjectPrototype
InsertY(<b>S3: Insert Shape\nProperty:</b> '<i>y</i>') --> | Property Count: 2 | PropertyTable
InsertY -->|Previous| InsertX
PropertyTableFork(<b>PropertyTable</b>\nx: Slot 0, writable, configurable, enumerable\nz: Slot 1, writable, configurable, enumerable)
InsertZ(<b>S4: Insert Shape\nProperty:</b> '<i>z</i>') --> | Property Count: 2 | PropertyTableFork:::New
InsertZ:::New -->|Previous| InsertX
O(<b>Object o\nElement 0:</b> JsValue: 100\n<b>Element 1:</b> JsValue: func\n<b>Element 2:</b> JsValue: func) --> InsertY
O2(<b>Object o2\nElement 0:</b> JsValue: 200\n<b>Element 1:</b> 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(<b>S0: Root Shape</b>\n<b>Prototype:</b> <i>None</i>) -->| Property Count: 0 | PropertyTable(<b>PropertyTable</b>\nx: Slot 0, writable, configurable, enumerable\ny: Slot 1, has_get, has_set, configurable, enumerable)
ObjectPrototype(<b>S1: Prototype Shape\nPrototype:</b> <i>Object.prototype</i>) -->| Property Count: 0 |PropertyTable
ObjectPrototype -->|Previous| Root
PropertyTableFork(<b>PropertyTable</b>\ny: Slot 0, has_get, has_set, configurable, enumerable)
InsertYNew(<b>S3: Insert Shape\nProperty:</b> <i>y</i>) --> | Property Count: 1 |PropertyTableFork:::New
InsertYNew:::New -->|Previous| ObjectPrototype
O(<b>Object o</b>\n<b>Element 0:</b> JsValue: func\n<b>Element 1:</b> JsValue: func) --> InsertYNew
O:::New
```
**NOTE:**: `o2` and its shape have been omitted from the diagram.
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.
Loading…
Cancel
Save