Skip to content

Commit

Permalink
Implementation of object shapes
Browse files Browse the repository at this point in the history
- Make forward reference weak gc
- Add shape property table cache
- Implement unique shape
- Implement unique shape property deleting delete
- Move property desciptor flags to shape
- Remove unneeded Option in property_table
- Implement shared property table
- Remove unneeded return of insert and remove
- Implement prototype transitions
- Implement varying width property storage
- Convert shape to unique if shape has exceeded transition max
- Add documentation
- Add `shape.md` documentation
- Use FxHashMap for lookup and vec for keys
- Direct initialization of arrays and objects with shape
- Make functions share the same shape
- Apply shared shapes to builtins
- Add forward reference check for attribute change
- Direct initialization of arrays
- Remove overwritten length property on TypedArray
- Refactor templates into separate struct
- Apply shapes to callable builtins
- Initialize builtin constructors directly
- Add inline caching to constructor function result
  • Loading branch information
HalidOdat committed Apr 24, 2023
1 parent 63d9d67 commit bd062f8
Show file tree
Hide file tree
Showing 89 changed files with 3,335 additions and 1,121 deletions.
7 changes: 7 additions & 0 deletions boa_cli/src/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions boa_cli/src/debug/shape.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 2 deletions boa_engine/benches/full.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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))
});
}

Expand Down
3 changes: 2 additions & 1 deletion boa_engine/src/builtins/array/array_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
);
Expand Down
80 changes: 53 additions & 27 deletions boa_engine/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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())
Expand All @@ -253,14 +253,43 @@ 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]] »).
// 4. Set A.[[Prototype]] to proto.
// 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(
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 4 additions & 5 deletions boa_engine/src/builtins/array_buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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),
Expand Down
6 changes: 5 additions & 1 deletion boa_engine/src/builtins/boolean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
12 changes: 5 additions & 7 deletions boa_engine/src/builtins/dataview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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.
Expand Down
12 changes: 7 additions & 5 deletions boa_engine/src/builtins/date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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())
Expand Down
6 changes: 5 additions & 1 deletion boa_engine/src/builtins/error/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion boa_engine/src/builtins/error/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion boa_engine/src/builtins/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit bd062f8

Please sign in to comment.