// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use serde::Deserializer; use serde::Serializer; use std::borrow::Borrow; use std::ffi::OsStr; use std::fmt::Debug; use std::fmt::Display; use std::fmt::Formatter; use std::hash::Hash; use std::ops::Deref; use std::sync::Arc; use url::Url; use v8::NewStringType; static EMPTY_STRING: v8::OneByteConst = v8::String::create_external_onebyte_const("".as_bytes()); /// A static string that is compile-time checked to be ASCII and is stored in the /// most efficient possible way to create V8 strings. #[derive(Clone, Copy)] #[repr(transparent)] pub struct FastStaticString { s: &'static v8::OneByteConst, } impl FastStaticString { pub const fn new(s: &'static v8::OneByteConst) -> Self { FastStaticString { s } } pub fn as_str(&self) -> &'static str { self.s.as_ref() } pub fn as_bytes(&self) -> &'static [u8] { self.s.as_ref() } #[doc(hidden)] pub const fn create_external_onebyte_const( s: &'static [u8], ) -> v8::OneByteConst { v8::String::create_external_onebyte_const(s) } pub fn v8_string<'s>( &self, scope: &mut v8::HandleScope<'s>, ) -> v8::Local<'s, v8::String> { FastString::from(*self).v8_string(scope) } pub const fn into_v8_const_ptr(&self) -> *const v8::OneByteConst { self.s as _ } } impl From<&'static v8::OneByteConst> for FastStaticString { fn from(s: &'static v8::OneByteConst) -> Self { Self::new(s) } } impl From for *const v8::OneByteConst { fn from(val: FastStaticString) -> Self { val.into_v8_const_ptr() } } impl Hash for FastStaticString { fn hash(&self, state: &mut H) { self.as_str().hash(state) } } impl AsRef for FastStaticString { fn as_ref(&self) -> &str { self.as_str() } } impl Deref for FastStaticString { type Target = str; fn deref(&self) -> &Self::Target { self.as_str() } } impl Borrow for FastStaticString { fn borrow(&self) -> &str { self.as_str() } } impl Debug for FastStaticString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(self.as_str(), f) } } impl Default for FastStaticString { fn default() -> Self { FastStaticString { s: &EMPTY_STRING } } } impl PartialEq for FastStaticString { fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() } } impl Eq for FastStaticString {} impl Display for FastStaticString { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } /// Module names and code can be sourced from strings or bytes that are either owned or borrowed. This enumeration allows us /// to perform a minimal amount of cloning and format-shifting of the underlying data. /// /// Note that any [`FastString`] created using [`ascii_str!`] must contain only ASCII characters. Other [`FastString`] types /// may be UTF-8, though this will incur a small performance penalty. It is recommended that large, static strings always /// use [`ascii_str!`]. /// /// Examples of ways to construct a [`FastString`]: /// /// ```rust /// # use deno_core::{ascii_str, FastString}; /// /// let code: FastString = ascii_str!("a string").into(); /// let code: FastString = format!("a string").into(); /// ``` pub struct FastString { inner: FastStringInner, } enum FastStringInner { /// Created from static data. Static(&'static str), /// Created from static ascii, known to contain only ASCII chars. StaticAscii(&'static str), /// Created from static data, known to contain only ASCII chars. StaticConst(FastStaticString), /// An owned chunk of data. Note that we use `Box` rather than `Vec` to avoid the /// storage overhead. Owned(Box), // Scripts loaded from the `deno_graph` infrastructure. Arc(Arc), } impl FastString { /// Create a [`FastString`] from a static string. The string may contain non-ASCII characters, and if /// so, will take the slower path when used in v8. pub const fn from_static(s: &'static str) -> Self { if s.is_ascii() { Self { inner: FastStringInner::StaticAscii(s), } } else { Self { inner: FastStringInner::Static(s), } } } /// Returns a static string from this `FastString`, if available. pub fn as_static_str(&self) -> Option<&'static str> { match self.inner { FastStringInner::Static(s) => Some(s), FastStringInner::StaticAscii(s) => Some(s), FastStringInner::StaticConst(s) => Some(s.as_str()), _ => None, } } /// Creates a cheap copy of this [`FastString`], potentially transmuting it to a faster form. Note that this /// is not a clone operation as it consumes the old [`FastString`]. pub fn into_cheap_copy(self) -> (Self, Self) { match self.inner { FastStringInner::Owned(s) => { let s: Arc = s.into(); ( Self { inner: FastStringInner::Arc(s.clone()), }, Self { inner: FastStringInner::Arc(s), }, ) } _ => (self.try_clone().unwrap(), self), } } /// If this [`FastString`] is cheaply cloneable, returns a clone. pub fn try_clone(&self) -> Option { match &self.inner { FastStringInner::Static(s) => Some(Self { inner: FastStringInner::Static(s), }), FastStringInner::StaticAscii(s) => Some(Self { inner: FastStringInner::StaticAscii(s), }), FastStringInner::StaticConst(s) => Some(Self { inner: FastStringInner::StaticConst(*s), }), FastStringInner::Arc(s) => Some(Self { inner: FastStringInner::Arc(s.clone()), }), FastStringInner::Owned(_s) => None, } } #[inline(always)] pub fn as_bytes(&self) -> &[u8] { self.as_str().as_bytes() } #[inline(always)] pub fn as_str(&self) -> &str { match &self.inner { // TODO(mmastrac): When we get a const deref, as_str can be const FastStringInner::Arc(s) => s, FastStringInner::Owned(s) => s, FastStringInner::Static(s) => s, FastStringInner::StaticAscii(s) => s, FastStringInner::StaticConst(s) => s.as_str(), } } /// Create a v8 string from this [`FastString`]. If the string is static and contains only ASCII characters, /// an external one-byte static is created. pub fn v8_string<'a>( &self, scope: &mut v8::HandleScope<'a>, ) -> v8::Local<'a, v8::String> { match self.inner { FastStringInner::StaticAscii(s) => { v8::String::new_external_onebyte_static(scope, s.as_bytes()).unwrap() } FastStringInner::StaticConst(s) => { v8::String::new_from_onebyte_const(scope, s.s).unwrap() } _ => { v8::String::new_from_utf8(scope, self.as_bytes(), NewStringType::Normal) .unwrap() } } } /// Truncates a [`FastString`] value, possibly re-allocating or memcpy'ing. May be slow. pub fn truncate(&mut self, index: usize) { match &mut self.inner { FastStringInner::Static(b) => { self.inner = FastStringInner::Static(&b[..index]) } FastStringInner::StaticAscii(b) => { self.inner = FastStringInner::StaticAscii(&b[..index]) } FastStringInner::StaticConst(b) => { self.inner = FastStringInner::StaticAscii(&b.as_str()[..index]) } // TODO(mmastrac): this could be more efficient FastStringInner::Owned(b) => { self.inner = FastStringInner::Owned(b[..index].to_owned().into()) } // We can't do much if we have an Arc, so we'll just take ownership of the truncated version FastStringInner::Arc(s) => { self.inner = FastStringInner::Arc(s[..index].to_owned().into()) } } } } impl Hash for FastString { fn hash(&self, state: &mut H) { self.as_str().hash(state) } } impl AsRef for FastString { fn as_ref(&self) -> &str { self.as_str() } } impl AsRef<[u8]> for FastString { fn as_ref(&self) -> &[u8] { self.as_str().as_ref() } } impl AsRef for FastString { fn as_ref(&self) -> &OsStr { self.as_str().as_ref() } } impl Deref for FastString { type Target = str; fn deref(&self) -> &Self::Target { self.as_str() } } impl Borrow for FastString { fn borrow(&self) -> &str { self.as_str() } } impl Debug for FastString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(self.as_str(), f) } } impl Default for FastString { fn default() -> Self { Self { inner: FastStringInner::StaticConst(FastStaticString::default()), } } } impl PartialEq for FastString { fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() } } impl Eq for FastString {} impl Display for FastString { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } /// [`FastString`] can be made cheaply from [`Url`] as we know it's owned and don't need to do an /// ASCII check. impl From for FastString { fn from(value: FastStaticString) -> Self { Self { inner: FastStringInner::StaticConst(value), } } } /// [`FastString`] can be made cheaply from [`Url`] as we know it's owned and don't need to do an /// ASCII check. impl From for FastString { fn from(value: Url) -> Self { let s: String = value.into(); s.into() } } /// [`FastString`] can be made cheaply from [`String`] as we know it's owned and don't need to do an /// ASCII check. impl From for FastString { fn from(value: String) -> Self { Self { inner: FastStringInner::Owned(value.into_boxed_str()), } } } /// [`FastString`] can be made cheaply from [`Arc`] as we know it's shared and don't need to do an /// ASCII check. impl From> for FastString { fn from(value: Arc) -> Self { Self { inner: FastStringInner::Arc(value), } } } impl From for Arc { fn from(value: FastString) -> Self { use FastStringInner::*; match value.inner { Static(text) | StaticAscii(text) => text.into(), StaticConst(text) => text.as_ref().into(), Owned(text) => text.into(), Arc(text) => text, } } } impl serde::Serialize for FastString { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(self.as_str()) } } type DeserializeProxy<'de> = &'de str; impl<'de> serde::Deserialize<'de> for FastString { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { DeserializeProxy::<'de>::deserialize(deserializer) .map(|v| v.to_owned().into()) } } /// Include a fast string in the binary. This string is asserted at compile-time to be 7-bit ASCII for optimal /// v8 performance. /// /// This macro creates a [`FastStaticString`] that may be converted to a [`FastString`] via [`Into::into`]. #[macro_export] macro_rules! ascii_str_include { ($file:expr) => {{ const STR: $crate::v8::OneByteConst = $crate::FastStaticString::create_external_onebyte_const( ::std::include_str!($file).as_bytes(), ); let s: &'static $crate::v8::OneByteConst = &STR; $crate::FastStaticString::new(s) }}; } /// Include a fast string in the binary from a string literal. This string is asserted at compile-time to be /// 7-bit ASCII for optimal v8 performance. /// /// This macro creates a [`FastStaticString`] that may be converted to a [`FastString`] via [`Into::into`]. #[macro_export] macro_rules! ascii_str { ($str:expr) => {{ const C: $crate::v8::OneByteConst = $crate::FastStaticString::create_external_onebyte_const($str.as_bytes()); unsafe { std::mem::transmute::<_, $crate::FastStaticString>(&C) } }}; } /// Used to generate the fast, const versions of op names. Internal only. #[macro_export] #[doc(hidden)] macro_rules! __op_name_fast { ($op:ident) => {{ const LITERAL: &'static [u8] = stringify!($op).as_bytes(); const STR: $crate::v8::OneByteConst = $crate::FastStaticString::create_external_onebyte_const(LITERAL); let s: &'static $crate::v8::OneByteConst = &STR; (stringify!($op), $crate::FastStaticString::new(s)) }}; } #[cfg(test)] mod tests { use super::*; #[test] fn string_eq() { let s: FastString = ascii_str!("Testing").into(); assert_eq!("Testing", s.as_str()); let s2 = FastString::from_static("Testing"); assert_eq!(s, s2); let (s1, s2) = s.into_cheap_copy(); assert_eq!("Testing", s1.as_str()); assert_eq!("Testing", s2.as_str()); let s = FastString::from("Testing".to_owned()); assert_eq!("Testing", s.as_str()); let (s1, s2) = s.into_cheap_copy(); assert_eq!("Testing", s1.as_str()); assert_eq!("Testing", s2.as_str()); } #[test] fn truncate() { let mut s = "123456".to_owned(); s.truncate(3); let mut code: FastString = ascii_str!("123456").into(); code.truncate(3); assert_eq!(s, code.as_ref()); let mut code: FastString = "123456".to_owned().into(); code.truncate(3); assert_eq!(s, code.as_ref()); let arc_str: Arc = "123456".into(); let mut code: FastString = arc_str.into(); code.truncate(3); assert_eq!(s, code.as_ref()); } #[test] fn test_large_include() { // This test would require an excessively large file in the repo, so we just run this manually // ascii_str_include!("runtime/tests/large_string.txt"); // ascii_str_include!(concat!("runtime", "/tests/", "large_string.txt")); } /// Ensure that all of our macros compile properly in a static context. #[test] fn test_const() { const _: (&str, FastStaticString) = __op_name_fast!(op_name); const _: FastStaticString = ascii_str!("hmm"); const _: FastStaticString = ascii_str!(concat!("hmm", "hmmmmm")); const _: FastStaticString = ascii_str_include!("Cargo.toml"); const _: FastStaticString = ascii_str_include!(concat!("./", "Cargo.toml")); } }