boa_string/vtable/
shared.rs1use crate::iter::CodePointsIter;
9use crate::r#type::{InternalStringType, Latin1, Utf16};
10use crate::vtable::JsStringVTable;
11use crate::{JsStr, JsStrVariant, JsString, JsStringKind};
12use std::hash::{Hash, Hasher};
13use std::marker::PhantomData;
14use std::mem::{ManuallyDrop, offset_of};
15use std::ptr::NonNull;
16use std::sync::Arc;
17
18trait SharedType: InternalStringType {
20 const SHARED_KIND: JsStringKind;
21}
22
23impl SharedType for Latin1 {
24 const SHARED_KIND: JsStringKind = JsStringKind::Latin1Shared;
25}
26
27impl SharedType for Utf16 {
28 const SHARED_KIND: JsStringKind = JsStringKind::Utf16Shared;
29}
30
31#[repr(C)]
39struct SharedString<T: SharedType> {
40 vtable: JsStringVTable,
41 data: Box<[T::Byte]>,
42 _marker: PhantomData<fn() -> T>,
43}
44
45static_assertions::const_assert!(offset_of!(SharedString<Latin1>, vtable) == 0);
46static_assertions::const_assert!(offset_of!(SharedString<Utf16>, vtable) == 0);
47
48#[derive(Clone)]
49enum SharedInner {
50 Latin1(Arc<SharedString<Latin1>>),
51 Utf16(Arc<SharedString<Utf16>>),
52}
53
54#[derive(Clone)]
61pub struct SharedJsStr {
62 inner: SharedInner,
63}
64
65const _: fn() = || {
66 fn assert<T: Send + Sync>() {}
67 assert::<SharedJsStr>();
68};
69
70impl SharedJsStr {
71 #[inline]
73 #[must_use]
74 pub fn as_str(&self) -> JsStr<'_> {
75 match &self.inner {
76 SharedInner::Latin1(arc) => JsStr::latin1(&arc.data),
77 SharedInner::Utf16(arc) => JsStr::utf16(&arc.data),
78 }
79 }
80
81 #[inline]
82 fn from_latin1(bytes: &[u8]) -> Self {
83 Self {
84 inner: SharedInner::Latin1(SharedString::<Latin1>::from_units(bytes)),
85 }
86 }
87
88 #[inline]
89 fn from_utf16(units: &[u16]) -> Self {
90 Self {
91 inner: SharedInner::Utf16(SharedString::<Utf16>::from_units(units)),
92 }
93 }
94
95 #[inline]
97 #[must_use]
98 pub(crate) fn into_js_string(self) -> JsString {
99 let ptr = match self.inner {
100 SharedInner::Latin1(arc) => vtable_ptr(arc),
101 SharedInner::Utf16(arc) => vtable_ptr(arc),
102 };
103 unsafe { JsString::from_ptr(ptr) }
106 }
107}
108
109impl<T: SharedType> SharedString<T> {
110 fn from_units(units: &[T::Byte]) -> Arc<Self> {
111 let data: Box<[T::Byte]> = Box::from(units);
112 let len = data.len();
113 Arc::new(Self {
114 vtable: JsStringVTable {
115 clone: shared_clone::<T>,
116 drop: shared_drop::<T>,
117 as_str: shared_as_str::<T>,
118 code_points: shared_code_points::<T>,
119 refcount: shared_refcount::<T>,
120 len,
121 kind: T::SHARED_KIND,
122 },
123 data,
124 _marker: PhantomData,
125 })
126 }
127}
128
129#[inline]
130fn shared_clone<T: SharedType>(vtable: NonNull<JsStringVTable>) -> JsString {
131 unsafe {
136 Arc::increment_strong_count(vtable.cast::<SharedString<T>>().as_ptr());
137 JsString::from_ptr(vtable)
138 }
139}
140
141#[inline]
142fn shared_drop<T: SharedType>(vtable: NonNull<JsStringVTable>) {
143 unsafe {
147 drop(Arc::from_raw(vtable.cast::<SharedString<T>>().as_ptr()));
148 }
149}
150
151#[inline]
152fn shared_as_str<T: SharedType>(vtable: NonNull<JsStringVTable>) -> JsStr<'static> {
153 let this: &SharedString<T> = unsafe { vtable.cast().as_ref() };
156 debug_assert_eq!(this.vtable.len, this.data.len());
157 let ptr = this.data.as_ptr();
158 let len = this.data.len();
159 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
163 T::str_ctor(slice)
164}
165
166#[inline]
167fn shared_code_points<T: SharedType>(vtable: NonNull<JsStringVTable>) -> CodePointsIter<'static> {
168 CodePointsIter::new(shared_as_str::<T>(vtable))
169}
170
171#[inline]
173#[allow(clippy::unnecessary_wraps)]
174fn shared_refcount<T: SharedType>(vtable: NonNull<JsStringVTable>) -> Option<usize> {
175 let arc =
178 unsafe { ManuallyDrop::new(Arc::from_raw(vtable.cast::<SharedString<T>>().as_ptr())) };
179 Some(Arc::strong_count(&arc))
180}
181
182#[inline]
184fn clone_arc<T: SharedType>(string: &JsString) -> Arc<SharedString<T>> {
185 debug_assert_eq!(string.kind(), T::SHARED_KIND);
186 let ptr = string.ptr.cast::<SharedString<T>>().as_ptr();
187 unsafe {
191 Arc::increment_strong_count(ptr);
192 Arc::from_raw(ptr)
193 }
194}
195
196#[inline]
197fn vtable_ptr<T: SharedType>(arc: Arc<SharedString<T>>) -> NonNull<JsStringVTable> {
198 let raw = Arc::into_raw(arc);
199 unsafe { NonNull::new_unchecked(raw.cast_mut()).cast() }
203}
204
205impl From<&JsString> for SharedJsStr {
206 #[inline]
207 fn from(value: &JsString) -> Self {
208 match value.kind() {
209 JsStringKind::Latin1Shared => Self {
210 inner: SharedInner::Latin1(clone_arc::<Latin1>(value)),
211 },
212 JsStringKind::Utf16Shared => Self {
213 inner: SharedInner::Utf16(clone_arc::<Utf16>(value)),
214 },
215 _ => match value.as_str().variant() {
216 JsStrVariant::Latin1(bytes) => Self::from_latin1(bytes),
217 JsStrVariant::Utf16(units) => Self::from_utf16(units),
218 },
219 }
220 }
221}
222
223impl From<&str> for SharedJsStr {
224 #[inline]
225 fn from(s: &str) -> Self {
226 if s.is_ascii() {
229 return Self::from_latin1(s.as_bytes());
230 }
231 if s.chars().all(|c| c as u32 <= 0xFF) {
232 let bytes: Vec<u8> = s.chars().map(|c| c as u8).collect();
233 return Self::from_latin1(&bytes);
234 }
235 let units = s.encode_utf16().collect::<Vec<_>>();
236 Self::from_utf16(&units)
237 }
238}
239
240impl From<&[u16]> for SharedJsStr {
241 #[inline]
242 fn from(units: &[u16]) -> Self {
243 Self::from_utf16(units)
244 }
245}
246
247impl<const N: usize> From<&[u16; N]> for SharedJsStr {
248 #[inline]
249 fn from(units: &[u16; N]) -> Self {
250 Self::from(&units[..])
251 }
252}
253
254impl PartialEq for SharedJsStr {
255 #[inline]
256 fn eq(&self, other: &Self) -> bool {
257 self.as_str() == other.as_str()
258 }
259}
260
261impl Eq for SharedJsStr {}
262
263impl PartialOrd for SharedJsStr {
264 #[inline]
265 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
266 Some(self.cmp(other))
267 }
268}
269
270impl Ord for SharedJsStr {
271 #[inline]
272 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
273 self.as_str().cmp(&other.as_str())
274 }
275}
276
277impl Hash for SharedJsStr {
278 #[inline]
279 fn hash<H: Hasher>(&self, state: &mut H) {
280 self.as_str().hash(state);
281 }
282}
283
284impl PartialEq<JsString> for SharedJsStr {
285 #[inline]
286 fn eq(&self, other: &JsString) -> bool {
287 self.as_str() == other.as_str()
288 }
289}
290
291impl PartialEq<SharedJsStr> for JsString {
292 #[inline]
293 fn eq(&self, other: &SharedJsStr) -> bool {
294 self.as_str() == other.as_str()
295 }
296}
297
298impl PartialEq<str> for SharedJsStr {
299 #[inline]
300 fn eq(&self, other: &str) -> bool {
301 self.as_str() == other
302 }
303}
304
305impl PartialEq<&str> for SharedJsStr {
306 #[inline]
307 fn eq(&self, other: &&str) -> bool {
308 self.as_str() == *other
309 }
310}
311
312impl PartialEq<SharedJsStr> for str {
313 #[inline]
314 fn eq(&self, other: &SharedJsStr) -> bool {
315 other == self
316 }
317}
318
319impl std::fmt::Debug for SharedJsStr {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 let units = self.as_str();
322 let content = units.display_lossy().to_string();
323 f.debug_struct("SharedJsStr")
324 .field("len", &units.len())
325 .field("latin1", &units.is_latin1())
326 .field("content", &content)
327 .finish_non_exhaustive()
328 }
329}