mirror of https://github.com/boa-dev/boa.git
Halid Odat
3 years ago
committed by
GitHub
18 changed files with 534 additions and 204 deletions
@ -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<usize>, |
||||
|
||||
/// 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<Self> { |
||||
// We get the layout of the `Inner` type and we extend by the size
|
||||
// of the string array.
|
||||
let inner_layout = Layout::new::<Inner>(); |
||||
let (layout, offset) = inner_layout |
||||
.extend(Layout::array::<u8>(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::<u8>().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<Inner> { |
||||
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::<Inner>(); |
||||
let (layout, offset) = inner_layout |
||||
.extend(Layout::array::<u8>(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::<u8>().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<Inner>) { |
||||
let len = (*x.as_ptr()).len; |
||||
|
||||
let inner_layout = Layout::new::<Inner>(); |
||||
let (layout, _offset) = inner_layout |
||||
.extend(Layout::array::<u8>(len).unwrap()) |
||||
.unwrap(); |
||||
|
||||
dealloc(x.as_ptr() as _, layout); |
||||
} |
||||
} |
||||
|
||||
/// This represents a JavaScript primitive string.
|
||||
///
|
||||
/// This is similar to `Rc<str>`. But unlike `Rc<str>` 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<Inner>, |
||||
_marker: PhantomData<std::rc::Rc<str>>, |
||||
} |
||||
|
||||
impl Default for JsString { |
||||
#[inline] |
||||
fn default() -> Self { |
||||
Self::new("") |
||||
} |
||||
} |
||||
|
||||
impl JsString { |
||||
/// Create a new JavaScript string.
|
||||
#[inline] |
||||
pub fn new<S: AsRef<str>>(s: S) -> Self { |
||||
let s = s.as_ref(); |
||||
Self { |
||||
inner: Inner::new(s), |
||||
_marker: PhantomData, |
||||
} |
||||
} |
||||
|
||||
/// Concatinate two string.
|
||||
pub fn concat<T, U>(x: T, y: U) -> JsString |
||||
where |
||||
T: AsRef<str>, |
||||
U: AsRef<str>, |
||||
{ |
||||
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<Box<str>> for JsString { |
||||
#[inline] |
||||
fn from(s: Box<str>) -> Self { |
||||
Self::new(s) |
||||
} |
||||
} |
||||
|
||||
impl From<String> for JsString { |
||||
#[inline] |
||||
fn from(s: String) -> Self { |
||||
Self::new(s) |
||||
} |
||||
} |
||||
|
||||
impl AsRef<str> for JsString { |
||||
#[inline] |
||||
fn as_ref(&self) -> &str { |
||||
self.as_str() |
||||
} |
||||
} |
||||
|
||||
impl Borrow<str> 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<JsString> 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<H: Hasher>(&self, state: &mut H) { |
||||
self.as_str().hash(state); |
||||
} |
||||
} |
||||
|
||||
impl PartialOrd for JsString { |
||||
#[inline] |
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
||||
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<str> for JsString { |
||||
#[inline] |
||||
fn eq(&self, other: &str) -> bool { |
||||
self.as_str() == other |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<JsString> 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<JsString> 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::<JsString>(), size_of::<*const u8>()); |
||||
assert_eq!(size_of::<Option<JsString>>(), 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); |
||||
} |
||||
} |
@ -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<str>); |
||||
|
||||
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<str> for RcString { |
||||
#[inline] |
||||
fn eq(&self, other: &str) -> bool { |
||||
self.as_str() == other |
||||
} |
||||
} |
||||
|
||||
impl PartialEq<RcString> 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<RcString> 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<str> for RcString { |
||||
#[inline] |
||||
fn borrow(&self) -> &str { |
||||
self.0.borrow() |
||||
} |
||||
} |
||||
|
||||
impl AsRef<str> for RcString { |
||||
#[inline] |
||||
fn as_ref(&self) -> &str { |
||||
self.as_str() |
||||
} |
||||
} |
||||
|
||||
impl From<String> 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<Box<str>> for RcString { |
||||
#[inline] |
||||
fn from(string: Box<str>) -> Self { |
||||
Self(Rc::from(string)) |
||||
} |
||||
} |
||||
|
||||
impl From<&str> for RcString { |
||||
#[inline] |
||||
fn from(string: &str) -> Self { |
||||
Self(Rc::from(string)) |
||||
} |
||||
} |
Loading…
Reference in new issue