diff --git a/Cargo.lock b/Cargo.lock index 833966577f8..d60871a92a5 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..f5dff3408be --- /dev/null +++ b/boa_builtins/build.rs @@ -0,0 +1,470 @@ +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; + + const GET = 0b0000_1000; + const SET = 0b0001_0000; + } +} + +/// 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 { + name: &'static str, + map: phf_codegen::OrderedMap>, + prototype: Option<&'static str>, + + slot_index: usize, +} + +impl BuiltInBuilder { + fn new(name: &'static str) -> Self { + Self { + name, + map: phf_codegen::OrderedMap::new(), + prototype: None, + slot_index: 0, + } + } + + fn inherits(&mut self, prototype: &'static str) -> &mut Self { + self.prototype = Some(prototype); + self + } + + 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 accessor(&mut self, key: K, mut attributes: Attribute) -> &mut Self + where + K: ToPropertyKey, + { + // TODO: should they always be set? + attributes |= Attribute::GET; + attributes |= Attribute::SET; + + let key = key.to_property_key(); + self.map.entry( + key, + &format!( + "({}, Attribute::from_bits_retain({}))", + self.slot_index, + attributes.bits() + ), + ); + self.slot_index += 2; + 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, file: &mut BufWriter) -> io::Result<&'static str> { + let prototype = if let Some(prototype) = self.prototype { + format!("Some(&'static {})", prototype) + } else { + "None".into() + }; + writeln!( + file, + "pub static {}_STATIC_SHAPE: StaticShape = StaticShape {{\n storage_len: {},\n prototype: {},\n property_table: {} }};", + self.name, + self.slot_index + 1, + prototype, + self.map.build(), + )?; + + Ok(self.name) + } +} + +struct BuiltInBuilderConstructor { + object: BuiltInBuilder, + prototype: BuiltInBuilder, +} + +impl BuiltInBuilderConstructor { + fn new(name: &'static str) -> Self { + let object_name = Box::leak(format!("{name}_CONSTRUCTOR").into_boxed_str()); + let prototype_name = Box::leak(format!("{name}_PROTOTYPE").into_boxed_str()); + let mut this = Self { + object: BuiltInBuilder::new(object_name), + prototype: BuiltInBuilder::new(prototype_name), + }; + + this.object + .property(utf16!("length"), Attribute::CONFIGURABLE); + this.object + .property(utf16!("name"), Attribute::CONFIGURABLE); + this.object + .property(utf16!("prototype"), Attribute::empty()); + + this.prototype.property( + utf16!("constructor"), + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ); + + this + } + + fn inherits(&mut self, prototype: &'static str) -> &mut Self { + self.object.inherits(prototype); + self + } + + fn method(&mut self, key: K) -> &mut Self + where + K: ToPropertyKey, + { + self.prototype.method(key); + self + } + + fn static_method(&mut self, key: K) -> &mut Self + where + K: ToPropertyKey, + { + self.object.method(key); + self + } + + fn accessor(&mut self, key: K, attributes: Attribute) -> &mut Self + where + K: ToPropertyKey, + { + self.prototype.accessor(key, attributes); + self + } + + fn static_accessor(&mut self, key: K, attributes: Attribute) -> &mut Self + where + K: ToPropertyKey, + { + self.object.accessor(key, attributes); + self + } + + fn static_property(&mut self, key: K, attributes: Attribute) -> &mut Self + where + K: ToPropertyKey, + { + self.object.property(key, attributes); + self + } + + fn property(&mut self, key: K, attributes: Attribute) -> &mut Self + where + K: ToPropertyKey, + { + self.prototype.property(key, attributes); + self + } + + fn build(&mut self, file: &mut BufWriter) -> io::Result<()> { + self.object.build(file)?; + self.prototype.build(file)?; + + Ok(()) + } +} + +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("EMPTY_OBJECT").build(file)?; + + BuiltInBuilder::new("JSON_OBJECT") + .method(utf16!("parse")) + .method(utf16!("stringify")) + .property(WellKnown::ToStringTag, Attribute::CONFIGURABLE) + .build(file)?; + + BuiltInBuilder::new("MATH_OBJECT") + .property(utf16!("E"), Attribute::empty()) + .property(utf16!("LN10"), Attribute::empty()) + .property(utf16!("LN2"), Attribute::empty()) + .property(utf16!("LOG10E"), Attribute::empty()) + .property(utf16!("LOG2E"), Attribute::empty()) + .property(utf16!("PI"), Attribute::empty()) + .property(utf16!("SQRT1_2"), Attribute::empty()) + .property(utf16!("SQRT2"), Attribute::empty()) + .method(utf16!("abs")) + .method(utf16!("acos")) + .method(utf16!("acosh")) + .method(utf16!("asin")) + .method(utf16!("asinh")) + .method(utf16!("atan")) + .method(utf16!("atanh")) + .method(utf16!("atan2")) + .method(utf16!("cbrt")) + .method(utf16!("ceil")) + .method(utf16!("clz32")) + .method(utf16!("cos")) + .method(utf16!("cosh")) + .method(utf16!("exp")) + .method(utf16!("expm1")) + .method(utf16!("floor")) + .method(utf16!("fround")) + .method(utf16!("hypot")) + .method(utf16!("imul")) + .method(utf16!("log")) + .method(utf16!("log1p")) + .method(utf16!("log10")) + .method(utf16!("log2")) + .method(utf16!("max")) + .method(utf16!("min")) + .method(utf16!("pow")) + .method(utf16!("random")) + .method(utf16!("round")) + .method(utf16!("sign")) + .method(utf16!("sin")) + .method(utf16!("sinh")) + .method(utf16!("sqrt")) + .method(utf16!("tan")) + .method(utf16!("tanh")) + .method(utf16!("trunc")) + .property(WellKnown::ToStringTag, Attribute::CONFIGURABLE) + .build(file)?; + + BuiltInBuilder::new("REFLECT_OBJECT") + .method(utf16!("apply")) + .method(utf16!("construct")) + .method(utf16!("defineProperty")) + .method(utf16!("deleteProperty")) + .method(utf16!("get")) + .method(utf16!("getOwnPropertyDescriptor")) + .method(utf16!("getPrototypeOf")) + .method(utf16!("has")) + .method(utf16!("isExtensible")) + .method(utf16!("ownKeys")) + .method(utf16!("preventExtensions")) + .method(utf16!("set")) + .method(utf16!("setPrototypeOf")) + .property(WellKnown::ToStringTag, Attribute::CONFIGURABLE) + .build(file)?; + + BuiltInBuilderConstructor::new("OBJECT") + .accessor(utf16!("__proto__"), Attribute::CONFIGURABLE) + .method(utf16!("hasOwnProperty")) + .method(utf16!("propertyIsEnumerable")) + .method(utf16!("toString")) + .method(utf16!("toLocaleString")) + .method(utf16!("valueOf")) + .method(utf16!("isPrototypeOf")) + .method(utf16!("__defineGetter__")) + .method(utf16!("__defineSetter__")) + .method(utf16!("__lookupGetter__")) + .method(utf16!("__lookupSetter__")) + .static_method(utf16!("create")) + .static_method(utf16!("setPrototypeOf")) + .static_method(utf16!("getPrototypeOf")) + .static_method(utf16!("defineProperty")) + .static_method(utf16!("defineProperties")) + .static_method(utf16!("assign")) + .static_method(utf16!("is")) + .static_method(utf16!("keys")) + .static_method(utf16!("values")) + .static_method(utf16!("entries")) + .static_method(utf16!("seal")) + .static_method(utf16!("isSealed")) + .static_method(utf16!("freeze")) + .static_method(utf16!("isFrozen")) + .static_method(utf16!("preventExtensions")) + .static_method(utf16!("isExtensible")) + .static_method(utf16!("getOwnPropertyDescriptor")) + .static_method(utf16!("getOwnPropertyDescriptors")) + .static_method(utf16!("getOwnPropertyNames")) + .static_method(utf16!("getOwnPropertySymbols")) + .static_method(utf16!("hasOwn")) + .static_method(utf16!("fromEntries")) + .build(file)?; + + BuiltInBuilderConstructor::new("FUNCTION") + .property( + utf16!("length"), + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ) + .property( + utf16!("name"), + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ) + .method(utf16!("apply")) + .method(utf16!("bind")) + .method(utf16!("call")) + .method(utf16!("toString")) + .property(WellKnown::HasInstance, Attribute::empty()) + .accessor(utf16!("caller"), Attribute::CONFIGURABLE) + .accessor(utf16!("arguments"), Attribute::CONFIGURABLE) + .build(file)?; + + BuiltInBuilderConstructor::new("ARRAY") + .property(utf16!("length"), Attribute::WRITABLE) + .property( + utf16!("values"), + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ) + .property( + WellKnown::Iterator, + Attribute::WRITABLE | Attribute::CONFIGURABLE, + ) + .property(WellKnown::Unscopables, Attribute::CONFIGURABLE) + .method(utf16!("at")) + .method(utf16!("concat")) + .method(utf16!("push")) + .method(utf16!("indexOf")) + .method(utf16!("lastIndexOf")) + .method(utf16!("includes")) + .method(utf16!("map")) + .method(utf16!("fill")) + .method(utf16!("forEach")) + .method(utf16!("filter")) + .method(utf16!("pop")) + .method(utf16!("join")) + .method(utf16!("toString")) + .method(utf16!("reverse")) + .method(utf16!("shift")) + .method(utf16!("unshift")) + .method(utf16!("every")) + .method(utf16!("find")) + .method(utf16!("findIndex")) + .method(utf16!("findLast")) + .method(utf16!("findLastIndex")) + .method(utf16!("flat")) + .method(utf16!("flatMap")) + .method(utf16!("slice")) + .method(utf16!("some")) + .method(utf16!("sort")) + .method(utf16!("splice")) + .method(utf16!("toLocaleString")) + .method(utf16!("reduce")) + .method(utf16!("reduceRight")) + .method(utf16!("keys")) + .method(utf16!("entries")) + .method(utf16!("copyWithin")) + // Static properties + .static_accessor(WellKnown::Species, Attribute::CONFIGURABLE) + .static_method(utf16!("from")) + .static_method(utf16!("isArray")) + .static_method(utf16!("of")) + .build(file)?; + + Ok(()) +} diff --git a/boa_builtins/src/lib.rs b/boa_builtins/src/lib.rs new file mode 100644 index 00000000000..c2ed539cf33 --- /dev/null +++ b/boa_builtins/src/lib.rs @@ -0,0 +1,109 @@ +use std::{ + fmt::Debug, + 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(Clone, Copy, PartialEq, Eq, Hash)] +pub enum StaticPropertyKey<'a> { + String(&'a [u16]), + Symbol(u8), +} + +impl<'a> Debug for StaticPropertyKey<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StaticPropertyKey::String(string) => { + let string = String::from_utf16_lossy(string); + write!(f, "String(\"{string}\")") + } + StaticPropertyKey::Symbol(symbol) => { + write!(f, "Symbol({symbol})") + } + } + } +} + +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); + +#[derive(Debug)] +pub struct StaticShape { + pub property_table: phf::OrderedMap, Slot>, + + pub storage_len: usize, + + /// \[\[Prototype\]\] + pub prototype: Option<&'static StaticShape>, +} + +impl StaticShape { + #[inline] + pub fn get(&self, key: StaticPropertyKey<'_>) -> Option { + // SAFETY: only used to extend the lifetime, so we are able to call get. + let key: &StaticPropertyKey<'static> = unsafe { std::mem::transmute(&key) }; + self.property_table + .get(key) + .map(|(index, attributes)| (*index, *attributes)) + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.property_table.is_empty() + } + + #[inline] + pub fn len(&self) -> usize { + self.property_table.len() + } + + #[inline] + pub fn get_string_key_expect(&self, index: usize) -> &'static [u16] { + match self + .property_table + .index(index) + .expect("there should be a key at the given index") + .0 + { + StaticPropertyKey::String(s) => s, + StaticPropertyKey::Symbol(s) => { + panic!("The key should be a string at position {index}, but symbol {s}") + } + } + } +} + +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_accessor( - JsSymbol::species(), - Some(get_species), - None, - Attribute::CONFIGURABLE, - ) - .property( - utf16!("length"), - 0, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, - ) - .property( - utf16!("values"), - values_function.clone(), - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .property( - symbol_iterator, - values_function, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .property( - symbol_unscopables, - unscopables_object, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .method(Self::at, "at", 1) - .method(Self::concat, "concat", 1) - .method(Self::push, "push", 1) - .method(Self::index_of, "indexOf", 1) - .method(Self::last_index_of, "lastIndexOf", 1) - .method(Self::includes_value, "includes", 1) - .method(Self::map, "map", 1) - .method(Self::fill, "fill", 1) - .method(Self::for_each, "forEach", 1) - .method(Self::filter, "filter", 1) - .method(Self::pop, "pop", 0) - .method(Self::join, "join", 1) - .method(Self::to_string, "toString", 0) - .method(Self::reverse, "reverse", 0) - .method(Self::shift, "shift", 0) - .method(Self::unshift, "unshift", 1) - .method(Self::every, "every", 1) - .method(Self::find, "find", 1) - .method(Self::find_index, "findIndex", 1) - .method(Self::find_last, "findLast", 1) - .method(Self::find_last_index, "findLastIndex", 1) - .method(Self::flat, "flat", 0) - .method(Self::flat_map, "flatMap", 1) - .method(Self::slice, "slice", 2) - .method(Self::some, "some", 1) - .method(Self::sort, "sort", 1) - .method(Self::splice, "splice", 2) - .method(Self::to_locale_string, "toLocaleString", 0) - .method(Self::reduce, "reduce", 1) - .method(Self::reduce_right, "reduceRight", 1) - .method(Self::keys, "keys", 0) - .method(Self::entries, "entries", 0) - .method(Self::copy_within, "copyWithin", 2) - // Static Methods - .static_method(Self::from, "from", 1) - .static_method(Self::is_array, "isArray", 1) - .static_method(Self::of, "of", 0) - .build(); + BuiltInBuilder::from_standard_constructor_static_shape::( + realm, + &boa_builtins::ARRAY_CONSTRUCTOR_STATIC_SHAPE, + &boa_builtins::ARRAY_PROTOTYPE_STATIC_SHAPE, + ) + .property(0) + .property(values_function.clone()) + .property(values_function) + .property(unscopables_object) + .method(Self::at, 1) + .method(Self::concat, 1) + .method(Self::push, 1) + .method(Self::index_of, 1) + .method(Self::last_index_of, 1) + .method(Self::includes_value, 1) + .method(Self::map, 1) + .method(Self::fill, 1) + .method(Self::for_each, 1) + .method(Self::filter, 1) + .method(Self::pop, 0) + .method(Self::join, 1) + .method(Self::to_string, 0) + .method(Self::reverse, 0) + .method(Self::shift, 0) + .method(Self::unshift, 1) + .method(Self::every, 1) + .method(Self::find, 1) + .method(Self::find_index, 1) + .method(Self::find_last, 1) + .method(Self::find_last_index, 1) + .method(Self::flat, 0) + .method(Self::flat_map, 1) + .method(Self::slice, 2) + .method(Self::some, 1) + .method(Self::sort, 1) + .method(Self::splice, 2) + .method(Self::to_locale_string, 0) + .method(Self::reduce, 1) + .method(Self::reduce_right, 1) + .method(Self::keys, 0) + .method(Self::entries, 0) + .method(Self::copy_within, 2) + // Static Methods + .static_accessor(Some(get_species), None) + .static_method(Self::from, 1) + .static_method(Self::is_array, 1) + .static_method(Self::of, 0) + .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index dab238ef99b..df457c6f125 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -19,12 +19,13 @@ use crate::{ error::JsNativeError, js_string, native_function::NativeFunction, - object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, ObjectKind, + }, object::{JsFunction, PrivateElement}, - property::{Attribute, PropertyDescriptor, PropertyKey}, + property::{PropertyDescriptor, PropertyKey}, realm::Realm, string::utf16, - symbol::JsSymbol, value::IntegerOrInfinity, vm::CodeBlock, Context, JsArgs, JsResult, JsString, JsValue, @@ -449,33 +450,64 @@ impl IntrinsicObject for BuiltInFunctionObject { let throw_type_error = realm.intrinsics().objects().throw_type_error(); - BuiltInBuilder::from_standard_constructor::(realm) - .method(Self::apply, "apply", 2) - .method(Self::bind, "bind", 1) - .method(Self::call, "call", 1) - .method(Self::to_string, "toString", 0) - .property(JsSymbol::has_instance(), has_instance, Attribute::default()) - .accessor( - utf16!("caller"), - Some(throw_type_error.clone()), - Some(throw_type_error.clone()), - Attribute::CONFIGURABLE, - ) - .accessor( - utf16!("arguments"), - Some(throw_type_error.clone()), - Some(throw_type_error), - Attribute::CONFIGURABLE, - ) - .build(); + // BuiltInBuilder::from_standard_constructor::(realm) + // .method(Self::apply, "apply", 2) + // .method(Self::bind, "bind", 1) + // .method(Self::call, "call", 1) + // .method(Self::to_string, "toString", 0) + // .property(JsSymbol::has_instance(), has_instance, Attribute::default()) + // .accessor( + // utf16!("caller"), + // Some(throw_type_error.clone()), + // Some(throw_type_error.clone()), + // Attribute::CONFIGURABLE, + // ) + // .accessor( + // utf16!("arguments"), + // Some(throw_type_error.clone()), + // Some(throw_type_error), + // Attribute::CONFIGURABLE, + // ) + // .build(); + + // BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype) + // .name("") + // .length(0) + // .build(); + + // prototype.set_prototype(Some(realm.intrinsics().constructors().object().prototype())); + + BuiltInBuilder::from_standard_constructor_static_shape::( + realm, + &boa_builtins::FUNCTION_CONSTRUCTOR_STATIC_SHAPE, + &boa_builtins::FUNCTION_PROTOTYPE_STATIC_SHAPE, + ) + .property(0) + .property("") + .method(Self::apply, 2) + .method(Self::bind, 1) + .method(Self::call, 1) + .method(Self::to_string, 0) + .property(has_instance) + .accessor( + Some(throw_type_error.clone()), + Some(throw_type_error.clone()), + ) + .accessor(Some(throw_type_error.clone()), Some(throw_type_error)) + .build(); let prototype = realm.intrinsics().constructors().function().prototype(); - - BuiltInBuilder::callable_with_object(realm, prototype.clone(), Self::prototype) - .name("") - .length(0) - .build(); - + { + let mut prototype = prototype.borrow_mut(); + let function = Function::new( + FunctionKind::Native { + function: NativeFunction::from_fn_ptr(Self::prototype), + constructor: (true).then_some(ConstructorKind::Base), + }, + realm.clone(), + ); + *prototype.kind_mut() = ObjectKind::Function(function); + } prototype.set_prototype(Some(realm.intrinsics().constructors().object().prototype())); } @@ -997,6 +1029,7 @@ impl BuiltInFunctionObject { Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into()) } + #[allow(dead_code)] #[allow(clippy::unnecessary_wraps)] fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { Ok(JsValue::undefined()) diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 8c07c8e99db..9f33d920e56 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -24,10 +24,9 @@ use crate::{ error::JsNativeError, js_string, object::{JsObject, RecursionLimiter}, - property::{Attribute, PropertyNameKind}, + property::PropertyNameKind, realm::Realm, string::{utf16, CodePoint}, - symbol::JsSymbol, value::IntegerOrInfinity, Context, JsArgs, JsResult, JsString, JsValue, }; @@ -49,14 +48,14 @@ impl IntrinsicObject for Json { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let to_string_tag = JsSymbol::to_string_tag(); - let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; - - BuiltInBuilder::with_intrinsic::(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, 2) + .static_method(Self::stringify, 3) + .static_property(Self::NAME) + .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/math/mod.rs b/boa_engine/src/builtins/math/mod.rs index 406661c5ff6..78c259b76a6 100644 --- a/boa_engine/src/builtins/math/mod.rs +++ b/boa_engine/src/builtins/math/mod.rs @@ -12,9 +12,8 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math use crate::{ - builtins::BuiltInObject, context::intrinsics::Intrinsics, object::JsObject, - property::Attribute, realm::Realm, string::utf16, symbol::JsSymbol, Context, JsArgs, JsResult, - JsValue, + builtins::BuiltInObject, context::intrinsics::Intrinsics, object::JsObject, realm::Realm, + Context, JsArgs, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -31,61 +30,55 @@ impl IntrinsicObject for Math { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - BuiltInBuilder::with_intrinsic::(realm) - .static_property(utf16!("E"), std::f64::consts::E, attribute) - .static_property(utf16!("LN10"), std::f64::consts::LN_10, attribute) - .static_property(utf16!("LN2"), std::f64::consts::LN_2, attribute) - .static_property(utf16!("LOG10E"), std::f64::consts::LOG10_E, attribute) - .static_property(utf16!("LOG2E"), std::f64::consts::LOG2_E, attribute) - .static_property(utf16!("PI"), std::f64::consts::PI, attribute) - .static_property( - utf16!("SQRT1_2"), - std::f64::consts::FRAC_1_SQRT_2, - attribute, - ) - .static_property(utf16!("SQRT2"), std::f64::consts::SQRT_2, attribute) - .static_method(Self::abs, "abs", 1) - .static_method(Self::acos, "acos", 1) - .static_method(Self::acosh, "acosh", 1) - .static_method(Self::asin, "asin", 1) - .static_method(Self::asinh, "asinh", 1) - .static_method(Self::atan, "atan", 1) - .static_method(Self::atanh, "atanh", 1) - .static_method(Self::atan2, "atan2", 2) - .static_method(Self::cbrt, "cbrt", 1) - .static_method(Self::ceil, "ceil", 1) - .static_method(Self::clz32, "clz32", 1) - .static_method(Self::cos, "cos", 1) - .static_method(Self::cosh, "cosh", 1) - .static_method(Self::exp, "exp", 1) - .static_method(Self::expm1, "expm1", 1) - .static_method(Self::floor, "floor", 1) - .static_method(Self::fround, "fround", 1) - .static_method(Self::hypot, "hypot", 2) - .static_method(Self::imul, "imul", 2) - .static_method(Self::log, "log", 1) - .static_method(Self::log1p, "log1p", 1) - .static_method(Self::log10, "log10", 1) - .static_method(Self::log2, "log2", 1) - .static_method(Self::max, "max", 2) - .static_method(Self::min, "min", 2) - .static_method(Self::pow, "pow", 2) - .static_method(Self::random, "random", 0) - .static_method(Self::round, "round", 1) - .static_method(Self::sign, "sign", 1) - .static_method(Self::sin, "sin", 1) - .static_method(Self::sinh, "sinh", 1) - .static_method(Self::sqrt, "sqrt", 1) - .static_method(Self::tan, "tan", 1) - .static_method(Self::tanh, "tanh", 1) - .static_method(Self::trunc, "trunc", 1) - .static_property( - JsSymbol::to_string_tag(), - Self::NAME, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .build(); + BuiltInBuilder::with_intrinsic_static_shape::( + realm, + &boa_builtins::MATH_OBJECT_STATIC_SHAPE, + ) + .static_property(std::f64::consts::E) + .static_property(std::f64::consts::LN_10) + .static_property(std::f64::consts::LN_2) + .static_property(std::f64::consts::LOG10_E) + .static_property(std::f64::consts::LOG2_E) + .static_property(std::f64::consts::PI) + .static_property(std::f64::consts::FRAC_1_SQRT_2) + .static_property(std::f64::consts::SQRT_2) + .static_method(Self::abs, 1) + .static_method(Self::acos, 1) + .static_method(Self::acosh, 1) + .static_method(Self::asin, 1) + .static_method(Self::asinh, 1) + .static_method(Self::atan, 1) + .static_method(Self::atanh, 1) + .static_method(Self::atan2, 2) + .static_method(Self::cbrt, 1) + .static_method(Self::ceil, 1) + .static_method(Self::clz32, 1) + .static_method(Self::cos, 1) + .static_method(Self::cosh, 1) + .static_method(Self::exp, 1) + .static_method(Self::expm1, 1) + .static_method(Self::floor, 1) + .static_method(Self::fround, 1) + .static_method(Self::hypot, 2) + .static_method(Self::imul, 2) + .static_method(Self::log, 1) + .static_method(Self::log1p, 1) + .static_method(Self::log10, 1) + .static_method(Self::log2, 1) + .static_method(Self::max, 2) + .static_method(Self::min, 2) + .static_method(Self::pow, 2) + .static_method(Self::random, 0) + .static_method(Self::round, 1) + .static_method(Self::sign, 1) + .static_method(Self::sin, 1) + .static_method(Self::sinh, 1) + .static_method(Self::sqrt, 1) + .static_method(Self::tan, 1) + .static_method(Self::tanh, 1) + .static_method(Self::trunc, 1) + .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..1d89c514555 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -39,6 +39,8 @@ pub mod escape; #[cfg(feature = "intl")] pub mod intl; +use boa_builtins::StaticShape as RawStaticShape; + pub(crate) use self::{ array::Array, async_function::AsyncFunction, @@ -95,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, }, @@ -584,19 +589,259 @@ struct BuiltInBuilder<'ctx, Kind> { prototype: JsObject, } -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(), - // } - // } +struct BuiltInBuilderConstructorStaticShape<'ctx> { + realm: &'ctx Realm, + function: NativeFunctionPointer, + + constructor_property_index: usize, + constructor_object: JsObject, + constructor_shape: &'static RawStaticShape, + constructor_storage: Vec, + + prototype_property_index: usize, + prototype_object: JsObject, + prototype_shape: &'static RawStaticShape, + prototype_storage: Vec, + + __proto__: JsPrototype, + inherits: Option, +} + +impl<'ctx> BuiltInBuilder<'ctx, Callable> { + fn from_standard_constructor_static_shape( + realm: &'ctx Realm, + constructor_shape: &'static RawStaticShape, + prototype_shape: &'static RawStaticShape, + ) -> BuiltInBuilderConstructorStaticShape<'ctx> { + let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors()); + // println!("{constructor_shape:#?}"); + // println!("{prototype_shape:#?}"); + let mut this = BuiltInBuilderConstructorStaticShape { + realm, + function: SC::constructor, + + constructor_property_index: 0, + constructor_shape, + constructor_storage: Vec::with_capacity(constructor_shape.storage_len), + constructor_object: constructor.constructor(), + + prototype_property_index: 0, + prototype_shape, + prototype_storage: Vec::with_capacity(prototype_shape.storage_len), + prototype_object: constructor.prototype(), + + __proto__: Some(realm.intrinsics().constructors().function().prototype()), + inherits: Some(realm.intrinsics().constructors().object().prototype()), + }; + + this.constructor_storage.push(SC::LENGTH.into()); + this.constructor_storage.push(js_string!(SC::NAME).into()); + this.constructor_storage + .push(this.prototype_object.clone().into()); + this.constructor_property_index += 3; + + this.prototype_storage + .push(this.constructor_object.clone().into()); + this.prototype_property_index += 1; + + this + } +} + +#[allow(dead_code)] +impl BuiltInBuilderConstructorStaticShape<'_> { + /// Adds a new static method to the builtin object. + fn static_method(mut self, function: NativeFunctionPointer, length: usize) -> Self { + let name = self + .constructor_shape + .get_string_key_expect(self.constructor_property_index); + + let function = BuiltInBuilder::callable(self.realm, function) + .name(name) + .length(length) + .build(); + + self.constructor_storage.push(function.into()); + self.constructor_property_index += 1; + self + } + /// Adds a new static data property to the builtin object. + fn static_property(mut self, value: V) -> Self + where + V: Into, + { + self.constructor_storage.push(value.into()); + self.constructor_property_index += 1; + self + } + + /// Specify the `[[Prototype]]` internal field of the builtin object. + /// + /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. + fn prototype(mut self, prototype: JsObject) -> Self { + self.__proto__ = Some(prototype); + self + } + + /// Adds a new method to the constructor's prototype. + fn method(mut self, function: NativeFunctionPointer, length: usize) -> Self { + let name = self + .prototype_shape + .get_string_key_expect(self.prototype_property_index); + + let function = BuiltInBuilder::callable(self.realm, function) + .name(name) + .length(length) + .build(); + + self.prototype_storage.push(function.into()); + self.prototype_property_index += 1; + self + } + + /// Adds a new data property to the constructor's prototype. + fn property(mut self, value: V) -> Self + where + V: Into, + { + self.prototype_storage.push(value.into()); + self.prototype_property_index += 1; + self + } + + /// Adds new accessor property to the constructor's prototype. + fn accessor(mut self, get: Option, set: Option) -> Self { + self.prototype_storage.extend([ + get.map(JsValue::new).unwrap_or_default(), + set.map(JsValue::new).unwrap_or_default(), + ]); + self.prototype_property_index += 1; + self + } + + fn static_accessor(mut self, get: Option, set: Option) -> Self { + self.constructor_storage.extend([ + get.map(JsValue::new).unwrap_or_default(), + set.map(JsValue::new).unwrap_or_default(), + ]); + self.constructor_property_index += 1; + self + } + + /// Specifies the parent prototype which objects created by this constructor inherit from. + /// + /// Default is `Object.prototype`. + #[allow(clippy::missing_const_for_fn)] + fn inherits(mut self, prototype: JsPrototype) -> Self { + self.inherits = prototype; + self + } + + fn build(mut self) { + debug_assert_eq!( + self.constructor_storage.len() + 1, + self.constructor_shape.storage_len + ); + debug_assert_eq!( + self.constructor_storage.capacity(), + self.constructor_shape.storage_len + ); + + 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 mut object = self.constructor_object.borrow_mut(); + *object.kind_mut() = ObjectKind::Function(function); + object.properties_mut().shape = Shape::r#static(StaticShape::new(self.constructor_shape)); + self.constructor_storage.push( + self.__proto__ + .unwrap_or_else(|| { + self.realm + .intrinsics() + .constructors() + .function() + .prototype() + }) + .into(), + ); + object.properties_mut().storage = self.constructor_storage; + + debug_assert_eq!( + self.prototype_storage.len() + 1, + self.prototype_shape.storage_len + ); + debug_assert_eq!( + self.prototype_storage.capacity(), + self.prototype_shape.storage_len + ); + let mut prototype = self.prototype_object.borrow_mut(); + prototype.properties_mut().shape = Shape::r#static(StaticShape::new(self.prototype_shape)); + self.prototype_storage + .push(self.inherits.map(JsValue::new).unwrap_or_default()); + prototype.properties_mut().storage = self.prototype_storage; + } +} + +struct BuiltInBuilderStaticShape<'ctx> { + realm: &'ctx Realm, + shape: &'static RawStaticShape, + object: JsObject, + property_index: usize, + storage: Vec, +} + +impl BuiltInBuilderStaticShape<'_> { + /// Adds a new static method to the builtin object. + fn static_method(mut self, function: NativeFunctionPointer, length: usize) -> Self { + let name = self.shape.get_string_key_expect(self.property_index); + + let function = BuiltInBuilder::callable(self.realm, function) + .name(name) + .length(length) + .build(); + + self.storage.push(function.into()); + self.property_index += 1; + 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.property_index += 1; + self + } + + fn build(mut self) { + debug_assert_eq!(self.storage.len(), self.shape.len()); + + debug_assert_eq!(self.storage.len() + 1, self.shape.storage_len); + debug_assert_eq!(self.storage.capacity(), self.shape.storage_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 with_intrinsic( realm: &'ctx Realm, ) -> BuiltInBuilder<'ctx, OrdinaryObject> { @@ -607,6 +852,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.storage_len), + property_index: 0, + } + } } struct BuiltInConstructorWithPrototype<'ctx> { @@ -622,6 +880,7 @@ struct BuiltInConstructorWithPrototype<'ctx> { prototype_property_table: PropertyTableInner, prototype_storage: Vec, prototype: JsObject, + __proto__: JsPrototype, inherits: Option, attributes: Attribute, diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 4c4fc5202a1..d1abf0ba49d 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -26,7 +26,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, FunctionObjectBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind, }, - property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, + property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, realm::Realm, string::utf16, symbol::JsSymbol, @@ -56,55 +56,46 @@ impl IntrinsicObject for Object { .name("set __proto__") .build(); - BuiltInBuilder::from_standard_constructor::(realm) - .inherits(None) - .accessor( - utf16!("__proto__"), - Some(legacy_proto_getter), - Some(legacy_setter_proto), - Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .method(Self::has_own_property, "hasOwnProperty", 1) - .method(Self::property_is_enumerable, "propertyIsEnumerable", 1) - .method(Self::to_string, "toString", 0) - .method(Self::to_locale_string, "toLocaleString", 0) - .method(Self::value_of, "valueOf", 0) - .method(Self::is_prototype_of, "isPrototypeOf", 1) - .method(Self::legacy_define_getter, "__defineGetter__", 2) - .method(Self::legacy_define_setter, "__defineSetter__", 2) - .method(Self::legacy_lookup_getter, "__lookupGetter__", 1) - .method(Self::legacy_lookup_setter, "__lookupSetter__", 1) - .static_method(Self::create, "create", 2) - .static_method(Self::set_prototype_of, "setPrototypeOf", 2) - .static_method(Self::get_prototype_of, "getPrototypeOf", 1) - .static_method(Self::define_property, "defineProperty", 3) - .static_method(Self::define_properties, "defineProperties", 2) - .static_method(Self::assign, "assign", 2) - .static_method(Self::is, "is", 2) - .static_method(Self::keys, "keys", 1) - .static_method(Self::values, "values", 1) - .static_method(Self::entries, "entries", 1) - .static_method(Self::seal, "seal", 1) - .static_method(Self::is_sealed, "isSealed", 1) - .static_method(Self::freeze, "freeze", 1) - .static_method(Self::is_frozen, "isFrozen", 1) - .static_method(Self::prevent_extensions, "preventExtensions", 1) - .static_method(Self::is_extensible, "isExtensible", 1) - .static_method( - Self::get_own_property_descriptor, - "getOwnPropertyDescriptor", - 2, - ) - .static_method( - Self::get_own_property_descriptors, - "getOwnPropertyDescriptors", - 1, - ) - .static_method(Self::get_own_property_names, "getOwnPropertyNames", 1) - .static_method(Self::get_own_property_symbols, "getOwnPropertySymbols", 1) - .static_method(Self::has_own, "hasOwn", 2) - .static_method(Self::from_entries, "fromEntries", 1) - .build(); + BuiltInBuilder::from_standard_constructor_static_shape::( + realm, + &boa_builtins::OBJECT_CONSTRUCTOR_STATIC_SHAPE, + &boa_builtins::OBJECT_PROTOTYPE_STATIC_SHAPE, + ) + .inherits(None) + .accessor(Some(legacy_proto_getter), Some(legacy_setter_proto)) + .method(Self::has_own_property, 1) + .method(Self::property_is_enumerable, 1) + .method(Self::to_string, 0) + .method(Self::to_locale_string, 0) + .method(Self::value_of, 0) + .method(Self::is_prototype_of, 1) + .method(Self::legacy_define_getter, 2) + .method(Self::legacy_define_setter, 2) + .method(Self::legacy_lookup_getter, 1) + .method(Self::legacy_lookup_setter, 1) + .static_method(Self::create, 2) + .static_method(Self::set_prototype_of, 2) + .static_method(Self::get_prototype_of, 1) + .static_method(Self::define_property, 3) + .static_method(Self::define_properties, 2) + .static_method(Self::assign, 2) + .static_method(Self::is, 2) + .static_method(Self::keys, 1) + .static_method(Self::values, 1) + .static_method(Self::entries, 1) + .static_method(Self::seal, 1) + .static_method(Self::is_sealed, 1) + .static_method(Self::freeze, 1) + .static_method(Self::is_frozen, 1) + .static_method(Self::prevent_extensions, 1) + .static_method(Self::is_extensible, 1) + .static_method(Self::get_own_property_descriptor, 2) + .static_method(Self::get_own_property_descriptors, 1) + .static_method(Self::get_own_property_names, 1) + .static_method(Self::get_own_property_symbols, 1) + .static_method(Self::has_own, 2) + .static_method(Self::from_entries, 1) + .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index a0b2807fe0d..38fb3d25905 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -16,9 +16,7 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, object::JsObject, - property::Attribute, realm::Realm, - symbol::JsSymbol, Context, JsArgs, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -34,32 +32,25 @@ impl IntrinsicObject for Reflect { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let to_string_tag = JsSymbol::to_string_tag(); - - BuiltInBuilder::with_intrinsic::(realm) - .static_method(Self::apply, "apply", 3) - .static_method(Self::construct, "construct", 2) - .static_method(Self::define_property, "defineProperty", 3) - .static_method(Self::delete_property, "deleteProperty", 2) - .static_method(Self::get, "get", 2) - .static_method( - Self::get_own_property_descriptor, - "getOwnPropertyDescriptor", - 2, - ) - .static_method(Self::get_prototype_of, "getPrototypeOf", 1) - .static_method(Self::has, "has", 2) - .static_method(Self::is_extensible, "isExtensible", 1) - .static_method(Self::own_keys, "ownKeys", 1) - .static_method(Self::prevent_extensions, "preventExtensions", 1) - .static_method(Self::set, "set", 3) - .static_method(Self::set_prototype_of, "setPrototypeOf", 2) - .static_property( - to_string_tag, - Self::NAME, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .build(); + BuiltInBuilder::with_intrinsic_static_shape::( + realm, + &boa_builtins::REFLECT_OBJECT_STATIC_SHAPE, + ) + .static_method(Self::apply, 3) + .static_method(Self::construct, 2) + .static_method(Self::define_property, 3) + .static_method(Self::delete_property, 2) + .static_method(Self::get, 2) + .static_method(Self::get_own_property_descriptor, 2) + .static_method(Self::get_prototype_of, 1) + .static_method(Self::has, 2) + .static_method(Self::is_extensible, 1) + .static_method(Self::own_keys, 1) + .static_method(Self::prevent_extensions, 1) + .static_method(Self::set, 3) + .static_method(Self::set_prototype_of, 2) + .static_property(Self::NAME) + .build(); } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 879ce8dd015..c349040a23f 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -80,6 +80,13 @@ impl StandardConstructor { } } + fn default_static_shape() -> Self { + Self { + constructor: JsFunction::empty_intrinsic_function_static_shape(true), + prototype: JsObject::default_with_static_shape(), + } + } + /// Return the prototype of the constructor object. /// /// This is the same as `Object.prototype`, `Array.prototype`, etc @@ -157,17 +164,16 @@ impl Default for StandardConstructors { fn default() -> Self { Self { async_generator_function: StandardConstructor::default(), - object: StandardConstructor::default(), + object: StandardConstructor::default_static_shape(), proxy: StandardConstructor::default(), date: StandardConstructor::default(), function: StandardConstructor { - constructor: JsFunction::empty_intrinsic_function(true), - prototype: JsFunction::empty_intrinsic_function(false).into(), + constructor: JsFunction::empty_intrinsic_function_static_shape(true), + prototype: JsFunction::empty_intrinsic_function_static_shape(false).into(), }, async_function: StandardConstructor::default(), generator_function: StandardConstructor::default(), - array: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, + array: StandardConstructor::with_prototype(JsObject::from_data_and_empty_static_shape( ObjectData::array(), )), bigint: StandardConstructor::default(), @@ -814,9 +820,9 @@ pub struct IntrinsicObjects { impl Default for IntrinsicObjects { fn default() -> Self { Self { - reflect: JsObject::default(), - math: JsObject::default(), - json: JsObject::default(), + reflect: JsObject::default_with_static_shape(), + math: JsObject::default_with_static_shape(), + 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/builtins/jsfunction.rs b/boa_engine/src/object/builtins/jsfunction.rs index 522242acc69..26a5fa6b988 100644 --- a/boa_engine/src/object/builtins/jsfunction.rs +++ b/boa_engine/src/object/builtins/jsfunction.rs @@ -40,6 +40,19 @@ impl JsFunction { } } + pub(crate) fn empty_intrinsic_function_static_shape(constructor: bool) -> Self { + Self { + inner: JsObject::from_object_and_vtable( + Object::with_empty_shape(), + if constructor { + &CONSTRUCTOR_INTERNAL_METHODS + } else { + &FUNCTION_INTERNAL_METHODS + }, + ), + } + } + /// Creates a [`JsFunction`] from a [`JsObject`], or returns `None` if the object is not a function. /// /// This does not clone the fields of the function, it only does a shallow clone of the object. diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 9ad7cd52f6a..e815e457d59 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -4,7 +4,7 @@ use super::{ internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS}, - shape::{shared_shape::SharedShape, Shape}, + shape::{shared_shape::SharedShape, static_shape::StaticShape, Shape}, JsPrototype, NativeObject, Object, PropertyMap, }; use crate::{ @@ -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, @@ -119,6 +125,23 @@ impl JsObject { } } + pub(crate) fn from_data_and_empty_static_shape(data: ObjectData) -> Self { + Self { + inner: Gc::new(VTableObject { + object: GcRefCell::new(Object { + kind: data.kind, + properties: PropertyMap::new( + Shape::r#static(StaticShape::new(&boa_builtins::EMPTY_OBJECT_STATIC_SHAPE)), + ThinVec::default(), + ), + 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`], diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index ed69064d16c..fde33bc4d3a 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() } @@ -1501,7 +1519,20 @@ impl Object { pub fn set_prototype>(&mut self, prototype: O) -> bool { let prototype = prototype.into(); if self.extensible { - self.properties.shape = self.properties.shape.change_prototype_transition(prototype); + if let Some(shape) = self.properties.shape.as_static() { + let prototype = self + .properties + .storage + .pop() + .as_ref() + .and_then(JsValue::as_object) + .cloned(); + self.properties.shape = Shape::unique(shape.to_unique(prototype)); + } else { + self.properties.shape = + self.properties.shape.change_prototype_transition(prototype); + } + true } else { // If target is non-extensible, [[SetPrototypeOf]] must return false 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..9b88f61dae7 --- /dev/null +++ b/boa_engine/src/object/shape/static_shape.rs @@ -0,0 +1,145 @@ +// 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)?; + + let (index, attributes) = self.inner.get(key)?; + + Some(Slot { + 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 + .property_table + .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 { + 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, diff --git a/boa_engine/src/vm/opcode/set/property.rs b/boa_engine/src/vm/opcode/set/property.rs index c3307358887..5d3a3ba6cc0 100644 --- a/boa_engine/src/vm/opcode/set/property.rs +++ b/boa_engine/src/vm/opcode/set/property.rs @@ -93,9 +93,11 @@ 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; + if !shape.is_static() { + let prototype = shape.prototype(); + if prototype.map_or(false, |x| x.is_proxy()) { + break 'fast_path; + } } dense_elements.push(value.clone());