From be4a8724b101fefd4dd73164295b0756b1f1dc00 Mon Sep 17 00:00:00 2001 From: Halid Odat Date: Sat, 17 Jul 2021 12:32:41 +0200 Subject: [PATCH] Custom JavaScript string rust type (#1380) - Moved src/value/rcstring.rs => src/string.rs --- boa/src/builtins/console/mod.rs | 8 +- boa/src/builtins/json/mod.rs | 3 +- boa/src/builtins/object/for_in_iterator.rs | 10 +- boa/src/builtins/regexp/mod.rs | 14 +- .../builtins/regexp/regexp_string_iterator.rs | 9 +- boa/src/builtins/string/mod.rs | 28 +- boa/src/bytecompiler.rs | 10 +- boa/src/lib.rs | 3 +- boa/src/object/iter.rs | 14 +- boa/src/object/mod.rs | 22 +- boa/src/property/mod.rs | 15 +- boa/src/string.rs | 448 ++++++++++++++++++ boa/src/symbol.rs | 10 +- boa/src/value/conversions.rs | 4 +- boa/src/value/mod.rs | 12 +- boa/src/value/operations.rs | 10 +- boa/src/value/rcstring.rs | 114 ----- boa/src/vm/code_block.rs | 4 +- 18 files changed, 534 insertions(+), 204 deletions(-) create mode 100644 boa/src/string.rs delete mode 100644 boa/src/value/rcstring.rs diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index e96beebe09..efbc5b78e2 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -20,8 +20,8 @@ use crate::{ builtins::BuiltIn, object::ObjectInitializer, property::Attribute, - value::{display::display_obj, RcString, Value}, - BoaProfiler, Context, Result, + value::{display::display_obj, Value}, + BoaProfiler, Context, JsString, Result, }; use rustc_hash::FxHashMap; use std::time::SystemTime; @@ -137,8 +137,8 @@ pub fn formatter(data: &[Value], context: &mut Context) -> Result { /// This is the internal console object state. #[derive(Debug, Default)] pub(crate) struct Console { - count_map: FxHashMap, - timer_map: FxHashMap, + count_map: FxHashMap, + timer_map: FxHashMap, groups: Vec, } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 05365b1961..d5d828e0c3 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -15,12 +15,13 @@ use crate::{ builtins::BuiltIn, + object::Object, object::ObjectInitializer, property::{Attribute, DataDescriptor, PropertyKey}, + symbol::WellKnownSymbols, value::IntegerOrInfinity, BoaProfiler, Context, Result, Value, }; -use crate::{object::Object, symbol::WellKnownSymbols}; use serde::Serialize; use serde_json::{self, ser::PrettyFormatter, Serializer, Value as JSONValue}; diff --git a/boa/src/builtins/object/for_in_iterator.rs b/boa/src/builtins/object/for_in_iterator.rs index 56e71b152b..f0193be21c 100644 --- a/boa/src/builtins/object/for_in_iterator.rs +++ b/boa/src/builtins/object/for_in_iterator.rs @@ -1,12 +1,12 @@ -use crate::value::RcString; use crate::{ builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, gc::{Finalize, Trace}, object::{GcObject, ObjectData}, + property::PropertyKey, property::{Attribute, DataDescriptor}, - BoaProfiler, Context, Result, Value, + symbol::WellKnownSymbols, + BoaProfiler, Context, JsString, Result, Value, }; -use crate::{property::PropertyKey, symbol::WellKnownSymbols}; use rustc_hash::FxHashSet; use std::collections::VecDeque; @@ -20,8 +20,8 @@ use std::collections::VecDeque; #[derive(Debug, Clone, Finalize, Trace)] pub struct ForInIterator { object: Value, - visited_keys: FxHashSet, - remaining_keys: VecDeque, + visited_keys: FxHashSet, + remaining_keys: VecDeque, object_was_visited: bool, } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index fa1511bc4c..a20a9595ce 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -17,8 +17,8 @@ use crate::{ object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE}, property::{Attribute, DataDescriptor}, symbol::WellKnownSymbols, - value::{IntegerOrInfinity, RcString, Value}, - BoaProfiler, Context, Result, + value::{IntegerOrInfinity, Value}, + BoaProfiler, Context, JsString, Result, }; use regexp_string_iterator::RegExpStringIterator; use regress::Regex; @@ -650,7 +650,7 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexpexec pub(crate) fn abstract_exec( this: &Value, - input: RcString, + input: JsString, context: &mut Context, ) -> Result { // 1. Assert: Type(R) is Object. @@ -694,7 +694,7 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexpbuiltinexec pub(crate) fn abstract_builtin_exec( this: &Value, - input: RcString, + input: JsString, context: &mut Context, ) -> Result { // 1. Assert: R is an initialized RegExp instance. @@ -1160,7 +1160,7 @@ impl RegExp { } // 12. Let accumulatedResult be the empty String. - let mut accumulated_result = RcString::from(""); + let mut accumulated_result = JsString::new(""); // 13. Let nextSourcePosition be 0. let mut next_source_position = 0; @@ -1227,7 +1227,7 @@ impl RegExp { // k. If functionalReplace is true, then // l. Else, - let replacement: RcString; + let replacement: JsString; if functional_replace { // i. Let replacerArgs be « matched ». let mut replacer_args = vec![Value::from(matched)]; @@ -1553,7 +1553,7 @@ impl RegExp { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-advancestringindex -fn advance_string_index(s: RcString, index: usize, unicode: bool) -> usize { +fn advance_string_index(s: JsString, index: usize, unicode: bool) -> usize { // Regress only works with utf8, so this function differs from the spec. // 1. Assert: index ≤ 2^53 - 1. diff --git a/boa/src/builtins/regexp/regexp_string_iterator.rs b/boa/src/builtins/regexp/regexp_string_iterator.rs index 99dcbfd327..87195e326a 100644 --- a/boa/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa/src/builtins/regexp/regexp_string_iterator.rs @@ -17,15 +17,14 @@ use crate::{ object::{GcObject, ObjectData}, property::{Attribute, DataDescriptor}, symbol::WellKnownSymbols, - value::RcString, - BoaProfiler, Context, Result, Value, + BoaProfiler, Context, JsString, Result, Value, }; // TODO: See todos in create_regexp_string_iterator and next. #[derive(Debug, Clone, Finalize, Trace)] pub struct RegExpStringIterator { matcher: Value, - string: RcString, + string: JsString, global: bool, unicode: bool, completed: bool, @@ -33,7 +32,7 @@ pub struct RegExpStringIterator { // TODO: See todos in create_regexp_string_iterator and next. impl RegExpStringIterator { - fn new(matcher: Value, string: RcString, global: bool, unicode: bool) -> Self { + fn new(matcher: Value, string: JsString, global: bool, unicode: bool) -> Self { Self { matcher, string, @@ -51,7 +50,7 @@ impl RegExpStringIterator { /// [spec]: https://tc39.es/ecma262/#sec-createregexpstringiterator pub(crate) fn create_regexp_string_iterator( matcher: &Value, - string: RcString, + string: JsString, global: bool, unicode: bool, context: &mut Context, diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index a66889ae11..b6829c933e 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -21,8 +21,7 @@ use crate::{ object::{ConstructorBuilder, Object, ObjectData}, property::Attribute, symbol::WellKnownSymbols, - value::{RcString, Value}, - BoaProfiler, Context, Result, + BoaProfiler, Context, JsString, Result, Value, }; use std::{ char::{decode_utf16, from_u32}, @@ -31,7 +30,7 @@ use std::{ }; use unicode_normalization::UnicodeNormalization; -pub(crate) fn code_point_at(string: RcString, position: i32) -> Option<(u32, u8, bool)> { +pub(crate) fn code_point_at(string: JsString, position: i32) -> Option<(u32, u8, bool)> { let size = string.encode_utf16().count() as i32; if position < 0 || position >= size { return None; @@ -170,7 +169,7 @@ impl String { .clone() } Some(ref value) => value.to_string(context)?, - None => RcString::default(), + None => JsString::default(), }; if new_target.is_undefined() { @@ -202,7 +201,7 @@ impl String { Ok(this) } - fn this_string_value(this: &Value, context: &mut Context) -> Result { + fn this_string_value(this: &Value, context: &mut Context) -> Result { match this { Value::String(ref string) => return Ok(string.clone()), Value::Object(ref object) => { @@ -685,10 +684,9 @@ impl String { // 11. If functionalReplace is true, then // 12. Else, - let replacement: RcString; - if functional_replace { + let replacement = if functional_replace { // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). - replacement = context + context .call( &replace_value, &Value::Undefined, @@ -698,22 +696,22 @@ impl String { this_str.clone().into(), ], )? - .to_string(context)?; + .to_string(context)? } else { // a. Assert: Type(replaceValue) is String. // b. Let captures be a new empty List. let captures = Vec::new(); // c. Let replacement be ! GetSubstitution(searchString, string, position, captures, undefined, replaceValue). - replacement = get_substitution( + get_substitution( search_str.to_string(), this_str.to_string(), position.unwrap(), captures, Value::undefined(), replace_value.to_string(context)?.to_string(), - )?; - } + )? + }; // 13. Return the string-concatenation of preserved, replacement, and the substring of string from position + searchLength. Ok(format!( @@ -862,9 +860,9 @@ impl String { /// Performs the actual string padding for padStart/End. /// fn string_pad( - primitive: RcString, + primitive: JsString, max_length: i32, - fill_string: Option, + fill_string: Option, at_start: bool, ) -> Value { let primitive_length = primitive.len() as i32; @@ -1480,7 +1478,7 @@ pub(crate) fn get_substitution( captures: Vec, _named_captures: Value, replacement: StdString, -) -> Result { +) -> Result { // 1. Assert: Type(matched) is String. // 2. Let matchLength be the number of code units in matched. diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index 9314215084..270ca1a430 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -4,9 +4,9 @@ use crate::{ op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, }, - value::{RcBigInt, RcString}, + value::RcBigInt, vm::{CodeBlock, Opcode}, - Value, + JsString, Value, }; use std::collections::HashMap; @@ -34,7 +34,7 @@ fn u64_to_array(value: u64) -> [u8; 8] { #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Literal { - String(RcString), + String(JsString), BigInt(RcBigInt), } @@ -64,7 +64,7 @@ enum Access<'a> { pub struct ByteCompiler { code_block: CodeBlock, literals_map: HashMap, - names_map: HashMap, + names_map: HashMap, loops: Vec, } @@ -111,7 +111,7 @@ impl ByteCompiler { return *index; } - let name: RcString = name.into(); + let name = JsString::new(name); let index = self.code_block.names.len() as u32; self.code_block.names.push(name.clone()); self.names_map.insert(name, index); diff --git a/boa/src/lib.rs b/boa/src/lib.rs index f7b0d8c349..1afa7ddc60 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -54,6 +54,7 @@ pub mod object; pub mod profiler; pub mod property; pub mod realm; +pub mod string; pub mod symbol; // syntax module has a lot of acronyms #[allow(clippy::upper_case_acronyms)] @@ -71,7 +72,7 @@ pub(crate) use crate::{exec::Executable, profiler::BoaProfiler}; // Export things to root level #[doc(inline)] -pub use crate::{context::Context, symbol::JsSymbol, value::Value}; +pub use crate::{context::Context, string::JsString, symbol::JsSymbol, value::Value}; use crate::syntax::{ ast::node::StatementList, diff --git a/boa/src/object/iter.rs b/boa/src/object/iter.rs index c0b6d4813f..f5b2f10ced 100644 --- a/boa/src/object/iter.rs +++ b/boa/src/object/iter.rs @@ -1,5 +1,5 @@ use super::{Object, PropertyDescriptor, PropertyKey}; -use crate::{value::RcString, JsSymbol}; +use crate::{JsString, JsSymbol}; use std::{collections::hash_map, iter::FusedIterator}; impl Object { @@ -109,7 +109,7 @@ impl Object { #[derive(Debug, Clone)] pub struct Iter<'a> { indexed_properties: hash_map::Iter<'a, u32, PropertyDescriptor>, - string_properties: hash_map::Iter<'a, RcString, PropertyDescriptor>, + string_properties: hash_map::Iter<'a, JsString, PropertyDescriptor>, symbol_properties: hash_map::Iter<'a, JsSymbol, PropertyDescriptor>, } @@ -342,10 +342,10 @@ impl FusedIterator for IndexPropertyValues<'_> {} /// An iterator over the `String` property entries of an `Object` #[derive(Debug, Clone)] -pub struct StringProperties<'a>(hash_map::Iter<'a, RcString, PropertyDescriptor>); +pub struct StringProperties<'a>(hash_map::Iter<'a, JsString, PropertyDescriptor>); impl<'a> Iterator for StringProperties<'a> { - type Item = (&'a RcString, &'a PropertyDescriptor); + type Item = (&'a JsString, &'a PropertyDescriptor); #[inline] fn next(&mut self) -> Option { @@ -369,10 +369,10 @@ impl FusedIterator for StringProperties<'_> {} /// An iterator over the string keys (`RcString`) of an `Object`. #[derive(Debug, Clone)] -pub struct StringPropertyKeys<'a>(hash_map::Keys<'a, RcString, PropertyDescriptor>); +pub struct StringPropertyKeys<'a>(hash_map::Keys<'a, JsString, PropertyDescriptor>); impl<'a> Iterator for StringPropertyKeys<'a> { - type Item = &'a RcString; + type Item = &'a JsString; #[inline] fn next(&mut self) -> Option { @@ -396,7 +396,7 @@ impl FusedIterator for StringPropertyKeys<'_> {} /// An iterator over the string values (`Property`) of an `Object`. #[derive(Debug, Clone)] -pub struct StringPropertyValues<'a>(hash_map::Values<'a, RcString, PropertyDescriptor>); +pub struct StringPropertyValues<'a>(hash_map::Values<'a, JsString, PropertyDescriptor>); impl<'a> Iterator for StringPropertyValues<'a> { type Item = &'a PropertyDescriptor; diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 3da5b7ece2..88efd42b4e 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -15,8 +15,8 @@ use crate::{ context::StandardConstructor, gc::{Finalize, Trace}, property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, - value::{RcBigInt, RcString, Value}, - BoaProfiler, Context, JsSymbol, + value::{RcBigInt, Value}, + BoaProfiler, Context, JsString, JsSymbol, }; use rustc_hash::FxHashMap; use std::{ @@ -69,7 +69,7 @@ pub struct Object { pub data: ObjectData, indexed_properties: FxHashMap, /// Properties - string_properties: FxHashMap, + string_properties: FxHashMap, /// Symbol Properties symbol_properties: FxHashMap, /// Instance prototype `__proto__`. @@ -93,7 +93,7 @@ pub enum ObjectData { Function(Function), Set(OrderedSet), SetIterator(SetIterator), - String(RcString), + String(JsString), StringIterator(StringIterator), Number(f64), Symbol(JsSymbol), @@ -216,7 +216,7 @@ impl Object { #[inline] pub fn string(value: S) -> Self where - S: Into, + S: Into, { Self { data: ObjectData::String(value.into()), @@ -413,7 +413,7 @@ impl Object { } #[inline] - pub fn as_string(&self) -> Option { + pub fn as_string(&self) -> Option { match self.data { ObjectData::String(ref string) => Some(string.clone()), _ => None, @@ -628,13 +628,13 @@ impl Object { #[derive(Debug, Clone)] pub struct FunctionBinding { binding: PropertyKey, - name: RcString, + name: JsString, } impl From<&str> for FunctionBinding { #[inline] fn from(name: &str) -> Self { - let name: RcString = name.into(); + let name: JsString = name.into(); Self { binding: name.clone().into(), @@ -646,7 +646,7 @@ impl From<&str> for FunctionBinding { impl From for FunctionBinding { #[inline] fn from(name: String) -> Self { - let name: RcString = name.into(); + let name: JsString = name.into(); Self { binding: name.clone().into(), @@ -655,9 +655,9 @@ impl From for FunctionBinding { } } -impl From for FunctionBinding { +impl From for FunctionBinding { #[inline] - fn from(name: RcString) -> Self { + fn from(name: JsString) -> Self { Self { binding: name.clone().into(), name, diff --git a/boa/src/property/mod.rs b/boa/src/property/mod.rs index bd3a889191..b5411c6645 100644 --- a/boa/src/property/mod.rs +++ b/boa/src/property/mod.rs @@ -17,8 +17,7 @@ use crate::{ gc::{Finalize, Trace}, object::GcObject, - value::{RcString, Value}, - JsSymbol, + JsString, JsSymbol, Value, }; use std::{convert::TryFrom, fmt}; @@ -306,14 +305,14 @@ impl PropertyDescriptor { /// [spec]: https://tc39.es/ecma262/#sec-ispropertykey #[derive(Trace, Finalize, Debug, Clone)] pub enum PropertyKey { - String(RcString), + String(JsString), Symbol(JsSymbol), Index(u32), } -impl From for PropertyKey { +impl From for PropertyKey { #[inline] - fn from(string: RcString) -> PropertyKey { + fn from(string: JsString) -> PropertyKey { if let Ok(index) = string.parse() { PropertyKey::Index(index) } else { @@ -430,7 +429,7 @@ impl From for PropertyKey { if let Ok(index) = u32::try_from(value) { PropertyKey::Index(index) } else { - PropertyKey::String(RcString::from(value.to_string())) + PropertyKey::String(JsString::from(value.to_string())) } } } @@ -440,7 +439,7 @@ impl From for PropertyKey { if let Ok(index) = u32::try_from(value) { PropertyKey::Index(index) } else { - PropertyKey::String(RcString::from(value.to_string())) + PropertyKey::String(JsString::from(value.to_string())) } } } @@ -450,7 +449,7 @@ impl From for PropertyKey { if let Ok(index) = u32::try_from(value) { PropertyKey::Index(index) } else { - PropertyKey::String(RcString::from(value.to_string())) + PropertyKey::String(JsString::from(value.to_string())) } } } diff --git a/boa/src/string.rs b/boa/src/string.rs new file mode 100644 index 0000000000..60cfc50dc6 --- /dev/null +++ b/boa/src/string.rs @@ -0,0 +1,448 @@ +use crate::gc::{empty_trace, Finalize, Trace}; +use std::{ + alloc::{alloc, dealloc, Layout}, + borrow::Borrow, + cell::Cell, + hash::{Hash, Hasher}, + marker::PhantomData, + ops::Deref, + ptr::{copy_nonoverlapping, NonNull}, +}; + +/// The inner representation of a [`JsString`]. +#[repr(C)] +struct Inner { + /// The utf8 length, the number of bytes. + len: usize, + + /// The number of references to the string. + /// + /// When this reaches `0` the string is deallocated. + refcount: Cell, + + /// An empty array which is used to get the offset of string data. + data: [u8; 0], +} + +impl Inner { + /// Create a new `Inner` from `&str`. + #[inline] + fn new(s: &str) -> NonNull { + // We get the layout of the `Inner` type and we extend by the size + // of the string array. + let inner_layout = Layout::new::(); + let (layout, offset) = inner_layout + .extend(Layout::array::(s.len()).unwrap()) + .unwrap(); + + let inner = unsafe { + let inner = alloc(layout) as *mut Inner; + + // Write the first part, the Inner. + inner.write(Inner { + len: s.len(), + refcount: Cell::new(1), + data: [0; 0], + }); + + // Get offset into the string data. + let data = (*inner).data.as_mut_ptr(); + + debug_assert!(std::ptr::eq(inner.cast::().add(offset), data)); + + // Copy string data into data offset. + copy_nonoverlapping(s.as_ptr(), data, s.len()); + + inner + }; + + // Safety: We already know it's not null, so this is safe. + unsafe { NonNull::new_unchecked(inner) } + } + + /// Concatinate two string. + #[inline] + fn concat(x: &str, y: &str) -> NonNull { + let total_string_size = x.len() + y.len(); + + // We get the layout of the `Inner` type and we extend by the size + // of the string array. + let inner_layout = Layout::new::(); + let (layout, offset) = inner_layout + .extend(Layout::array::(total_string_size).unwrap()) + .unwrap(); + + let inner = unsafe { + let inner = alloc(layout) as *mut Inner; + + // Write the first part, the Inner. + inner.write(Inner { + len: total_string_size, + refcount: Cell::new(1), + data: [0; 0], + }); + + // Get offset into the string data. + let data = (*inner).data.as_mut_ptr(); + + debug_assert!(std::ptr::eq(inner.cast::().add(offset), data)); + + // Copy the two string data into data offset. + copy_nonoverlapping(x.as_ptr(), data, x.len()); + copy_nonoverlapping(y.as_ptr(), data.add(x.len()), y.len()); + + inner + }; + + // Safety: We already know it's not null, so this is safe. + unsafe { NonNull::new_unchecked(inner) } + } + + /// Deallocate inner type with string data. + #[inline] + unsafe fn dealloc(x: NonNull) { + let len = (*x.as_ptr()).len; + + let inner_layout = Layout::new::(); + let (layout, _offset) = inner_layout + .extend(Layout::array::(len).unwrap()) + .unwrap(); + + dealloc(x.as_ptr() as _, layout); + } +} + +/// This represents a JavaScript primitive string. +/// +/// This is similar to `Rc`. But unlike `Rc` which stores the length +/// on the stack and a pointer to the data (this is also known as fat pointers). +/// The `JsString` length and data is stored on the heap. and just an non-null +/// pointer is kept, so its size is the size of a pointer. +pub struct JsString { + inner: NonNull, + _marker: PhantomData>, +} + +impl Default for JsString { + #[inline] + fn default() -> Self { + Self::new("") + } +} + +impl JsString { + /// Create a new JavaScript string. + #[inline] + pub fn new>(s: S) -> Self { + let s = s.as_ref(); + Self { + inner: Inner::new(s), + _marker: PhantomData, + } + } + + /// Concatinate two string. + pub fn concat(x: T, y: U) -> JsString + where + T: AsRef, + U: AsRef, + { + let x = x.as_ref(); + let y = y.as_ref(); + + Self { + inner: Inner::concat(x, y), + _marker: PhantomData, + } + } + + /// Return the inner representation. + #[inline] + fn inner(&self) -> &Inner { + unsafe { self.inner.as_ref() } + } + + /// Return the JavaScript string as a rust `&str`. + #[inline] + pub fn as_str(&self) -> &str { + let inner = self.inner(); + + unsafe { + let slice = std::slice::from_raw_parts(inner.data.as_ptr(), inner.len); + std::str::from_utf8_unchecked(slice) + } + } + + /// Gets the number of `JsString`s which point to this allocation. + #[inline] + pub fn refcount(this: &Self) -> usize { + this.inner().refcount.get() + } + + /// Returns `true` if the two `JsString`s point to the same allocation (in a vein similar to [`ptr::eq`]). + /// + /// [`ptr::eq`]: std::ptr::eq + #[inline] + pub fn ptr_eq(x: &Self, y: &Self) -> bool { + x.inner == y.inner + } +} + +impl Finalize for JsString {} + +// Safety: [`JsString`] does not contain any objects which recquire trace, +// so this is safe. +unsafe impl Trace for JsString { + empty_trace!(); +} + +impl Clone for JsString { + #[inline] + fn clone(&self) -> Self { + let inner = self.inner(); + inner.refcount.set(inner.refcount.get() + 1); + + JsString { + inner: self.inner, + _marker: PhantomData, + } + } +} + +impl Drop for JsString { + #[inline] + fn drop(&mut self) { + let inner = self.inner(); + if inner.refcount.get() == 1 { + // Safety: If refcount is 1 and we call drop, that means this is the last + // JsString which points to this memory allocation, so deallocating it is safe. + unsafe { + Inner::dealloc(self.inner); + } + } else { + inner.refcount.set(inner.refcount.get() - 1); + } + } +} + +impl std::fmt::Debug for JsString { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_str().fmt(f) + } +} + +impl std::fmt::Display for JsString { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_str().fmt(f) + } +} + +impl From<&str> for JsString { + #[inline] + fn from(s: &str) -> Self { + Self::new(s) + } +} + +impl From> for JsString { + #[inline] + fn from(s: Box) -> Self { + Self::new(s) + } +} + +impl From for JsString { + #[inline] + fn from(s: String) -> Self { + Self::new(s) + } +} + +impl AsRef for JsString { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Borrow for JsString { + #[inline] + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl Deref for JsString { + type Target = str; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl PartialEq for JsString { + #[inline] + fn eq(&self, other: &Self) -> bool { + // If they point at the same memory allocation, then they are equal. + if Self::ptr_eq(self, other) { + return true; + } + + self.as_str() == other.as_str() + } +} + +impl Eq for JsString {} + +impl Hash for JsString { + #[inline] + fn hash(&self, state: &mut H) { + self.as_str().hash(state); + } +} + +impl PartialOrd for JsString { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.as_str().partial_cmp(other) + } +} + +impl Ord for JsString { + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_str().cmp(other) + } +} + +impl PartialEq for JsString { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl PartialEq for str { + #[inline] + fn eq(&self, other: &JsString) -> bool { + self == other.as_str() + } +} + +impl PartialEq<&str> for JsString { + #[inline] + fn eq(&self, other: &&str) -> bool { + self.as_str() == *other + } +} + +impl PartialEq for &str { + #[inline] + fn eq(&self, other: &JsString) -> bool { + *self == other.as_str() + } +} + +#[cfg(test)] +mod tests { + use super::JsString; + use std::mem::size_of; + + #[test] + fn empty() { + let _ = JsString::new(""); + } + + #[test] + fn pointer_size() { + assert_eq!(size_of::(), size_of::<*const u8>()); + assert_eq!(size_of::>(), size_of::<*const u8>()); + } + + #[test] + fn refcount() { + let x = JsString::new("Hello wrold"); + assert_eq!(JsString::refcount(&x), 1); + + { + let y = x.clone(); + assert_eq!(JsString::refcount(&x), 2); + assert_eq!(JsString::refcount(&y), 2); + + { + let z = y.clone(); + assert_eq!(JsString::refcount(&x), 3); + assert_eq!(JsString::refcount(&y), 3); + assert_eq!(JsString::refcount(&z), 3); + } + + assert_eq!(JsString::refcount(&x), 2); + assert_eq!(JsString::refcount(&y), 2); + } + + assert_eq!(JsString::refcount(&x), 1); + } + + #[test] + fn ptr_eq() { + let x = JsString::new("Hello"); + let y = x.clone(); + + assert!(JsString::ptr_eq(&x, &y)); + + let z = JsString::new("Hello"); + assert!(!JsString::ptr_eq(&x, &z)); + assert!(!JsString::ptr_eq(&y, &z)); + } + + #[test] + fn as_str() { + let s = "Hello"; + let x = JsString::new(s); + + assert_eq!(x.as_str(), s); + } + + #[test] + fn hash() { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let s = "Hello, world!"; + let x = JsString::new(s); + + assert_eq!(x.as_str(), s); + + let mut hasher = DefaultHasher::new(); + s.hash(&mut hasher); + let s_hash = hasher.finish(); + let mut hasher = DefaultHasher::new(); + x.hash(&mut hasher); + let x_hash = hasher.finish(); + + assert_eq!(s_hash, x_hash); + } + + #[test] + fn concat() { + let x = JsString::new("hello"); + let y = ", "; + let z = JsString::new("world"); + let w = String::from("!"); + + let xy = JsString::concat(x, y); + assert_eq!(xy, "hello, "); + assert_eq!(JsString::refcount(&xy), 1); + + let xyz = JsString::concat(xy, z); + assert_eq!(xyz, "hello, world"); + assert_eq!(JsString::refcount(&xyz), 1); + + let xyzw = JsString::concat(xyz, w); + assert_eq!(xyzw, "hello, world!"); + assert_eq!(JsString::refcount(&xyzw), 1); + } +} diff --git a/boa/src/symbol.rs b/boa/src/symbol.rs index 353dee4dd9..e35f154dce 100644 --- a/boa/src/symbol.rs +++ b/boa/src/symbol.rs @@ -17,7 +17,7 @@ use crate::{ gc::{empty_trace, Finalize, Trace}, - value::RcString, + JsString, }; use std::{ cell::Cell, @@ -246,7 +246,7 @@ impl WellKnownSymbols { #[derive(Debug, Clone)] struct Inner { hash: u64, - description: Option, + description: Option, } /// This represents a JavaScript symbol primitive. @@ -258,7 +258,7 @@ pub struct JsSymbol { impl JsSymbol { /// Create a new symbol. #[inline] - pub fn new(description: Option) -> Self { + pub fn new(description: Option) -> Self { let hash = SYMBOL_HASH_COUNT.with(|count| { let hash = count.get(); count.set(hash + 1); @@ -272,7 +272,7 @@ impl JsSymbol { /// Create a new symbol with a specified hash and description. #[inline] - fn with_hash(hash: u64, description: Option) -> Self { + fn with_hash(hash: u64, description: Option) -> Self { Self { inner: Rc::new(Inner { hash, description }), } @@ -280,7 +280,7 @@ impl JsSymbol { /// Returns the `Symbol`s description. #[inline] - pub fn description(&self) -> Option { + pub fn description(&self) -> Option { self.inner.description.clone() } diff --git a/boa/src/value/conversions.rs b/boa/src/value/conversions.rs index b920242ff2..13107ddfdf 100644 --- a/boa/src/value/conversions.rs +++ b/boa/src/value/conversions.rs @@ -44,9 +44,9 @@ impl From for Value { } } -impl From for Value { +impl From for Value { #[inline] - fn from(value: RcString) -> Self { + fn from(value: JsString) -> Self { Value::String(value) } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 9708e0f1a1..8bdfc4dfa6 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -14,7 +14,7 @@ use crate::{ object::{GcObject, Object, ObjectData}, property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, symbol::{JsSymbol, WellKnownSymbols}, - BoaProfiler, Context, Result, + BoaProfiler, Context, JsString, Result, }; use gc::{Finalize, Trace}; use serde_json::{Number as JSONNumber, Value as JSONValue}; @@ -31,7 +31,6 @@ mod equality; mod hash; mod operations; mod rcbigint; -mod rcstring; mod r#type; pub use conversions::*; @@ -41,7 +40,6 @@ pub use hash::*; pub use operations::*; pub use r#type::Type; pub use rcbigint::RcBigInt; -pub use rcstring::RcString; /// A Javascript value #[derive(Trace, Finalize, Debug, Clone)] @@ -53,7 +51,7 @@ pub enum Value { /// `boolean` - A `true` / `false` value, for if a certain criteria is met. Boolean(bool), /// `String` - A UTF-8 string, such as `"Hello, world"`. - String(RcString), + String(JsString), /// `Number` - A 64-bit floating point number, such as `3.1415` Rational(f64), /// `Number` - A 32-bit integer, such as `42`. @@ -109,7 +107,7 @@ impl Value { #[inline] pub fn string(value: S) -> Self where - S: Into, + S: Into, { Self::String(value.into()) } @@ -368,7 +366,7 @@ impl Value { /// Returns the string if the values is a string, otherwise `None`. #[inline] - pub fn as_string(&self) -> Option<&RcString> { + pub fn as_string(&self) -> Option<&JsString> { match self { Self::String(ref string) => Some(string), _ => None, @@ -643,7 +641,7 @@ impl Value { /// Converts the value to a string. /// /// This function is equivalent to `String(value)` in JavaScript. - pub fn to_string(&self, context: &mut Context) -> Result { + pub fn to_string(&self, context: &mut Context) -> Result { match self { Value::Null => Ok("null".into()), Value::Undefined => Ok("undefined".into()), diff --git a/boa/src/value/operations.rs b/boa/src/value/operations.rs index 7067bc075e..cbd30172e2 100644 --- a/boa/src/value/operations.rs +++ b/boa/src/value/operations.rs @@ -11,9 +11,9 @@ impl Value { (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y), (Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)), - (Self::String(ref x), Self::String(ref y)) => Self::string(format!("{}{}", x, y)), - (Self::String(ref x), ref y) => Self::string(format!("{}{}", x, y.to_string(context)?)), - (ref x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(context)?, y)), + (Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)), + (Self::String(ref x), ref y) => Self::from(JsString::concat(x, y.to_string(context)?)), + (ref x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)), (Self::BigInt(ref n1), Self::BigInt(ref n2)) => { Self::bigint(n1.as_inner().clone() + n2.as_inner().clone()) } @@ -24,10 +24,10 @@ impl Value { other.to_primitive(context, PreferredType::Default)?, ) { (Self::String(ref x), ref y) => { - Self::string(format!("{}{}", x, y.to_string(context)?)) + Self::from(JsString::concat(x, y.to_string(context)?)) } (ref x, Self::String(ref y)) => { - Self::string(format!("{}{}", x.to_string(context)?, y)) + Self::from(JsString::concat(x.to_string(context)?, y)) } (x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Self::rational(x + y), diff --git a/boa/src/value/rcstring.rs b/boa/src/value/rcstring.rs deleted file mode 100644 index 987d7a7a39..0000000000 --- a/boa/src/value/rcstring.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crate::gc::{empty_trace, Finalize, Trace}; - -use std::{ - borrow::Borrow, - fmt::{self, Display}, - ops::Deref, - rc::Rc, -}; - -#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct RcString(Rc); - -unsafe impl Trace for RcString { - empty_trace!(); -} - -impl RcString { - #[inline] - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl Default for RcString { - #[inline] - fn default() -> Self { - Self(Rc::from(String::new())) - } -} - -impl Display for RcString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl PartialEq for RcString { - #[inline] - fn eq(&self, other: &str) -> bool { - self.as_str() == other - } -} - -impl PartialEq for str { - #[inline] - fn eq(&self, other: &RcString) -> bool { - self == other.as_str() - } -} - -impl PartialEq<&str> for RcString { - #[inline] - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} - -impl PartialEq for &str { - #[inline] - fn eq(&self, other: &RcString) -> bool { - *self == other.as_str() - } -} - -impl Deref for RcString { - type Target = str; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Borrow for RcString { - #[inline] - fn borrow(&self) -> &str { - self.0.borrow() - } -} - -impl AsRef for RcString { - #[inline] - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl From for RcString { - #[inline] - fn from(string: String) -> Self { - Self(Rc::from(string)) - } -} - -impl From<&RcString> for String { - #[inline] - fn from(string: &RcString) -> Self { - string.to_string() - } -} - -impl From> for RcString { - #[inline] - fn from(string: Box) -> Self { - Self(Rc::from(string)) - } -} - -impl From<&str> for RcString { - #[inline] - fn from(string: &str) -> Self { - Self(Rc::from(string)) - } -} diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index 8f6e51a6c5..d3eb4bb8db 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -1,4 +1,4 @@ -use crate::{value::RcString, vm::Opcode, Value}; +use crate::{vm::Opcode, JsString, Value}; use std::{convert::TryInto, fmt::Write, mem::size_of}; @@ -25,7 +25,7 @@ pub struct CodeBlock { pub(crate) literals: Vec, /// Variables names - pub(crate) names: Vec, + pub(crate) names: Vec, } impl Default for CodeBlock {