diff --git a/Cargo.lock b/Cargo.lock index 4b33bc5f49d..c431b6bebd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,6 +370,17 @@ dependencies = [ "serde", ] +[[package]] +name = "boa_builtins" +version = "0.16.0" +dependencies = [ + "bitflags 2.2.1", + "boa_macros", + "phf", + "phf_codegen", + "phf_shared", +] + [[package]] name = "boa_cli" version = "0.16.0" @@ -396,6 +407,7 @@ version = "0.16.0" dependencies = [ "bitflags 2.2.1", "boa_ast", + "boa_builtins", "boa_gc", "boa_icu_provider", "boa_interner", @@ -3013,6 +3025,16 @@ dependencies = [ "phf_shared", ] +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + [[package]] name = "phf_generator" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index d47cb01a06e..c31054aeb72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "boa_ast", + "boa_builtins", "boa_cli", "boa_engine", "boa_examples", @@ -28,6 +29,7 @@ description = "Boa is a Javascript lexer, parser and compiler written in Rust. C [workspace.dependencies] boa_ast = { version = "0.16.0", path = "boa_ast" } +boa_builtins = { version = "0.16.0", path = "boa_builtins" } boa_engine = { version = "0.16.0", path = "boa_engine" } boa_gc = { version = "0.16.0", path = "boa_gc" } boa_icu_provider = { version = "0.16.0", path = "boa_icu_provider" } diff --git a/boa_builtins/Cargo.toml b/boa_builtins/Cargo.toml new file mode 100644 index 00000000000..c047486599a --- /dev/null +++ b/boa_builtins/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "boa_builtins" +description = "Builtins of the Boa JavaScript engine." +publish = true +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +build = "build.rs" + +[dependencies] +bitflags = "2.1.0" +phf = "^0.11.1" +phf_shared = "^0.11.1" + +[build-dependencies] +boa_macros.workspace = true +phf_codegen = "^0.11.1" +phf_shared = "^0.11.1" +bitflags = "2.1.0" diff --git a/boa_builtins/README.md b/boa_builtins/README.md new file mode 100644 index 00000000000..29b26df1359 --- /dev/null +++ b/boa_builtins/README.md @@ -0,0 +1 @@ +# TOOD diff --git a/boa_builtins/build.rs b/boa_builtins/build.rs new file mode 100644 index 00000000000..1d1b2b13138 --- /dev/null +++ b/boa_builtins/build.rs @@ -0,0 +1,177 @@ +use std::fs::File; +use std::hash::{Hash, Hasher}; +use std::io::{self, BufWriter, Write}; +use std::path::Path; +use std::{env, fmt}; + +use bitflags::bitflags; +use phf_shared::{FmtConst, PhfBorrow, PhfHash}; + +use boa_macros::utf16; + +bitflags! { + /// This struct constains the property flags as described in the ECMAScript specification. + #[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; + + /// If the property can be enumerated by a `for-in` loop. + const ENUMERABLE = 0b0000_0010; + + /// If the property descriptor can be changed later. + const CONFIGURABLE = 0b0000_0100; + } +} + +/// List of well known symbols. +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +#[allow(dead_code)] +enum WellKnown { + AsyncIterator, + HasInstance, + IsConcatSpreadable, + Iterator, + Match, + MatchAll, + Replace, + Search, + Species, + Split, + ToPrimitive, + ToStringTag, + Unscopables, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum StaticPropertyKey<'a> { + String(&'a [u16]), + Symbol(u8), +} + +impl PhfHash for StaticPropertyKey<'static> { + #[inline] + fn phf_hash(&self, state: &mut H) { + self.hash(state) + } +} + +impl FmtConst for StaticPropertyKey<'static> { + fn fmt_const(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if matches!(self, StaticPropertyKey::String(_)) { + f.write_str("StaticPropertyKey::String(")?; + } else { + f.write_str("StaticPropertyKey::Symbol(")?; + } + + match self { + StaticPropertyKey::String(s) => write!(f, "&{:?})", s), + StaticPropertyKey::Symbol(s) => write!(f, "{})", s), + } + } +} + +impl<'b, 'a: 'b> PhfBorrow> for StaticPropertyKey<'a> { + fn borrow(&self) -> &StaticPropertyKey<'b> { + self + } +} + +trait ToPropertyKey { + fn to_property_key(self) -> StaticPropertyKey<'static>; +} + +impl ToPropertyKey for &'static [u16] { + fn to_property_key(self) -> StaticPropertyKey<'static> { + StaticPropertyKey::String(self) + } +} + +impl ToPropertyKey for WellKnown { + fn to_property_key(self) -> StaticPropertyKey<'static> { + StaticPropertyKey::Symbol(self as u8) + } +} + +struct BuiltInBuilder<'a> { + file: &'a mut BufWriter, + + name: &'static str, + map: phf_codegen::OrderedMap>, + + slot_index: usize, +} + +impl<'a> BuiltInBuilder<'a> { + fn new(file: &'a mut BufWriter, name: &'static str) -> Self { + Self { + file, + name, + map: phf_codegen::OrderedMap::new(), + slot_index: 0, + } + } + + fn method(&mut self, key: K) -> &mut Self + where + K: ToPropertyKey, + { + let key = key.to_property_key(); + let attributes = Attribute::WRITABLE | Attribute::CONFIGURABLE; + self.map.entry( + key, + &format!( + "({}, Attribute::from_bits_retain({}))", + self.slot_index, + attributes.bits() + ), + ); + self.slot_index += 1; + self + } + + fn property(&mut self, key: K, attributes: Attribute) -> &mut Self + where + K: ToPropertyKey, + { + let key = key.to_property_key(); + self.map.entry( + key, + &format!( + "({}, Attribute::from_bits_retain({}))", + self.slot_index, + attributes.bits() + ), + ); + self.slot_index += 1; + self + } + + fn build(&mut self) -> io::Result<()> { + writeln!( + self.file, + "pub static {}_STATIC_SHAPE: ::phf::OrderedMap::, (u32, Attribute)> = \n{};", + self.name, + self.map.build(), + ) + } +} + +fn main() -> io::Result<()> { + let file = Path::new(&env::var("OUT_DIR").unwrap()).join("static_shapes_codegen.rs"); + let file = &mut BufWriter::new(File::create(file)?); + + BuiltInBuilder::new(file, "EMPTY_OBJECT").build()?; + + BuiltInBuilder::new(file, "JSON_OBJECT") + .method(utf16!("parse")) + .method(utf16!("stringify")) + .property( + WellKnown::ToStringTag, + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ) + .build()?; + + Ok(()) +} diff --git a/boa_builtins/src/lib.rs b/boa_builtins/src/lib.rs new file mode 100644 index 00000000000..f11bc6cb261 --- /dev/null +++ b/boa_builtins/src/lib.rs @@ -0,0 +1,47 @@ +use std::hash::{Hash, Hasher}; + +use bitflags::bitflags; +use phf::PhfHash; +use phf_shared::PhfBorrow; + +bitflags! { + /// This struct constains the property flags as described in the ECMAScript specification. + #[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; + + /// If the property can be enumerated by a `for-in` loop. + const ENUMERABLE = 0b0000_0010; + + /// If the property descriptor can be changed later. + const CONFIGURABLE = 0b0000_0100; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StaticPropertyKey<'a> { + String(&'a [u16]), + Symbol(u8), +} + +impl PhfHash for StaticPropertyKey<'static> { + #[inline] + fn phf_hash(&self, state: &mut H) { + self.hash(state) + } +} + +impl<'b, 'a: 'b> PhfBorrow> for StaticPropertyKey<'a> { + #[inline] + fn borrow(&self) -> &StaticPropertyKey<'b> { + self + } +} + +pub type Slot = (u32, Attribute); +pub type StaticShape = phf::OrderedMap, Slot>; + +include!(concat!(env!("OUT_DIR"), "/static_shapes_codegen.rs")); + +// static NUMBER_BUITIN_OBJECT_STATIC_SHAPE_REF: &StaticShape = &NUMBER_BUITIN_OBJECT_STATIC_SHAPE; diff --git a/boa_cli/src/debug/shape.rs b/boa_cli/src/debug/shape.rs index 167bc5b3c47..e874008cf92 100644 --- a/boa_cli/src/debug/shape.rs +++ b/boa_cli/src/debug/shape.rs @@ -31,8 +31,12 @@ fn r#type(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult(realm) - .static_method(Self::parse, "parse", 2) - .static_method(Self::stringify, "stringify", 3) - .static_property(to_string_tag, Self::NAME, attribute) - .build(); + BuiltInBuilder::with_intrinsic_static_shape::( + realm, + &boa_builtins::JSON_OBJECT_STATIC_SHAPE, + ) + .static_method(Self::parse, js_string!("parse"), 2) + .static_method(Self::stringify, js_string!("stringify"), 3) + .static_property(Self::NAME) + .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index e726d54a011..f6b3335109d 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -95,7 +95,10 @@ use crate::{ js_string, native_function::{NativeFunction, NativeFunctionPointer}, object::{ - shape::{property_table::PropertyTableInner, slot::SlotAttributes}, + shape::{ + property_table::PropertyTableInner, slot::SlotAttributes, static_shape::StaticShape, + Shape, + }, FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, ObjectKind, CONSTRUCTOR, PROTOTYPE, }, @@ -584,6 +587,56 @@ struct BuiltInBuilder<'ctx, Kind> { prototype: JsObject, } +struct BuiltInBuilderStaticShape<'ctx> { + realm: &'ctx Realm, + shape: &'static boa_builtins::StaticShape, + object: JsObject, + storage: Vec, +} + +impl BuiltInBuilderStaticShape<'_> { + /// Adds a new static method to the builtin object. + fn static_method( + mut self, + function: NativeFunctionPointer, + name: JsString, + length: usize, + ) -> Self { + let function = BuiltInBuilder::callable(self.realm, function) + .name(name) + .length(length) + .build(); + + self.storage.push(function.into()); + self + } + + /// Adds a new static data property to the builtin object. + fn static_property(mut self, value: V) -> Self + where + V: Into, + { + self.storage.push(value.into()); + self + } + + fn build(mut self) { + debug_assert_eq!(self.storage.len(), self.shape.len()); + + let mut object = self.object.borrow_mut(); + object.properties_mut().shape = Shape::r#static(StaticShape::new(self.shape)); + self.storage.push( + self.realm + .intrinsics() + .constructors() + .object() + .prototype() + .into(), + ); + object.properties_mut().storage = self.storage; + } +} + impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { // fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> { // BuiltInBuilder { @@ -607,6 +660,19 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { prototype: realm.intrinsics().constructors().object().prototype(), } } + + fn with_intrinsic_static_shape( + realm: &'ctx Realm, + shape: &'static boa_builtins::StaticShape, + ) -> BuiltInBuilderStaticShape<'ctx> { + BuiltInBuilderStaticShape { + realm, + shape, + object: I::get(realm.intrinsics()), + storage: Vec::with_capacity(shape.len() + 1), + // prototype: realm.intrinsics().constructors().object().prototype(), + } + } } struct BuiltInConstructorWithPrototype<'ctx> { diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 879ce8dd015..b8b136a4401 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -816,7 +816,7 @@ impl Default for IntrinsicObjects { Self { reflect: JsObject::default(), math: JsObject::default(), - json: JsObject::default(), + json: JsObject::default_with_static_shape(), throw_type_error: JsFunction::empty_intrinsic_function(false), array_prototype_values: JsFunction::empty_intrinsic_function(false), iterator_prototypes: IteratorPrototypes::default(), diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 9ad7cd52f6a..28381495a17 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -59,6 +59,12 @@ impl Default for JsObject { } impl JsObject { + /// TODO: doc + pub(crate) fn default_with_static_shape() -> Self { + let data = ObjectData::ordinary(); + Self::from_object_and_vtable(Object::with_empty_shape(), data.internal_methods) + } + /// Creates a new `JsObject` from its inner object and its vtable. pub(crate) fn from_object_and_vtable( object: Object, diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index ed69064d16c..18101835a95 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -24,7 +24,7 @@ use self::{ string::STRING_EXOTIC_INTERNAL_METHODS, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }, - shape::Shape, + shape::{static_shape::StaticShape, Shape}, }; #[cfg(feature = "intl")] use crate::builtins::intl::{ @@ -838,6 +838,18 @@ impl Debug for ObjectKind { } impl Object { + fn with_empty_shape() -> Self { + Self { + kind: ObjectKind::Ordinary, + properties: PropertyMap::new( + Shape::r#static(StaticShape::new(&boa_builtins::EMPTY_OBJECT_STATIC_SHAPE)), + ThinVec::default(), + ), + extensible: true, + private_elements: ThinVec::new(), + } + } + /// Returns a mutable reference to the kind of an object. pub(crate) fn kind_mut(&mut self) -> &mut ObjectKind { &mut self.kind @@ -1489,6 +1501,12 @@ impl Object { /// Gets the prototype instance of this object. #[inline] pub fn prototype(&self) -> JsPrototype { + // If it is static then the prototype is stored on the (len - 1) position. + if self.properties.shape.is_static() { + return self.properties.storage[self.properties.storage.len() - 1] + .as_object() + .cloned(); + } self.properties.shape.prototype() } diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index 90ffb50989b..4ac989cc67d 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -311,7 +311,21 @@ impl PropertyMap { property_key: key.clone(), attributes, }; - let transition = self.shape.change_attributes_transition(key); + + let transition = if let Some(shape) = self.shape.as_static() { + let prototype = self + .storage + .pop() + .as_ref() + .and_then(JsValue::as_object) + .cloned(); + shape + .to_unique(prototype) + .change_attributes_transition(&key) + } else { + self.shape.change_attributes_transition(key) + }; + self.shape = transition.shape; match transition.action { ChangeTransitionAction::Nothing => {} @@ -346,6 +360,16 @@ impl PropertyMap { return true; } + if let Some(shape) = self.shape.as_static() { + let prototype = self + .storage + .pop() + .as_ref() + .and_then(JsValue::as_object) + .cloned(); + self.shape = Shape::unique(shape.to_unique(prototype)); + } + let transition_key = TransitionKey { property_key: key.clone(), attributes, @@ -390,6 +414,16 @@ impl PropertyMap { return self.indexed_properties.remove(*index); } if let Some(slot) = self.shape.lookup(key) { + if let Some(shape) = self.shape.as_static() { + let prototype = self + .storage + .pop() + .as_ref() + .and_then(JsValue::as_object) + .cloned(); + self.shape = Shape::unique(shape.to_unique(prototype)); + } + // shift all elements when removing. if slot.attributes.is_accessor_descriptor() { self.storage.remove(slot.index as usize + 1); diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 0d7f6e8c13e..fcd21d986a0 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod property_table; pub(crate) mod shared_shape; pub(crate) mod slot; +pub(crate) mod static_shape; pub(crate) mod unique_shape; pub use shared_shape::SharedShape; @@ -14,7 +15,7 @@ use boa_gc::{Finalize, Trace}; use crate::property::PropertyKey; -use self::{shared_shape::TransitionKey, slot::Slot}; +use self::{shared_shape::TransitionKey, slot::Slot, static_shape::StaticShape}; use super::JsPrototype; @@ -54,6 +55,7 @@ pub(crate) struct ChangeTransition { enum Inner { Unique(UniqueShape), Shared(SharedShape), + Static(StaticShape), } /// Represents the shape of an object. @@ -89,18 +91,39 @@ impl Shape { } } + /// Create a [`Shape`] from a [`StaticShape`]. + pub(crate) const fn r#static(shape: StaticShape) -> Self { + Self { + inner: Inner::Static(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 static shape, `false` otherwise. + pub(crate) const fn as_static(&self) -> Option<&StaticShape> { + if let Inner::Static(shape) = &self.inner { + return Some(shape); + } + None + } + /// Returns `true` if it's a unique shape, `false` otherwise. #[inline] pub const fn is_unique(&self) -> bool { matches!(self.inner, Inner::Unique(_)) } + /// Returns `true` if it's a static shape, `false` otherwise. + #[inline] + pub const fn is_static(&self) -> bool { + matches!(self.inner, Inner::Static(_)) + } + pub(crate) const fn as_unique(&self) -> Option<&UniqueShape> { if let Inner::Unique(shape) = &self.inner { return Some(shape); @@ -121,6 +144,7 @@ impl Shape { Self::shared(shape) } Inner::Unique(shape) => Self::unique(shape.insert_property_transition(key)), + Inner::Static(shape) => Self::unique(shape.insert_property_transition(key)), } } @@ -147,6 +171,7 @@ impl Shape { } } Inner::Unique(shape) => shape.change_attributes_transition(&key), + Inner::Static(shape) => shape.change_attributes_transition(&key), } } @@ -163,6 +188,7 @@ impl Shape { Self::shared(shape) } Inner::Unique(shape) => Self::unique(shape.remove_property_transition(key)), + Inner::Static(shape) => Self::unique(shape.remove_property_transition(key)), } } @@ -177,14 +203,16 @@ impl Shape { Self::shared(shape) } Inner::Unique(shape) => Self::unique(shape.change_prototype_transition(prototype)), + Inner::Static(shape) => Self::unique(shape.change_prototype_transition(prototype)), } } /// Get the [`JsPrototype`] of the [`Shape`]. - pub fn prototype(&self) -> JsPrototype { + pub(crate) fn prototype(&self) -> JsPrototype { match &self.inner { Inner::Shared(shape) => shape.prototype(), Inner::Unique(shape) => shape.prototype(), + Inner::Static(_) => unreachable!("Static shapes don't have prototypes in them"), } } @@ -194,6 +222,7 @@ impl Shape { match &self.inner { Inner::Shared(shape) => shape.lookup(key), Inner::Unique(shape) => shape.lookup(key), + Inner::Static(shape) => shape.lookup(key), } } @@ -203,6 +232,7 @@ impl Shape { match &self.inner { Inner::Shared(shape) => shape.keys(), Inner::Unique(shape) => shape.keys(), + Inner::Static(shape) => shape.keys(), } } @@ -212,6 +242,7 @@ impl Shape { match &self.inner { Inner::Shared(shape) => shape.to_addr_usize(), Inner::Unique(shape) => shape.to_addr_usize(), + Inner::Static(shape) => shape.to_addr_usize(), } } } diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index b219d08fd24..6aab8b7484a 100644 --- a/boa_engine/src/object/shape/slot.rs +++ b/boa_engine/src/object/shape/slot.rs @@ -42,7 +42,7 @@ impl SlotAttributes { /// /// 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)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct Slot { pub(crate) index: SlotIndex, pub(crate) attributes: SlotAttributes, diff --git a/boa_engine/src/object/shape/static_shape.rs b/boa_engine/src/object/shape/static_shape.rs new file mode 100644 index 00000000000..636b9370df0 --- /dev/null +++ b/boa_engine/src/object/shape/static_shape.rs @@ -0,0 +1,143 @@ +// Temp allow +#![allow(dead_code)] +#![allow(clippy::needless_pass_by_value)] + +use std::fmt::Debug; + +use boa_builtins::StaticPropertyKey; +use boa_gc::{Finalize, Trace}; + +use crate::{ + object::shape::property_table::PropertyTableInner, property::PropertyKey, symbol::WellKnown, + JsSymbol, +}; + +use super::{ + shared_shape::TransitionKey, slot::SlotAttributes, ChangeTransition, JsPrototype, Shape, Slot, + UniqueShape, +}; + +pub(crate) type StaticShapeInner = &'static boa_builtins::StaticShape; + +/// TODO: doc +fn from_static_property_key(key: &StaticPropertyKey<'_>) -> PropertyKey { + match key { + boa_builtins::StaticPropertyKey::String(s) => PropertyKey::from(*s), + boa_builtins::StaticPropertyKey::Symbol(s) => { + let symbol = match WellKnown::try_from(*s).expect("should be an well known symbol") { + WellKnown::AsyncIterator => JsSymbol::async_iterator(), + WellKnown::HasInstance => JsSymbol::has_instance(), + WellKnown::IsConcatSpreadable => JsSymbol::is_concat_spreadable(), + WellKnown::Iterator => JsSymbol::iterator(), + WellKnown::Match => JsSymbol::r#match(), + WellKnown::MatchAll => JsSymbol::match_all(), + WellKnown::Replace => JsSymbol::replace(), + WellKnown::Search => JsSymbol::search(), + WellKnown::Species => JsSymbol::species(), + WellKnown::Split => JsSymbol::split(), + WellKnown::ToPrimitive => JsSymbol::to_primitive(), + WellKnown::ToStringTag => JsSymbol::to_string_tag(), + WellKnown::Unscopables => JsSymbol::unscopables(), + }; + + PropertyKey::Symbol(symbol) + } + } +} + +/// TODO: doc +fn to_static_property_key(key: &PropertyKey) -> Option> { + match key { + PropertyKey::String(s) => Some(StaticPropertyKey::String(s.as_slice())), + PropertyKey::Symbol(s) => Some(StaticPropertyKey::Symbol(s.hash().try_into().ok()?)), + PropertyKey::Index(_) => None, + } +} + +/// 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(Debug, Clone, Trace, Finalize)] +pub(crate) struct StaticShape { + inner: StaticShapeInner, +} + +impl StaticShape { + /// Create a new [`UniqueShape`]. + pub(crate) const fn new(inner: StaticShapeInner) -> Self { + Self { inner } + } + + /// Inserts a new property into the [`UniqueShape`]. + pub(crate) fn insert_property_transition(&self, _key: TransitionKey) -> UniqueShape { + todo!() + } + + /// 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) -> UniqueShape { + todo!() + } + + /// 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 { + let key = to_static_property_key(key)?; + + // SAFETY: only used to extend the lifetime, so we are able to call get. + let key: &StaticPropertyKey<'static> = unsafe { std::mem::transmute(&key) }; + let (index, attributes) = self.inner.get(key)?; + + Some(Slot { + index: *index, + attributes: SlotAttributes::from_bits_retain(attributes.bits()), + }) + } + + /// 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 { + todo!() + } + + /// 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) -> UniqueShape { + todo!() + } + + /// Gets all keys first strings then symbols in creation order. + pub(crate) fn keys(&self) -> Vec { + self.inner.keys().map(from_static_property_key).collect() + } + + /// TODO: doc + pub(crate) fn to_unique(&self, prototype: JsPrototype) -> UniqueShape { + // TODO: optimization, preallocate capacity. + let mut property_table = PropertyTableInner::default(); + for (key, (_, slot_attributes)) in self.inner { + property_table.insert( + from_static_property_key(key), + SlotAttributes::from_bits_retain(slot_attributes.bits()), + ); + } + UniqueShape::new(prototype, property_table) + } + + /// Return location in memory of the [`UniqueShape`]. + pub(crate) fn to_addr_usize(&self) -> usize { + let ptr: *const _ = self.inner; + ptr as usize + } +} diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index 925dd6f9e03..423fed64626 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -61,7 +61,7 @@ fn get_id() -> Option { /// List of well known symbols. #[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] -enum WellKnown { +pub(crate) enum WellKnown { AsyncIterator, HasInstance, IsConcatSpreadable,