From 5c0899c6882499347cd9b76771de976a0082d23a Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Fri, 14 Apr 2023 06:21:49 +0200 Subject: [PATCH] Implement static shapes --- Cargo.lock | 23 +++ Cargo.toml | 2 + boa_builtins/Cargo.toml | 21 +++ boa_builtins/README.md | 1 + boa_builtins/build.rs | 192 ++++++++++++++++++++ boa_builtins/src/lib.rs | 47 +++++ boa_cli/src/debug/shape.rs | 6 +- boa_engine/Cargo.toml | 2 + boa_engine/src/builtins/json/mod.rs | 19 +- boa_engine/src/builtins/mod.rs | 68 ++++++- boa_engine/src/context/intrinsics.rs | 2 +- boa_engine/src/object/jsobject.rs | 6 + boa_engine/src/object/mod.rs | 20 +- boa_engine/src/object/shape/mod.rs | 27 ++- boa_engine/src/object/shape/slot.rs | 2 +- boa_engine/src/object/shape/static_shape.rs | 139 ++++++++++++++ boa_engine/src/symbol.rs | 2 +- 17 files changed, 561 insertions(+), 18 deletions(-) create mode 100644 boa_builtins/Cargo.toml create mode 100644 boa_builtins/README.md create mode 100644 boa_builtins/build.rs create mode 100644 boa_builtins/src/lib.rs create mode 100644 boa_engine/src/object/shape/static_shape.rs diff --git a/Cargo.lock b/Cargo.lock index 2bfab6a3b9d..2d147813018 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.0", + "boa_macros", + "phf", + "phf_codegen", + "phf_shared", +] + [[package]] name = "boa_cli" version = "0.16.0" @@ -395,6 +406,7 @@ version = "0.16.0" dependencies = [ "bitflags 2.2.0", "boa_ast", + "boa_builtins", "boa_gc", "boa_icu_provider", "boa_interner", @@ -425,6 +437,7 @@ dependencies = [ "num-traits", "num_enum", "once_cell", + "phf", "pollster", "rand", "regress", @@ -2995,6 +3008,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 cad08fa89b6..266e0ea8d04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "boa_ast", + "boa_builtins", "boa_cli", "boa_engine", "boa_examples", @@ -27,6 +28,7 @@ description = "Boa is a Javascript lexer, parser and compiler written in Rust. C [workspace.dependencies] boa_engine = { version = "0.16.0", path = "boa_engine" } +boa_builtins = { version = "0.16.0", path = "boa_builtins" } boa_interner = { version = "0.16.0", path = "boa_interner" } boa_gc = { version = "0.16.0", path = "boa_gc" } boa_profiler = { version = "0.16.0", path = "boa_profiler" } diff --git a/boa_builtins/Cargo.toml b/boa_builtins/Cargo.toml new file mode 100644 index 00000000000..e6756d31b46 --- /dev/null +++ b/boa_builtins/Cargo.toml @@ -0,0 +1,21 @@ +[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" 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..059981d0b07 --- /dev/null +++ b/boa_builtins/build.rs @@ -0,0 +1,192 @@ +use std::fs::File; +use std::hash::{Hash, Hasher}; +use std::io::{self, BufWriter, Write}; +use std::path::Path; +use std::{env, fmt}; + +use phf_shared::{FmtConst, PhfBorrow, PhfHash}; + +use boa_macros::utf16; + +/// 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 + } +} + +fn main() -> io::Result<()> { + let file = Path::new(&env::var("OUT_DIR").unwrap()).join("static_shapes_codegen.rs"); + let mut file = BufWriter::new(File::create(file)?); + + // writeln!(&mut file, "\n\n")?; + + writeln!( + &mut file, + "pub static EMPTY_OBJECT_STATIC_SHAPE: ::phf::OrderedMap::, (u32, Attribute)> = \n{};", + phf_codegen::OrderedMap::>::new() + .build() + )?; + + writeln!( + &mut file, + "pub static JSON_OBJECT_STATIC_SHAPE: ::phf::OrderedMap::, (u32, Attribute)> = \n{};", + phf_codegen::OrderedMap::new() + .entry(StaticPropertyKey::String(utf16!("parse")), "(0, Attribute::WRITABLE.union(Attribute::CONFIGURABLE))") + .entry(StaticPropertyKey::String(utf16!("stringify")), "(1, Attribute::WRITABLE.union(Attribute::CONFIGURABLE))") + .entry(StaticPropertyKey::Symbol(WellKnown::ToStringTag as u8), "(2, Attribute::WRITABLE.union(Attribute::CONFIGURABLE))") + .build() + )?; + + // writeln!( + // &mut file, + // "static BYTE_STR_KEYS: ::phf::Map<&[u8], u32> = \n{};", + // phf_codegen::Map::<&[u8]>::new() + // .entry(b"foo", "0") + // .entry(b"bar", "1") + // .entry(b"baz", "2") + // .entry(b"quux4555", "3") + // .build() + // )?; + + // writeln!( + // &mut file, + // "static SET: ::phf::Set = \n{};", + // phf_codegen::Set::new() + // .entry(1u32) + // .entry(2u32) + // .entry(3u32) + // .build() + // )?; + + // writeln!( + // &mut file, + // "static ORDERED_MAP: ::phf::OrderedMap = \n{};", + // phf_codegen::OrderedMap::new() + // .entry(1u32, "\"a\"") + // .entry(2u32, "\"b\"") + // .entry(3u32, "\"c\"") + // .build() + // )?; + + // writeln!( + // &mut file, + // "static ORDERED_SET: ::phf::OrderedSet = \n{};", + // phf_codegen::OrderedSet::new() + // .entry(1u32) + // .entry(2u32) + // .entry(3u32) + // .build() + // )?; + + // writeln!( + // &mut file, + // "static STR_KEYS: ::phf::Map<&'static str, u32> = \n{};", + // phf_codegen::Map::new() + // .entry("a", "1") + // .entry("b", "2") + // .entry("c", "3") + // .build() + // )?; + + // write!( + // &mut file, + // "static UNICASE_MAP: ::phf::Map<::unicase::UniCase<&'static str>, &'static str> = \n{};", + // phf_codegen::Map::new() + // .entry(UniCase::new("abc"), "\"a\"") + // .entry(UniCase::new("DEF"), "\"b\"") + // .build() + // )?; + + // write!( + // &mut file, + // "static UNCASED_MAP: ::phf::Map<&'static ::uncased::UncasedStr, &'static str> = \n{};", + // phf_codegen::Map::new() + // .entry(UncasedStr::new("abc"), "\"a\"") + // .entry(UncasedStr::new("DEF"), "\"b\"") + // .build() + // )?; + + // //u32 is used here purely for a type that impls `Hash+PhfHash+Eq+fmt::Debug`, but is not required for the empty test itself + // writeln!( + // &mut file, + // "static EMPTY: ::phf::Map = \n{};", + // phf_codegen::Map::::new().build() + // )?; + + // writeln!( + // &mut file, + // "static EMPTY_ORDERED: ::phf::OrderedMap = \n{};", + // phf_codegen::OrderedMap::::new().build() + // )?; + + // writeln!( + // &mut file, + // "static ARRAY_KEYS: ::phf::Map<[u8; 3], u32> = \n{};", + // phf_codegen::Map::<[u8; 3]>::new() + // .entry(*b"foo", "0") + // .entry(*b"bar", "1") + // .entry(*b"baz", "2") + // .build() + // )?; + + // // key type required here as it will infer `&'static [u8; 3]` instead + // writeln!( + // &mut file, + // "static BYTE_STR_KEYS: ::phf::Map<&[u8], u32> = \n{};", + // phf_codegen::Map::<&[u8]>::new() + // .entry(b"foo", "0") + // .entry(b"bar", "1") + // .entry(b"baz", "2") + // .entry(b"quux", "3") + // .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 b95017edef5..08ea20878eb 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -97,7 +97,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, }, @@ -604,6 +607,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 { @@ -627,6 +680,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 1c9178e065d..829c6988853 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/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 0d7f6e8c13e..22c171204d3 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,6 +91,13 @@ 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 { @@ -101,6 +110,12 @@ impl Shape { 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 +136,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 +163,7 @@ impl Shape { } } Inner::Unique(shape) => shape.change_attributes_transition(&key), + Inner::Static(shape) => shape.change_attributes_transition(&key), } } @@ -163,6 +180,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 +195,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 +214,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 +224,7 @@ impl Shape { match &self.inner { Inner::Shared(shape) => shape.keys(), Inner::Unique(shape) => shape.keys(), + Inner::Static(shape) => shape.keys(), } } @@ -212,6 +234,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..3608e69b114 --- /dev/null +++ b/boa_engine/src/object/shape/static_shape.rs @@ -0,0 +1,139 @@ +// 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::{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 { + // UniqueShape::new( + // prototype, + // self.property_table() + // .inner() + // .borrow() + // .clone_count(self.property_count()), + // ) + todo!() + } + + /// 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,