diff --git a/boa/src/string.rs b/boa/src/string.rs index 7a6bf6c234..b5179737b1 100644 --- a/boa/src/string.rs +++ b/boa/src/string.rs @@ -2,6 +2,7 @@ use crate::{ builtins::string::is_trimmable_whitespace, gc::{empty_trace, Finalize, Trace}, }; +use rustc_hash::FxHashSet; use std::{ alloc::{alloc, dealloc, Layout}, borrow::Borrow, @@ -12,6 +13,183 @@ use std::{ ptr::{copy_nonoverlapping, NonNull}, }; +const CONSTANTS_ARRAY: [&str; 127] = [ + // Empty string + "", + // Misc + ",", + ":", + // Generic use + "name", + "length", + "arguments", + "prototype", + "constructor", + // typeof + "null", + "undefined", + "number", + "string", + "symbol", + "bigint", + "object", + "function", + // Property descriptor + "value", + "get", + "set", + "writable", + "enumerable", + "configurable", + // Object object + "Object", + "assing", + "create", + "toString", + "valueOf", + "is", + "seal", + "isSealed", + "freeze", + "isFrozen", + "keys", + "values", + "entries", + // Function object + "Function", + "apply", + "bind", + "call", + // Array object + "Array", + "from", + "isArray", + "of", + "get [Symbol.species]", + "copyWithin", + "entries", + "every", + "fill", + "filter", + "find", + "findIndex", + "flat", + "flatMap", + "forEach", + "includes", + "indexOf", + "join", + "map", + "reduce", + "reduceRight", + "reverse", + "shift", + "slice", + "some", + "sort", + "unshift", + "push", + "pop", + // String object + "String", + "charAt", + "charCodeAt", + "concat", + "endsWith", + "includes", + "indexOf", + "lastIndexOf", + "match", + "matchAll", + "normalize", + "padEnd", + "padStart", + "repeat", + "replace", + "replaceAll", + "search", + "slice", + "split", + "startsWith", + "substring", + "toLowerString", + "toUpperString", + "trim", + "trimEnd", + "trimStart", + // Number object + "Number", + // Boolean object + "Boolean", + // RegExp object + "RegExp", + "exec", + "test", + "flags", + "index", + "lastIndex", + // Symbol object + "Symbol", + "for", + "keyFor", + "description", + "[Symbol.toPrimitive]", + "", + // Map object + "Map", + "clear", + "delete", + "get", + "has", + "set", + "size", + // Set object + "Set", + // Reflect object + "Reflect", + // Error objects + "Error", + "TypeError", + "RangeError", + "SyntaxError", + "ReferenceError", + "EvalError", + "URIError", + "message", + // Date object + "Date", + "toJSON", +]; + +const MAX_CONSTANT_STRING_LENGTH: usize = { + let mut max = 0; + let mut i = 0; + while i < CONSTANTS_ARRAY.len() { + let len = CONSTANTS_ARRAY[i].len(); + if len > max { + max = len; + } + i += 1; + } + max +}; + +thread_local! { + static CONSTANTS: FxHashSet = { + let mut constants = FxHashSet::default(); + + for s in CONSTANTS_ARRAY.iter() { + let s = JsString { + inner: Inner::new(s), + _marker: PhantomData, + }; + constants.insert(s); + } + + constants + }; +} + /// The inner representation of a [`JsString`]. #[repr(C)] struct Inner { @@ -140,10 +318,23 @@ impl Default for JsString { } impl JsString { + /// Create an empty string, same as calling default. + #[inline] + pub fn empty() -> Self { + JsString::default() + } + /// Create a new JavaScript string. #[inline] pub fn new>(s: S) -> Self { let s = s.as_ref(); + + if s.len() <= MAX_CONSTANT_STRING_LENGTH { + if let Some(constant) = CONSTANTS.with(|c| c.get(s).cloned()) { + return constant; + } + } + Self { inner: Inner::new(s), _marker: PhantomData, @@ -159,18 +350,34 @@ impl JsString { let x = x.as_ref(); let y = y.as_ref(); - Self { + let this = Self { inner: Inner::concat_array(&[x, y]), _marker: PhantomData, + }; + + if this.len() <= MAX_CONSTANT_STRING_LENGTH { + if let Some(constant) = CONSTANTS.with(|c| c.get(&this).cloned()) { + return constant; + } } + + this } /// Concatenate array of string. pub fn concat_array(strings: &[&str]) -> JsString { - Self { + let this = Self { inner: Inner::concat_array(strings), _marker: PhantomData, + }; + + if this.len() <= MAX_CONSTANT_STRING_LENGTH { + if let Some(constant) = CONSTANTS.with(|c| c.get(&this).cloned()) { + return constant; + } } + + this } /// Return the inner representation.