Repository: denoland/deno_core
Pull request #115 (2023-08-02 22:43:47)

feat(ops): Very fast #[string(onebyte)] Cow<[u8]>
If strings are known to be one-byte (for example, HTTP headers), this performs extremely fast.

 - Very short strings: 75% faster -- almost as fast as a u32!
 - ~1000-byte strings: Approx the same
 - ~1000000-byte strings: 50% faster
 - Compared to serde_v8's ByteString, this is 20x faster (though it is only available in "slow" ops)

This is also supported in the return position, but only for slow ops.

```
test baseline                               ... bench:         691 ns/iter (+/- 26)
test bench_op_option_u32                    ... bench:       8,590 ns/iter (+/- 267)
test bench_op_string                        ... bench:      11,357 ns/iter (+/- 330)
test bench_op_string_bytestring             ... bench:      60,128 ns/iter (+/- 1,572)
test bench_op_string_large_1000             ... bench:     108,491 ns/iter (+/- 2,926)
test bench_op_string_large_1000000          ... bench:     790,843 ns/iter (+/- 28,126)
test bench_op_string_large_utf8_1000        ... bench:   2,116,087 ns/iter (+/- 50,677)
test bench_op_string_large_utf8_1000000     ... bench:  22,403,662 ns/iter (+/- 586,581)
test bench_op_string_old                    ... bench:      12,412 ns/iter (+/- 940)
test bench_op_string_old_large_1000         ... bench:     128,759 ns/iter (+/- 1,444)
test bench_op_string_old_large_1000000      ... bench:     471,671 ns/iter (+/- 70,252)
test bench_op_string_old_large_utf8_1000    ... bench:   2,923,649 ns/iter (+/- 78,738)
test bench_op_string_old_large_utf8_1000000 ... bench:  29,619,216 ns/iter (+/- 548,542)
test bench_op_string_onebyte                ... bench:       3,826 ns/iter (+/- 114)
test bench_op_string_onebyte_large_1000     ... bench:     107,577 ns/iter (+/- 3,695)
test bench_op_string_onebyte_large_1000000  ... bench:     393,868 ns/iter (+/- 41,078)
test bench_op_string_option_u32             ... bench:      38,199 ns/iter (+/- 1,222)
test bench_op_u32                           ... bench:       2,835 ns/iter (+/- 52)
test bench_op_v8_global                     ... bench:      29,435 ns/iter (+/- 1,718)
test bench_op_v8_global_scope               ... bench:      39,866 ns/iter (+/- 957)
test bench_op_v8_local                      ... bench:       4,413 ns/iter (+/- 96)
test bench_op_v8_local_nofast               ... bench:       8,429 ns/iter (+/- 225)
test bench_op_v8_local_scope                ... bench:      17,208 ns/iter (+/- 452)
test bench_op_void                          ... bench:       2,788 ns/iter (+/- 89)
```


Patch:
diff --git a/core/benches/ops/sync.rs b/core/benches/ops/sync.rs
index 24708a7..4904726 100644
--- a/core/benches/ops/sync.rs
+++ b/core/benches/ops/sync.rs
@@ -1,3 +1,5 @@
+use std::borrow::Cow;
+
 use bencher::*;
 use deno_core::error::generic_error;
 use deno_core::*;
@@ -9,6 +11,8 @@ deno_core::extension!(
     op_u32,
     op_option_u32,
     op_string,
+    op_string_onebyte,
+    op_string_bytestring,
     op_string_old,
     op_string_option_u32,
     op_local,
@@ -41,6 +45,16 @@ pub fn op_string(#[string] s: &str) -> u32 {
   s.len() as _
 }
 
+#[op2(fast)]
+pub fn op_string_onebyte(#[string(onebyte)] s: Cow<[u8]>) -> u32 {
+  s.len() as _
+}
+
+#[op2]
+pub fn op_string_bytestring(#[serde] s: ByteString) -> u32 {
+  s.len() as _
+}
+
 #[op(fast)]
 pub fn op_string_old(s: &str) -> u32 {
   s.len() as _
@@ -147,7 +161,7 @@ bench();
 }
 
 const BENCH_COUNT: usize = 1000;
-const LARGE_BENCH_COUNT: usize = 10;
+const LARGE_BENCH_COUNT: usize = 5;
 
 /// Tests the overhead of execute_script.
 fn baseline(b: &mut Bencher) {
@@ -202,6 +216,38 @@ fn bench_op_string_large_1000000(b: &mut Bencher) {
   );
 }
 
+/// A string function with a numeric return value.
+fn bench_op_string_onebyte(b: &mut Bencher) {
+  bench_op(b, BENCH_COUNT, "op_string_onebyte", 1, "accum += op_string_onebyte('this is a reasonably long string that we would like to get the length of!');");
+}
+
+/// A string function with a numeric return value.
+fn bench_op_string_onebyte_large_1000(b: &mut Bencher) {
+  bench_op(
+    b,
+    BENCH_COUNT,
+    "op_string_onebyte",
+    1,
+    "accum += op_string_onebyte(LARGE_STRING_1000);",
+  );
+}
+
+/// A string function with a numeric return value.
+fn bench_op_string_onebyte_large_1000000(b: &mut Bencher) {
+  bench_op(
+    b,
+    LARGE_BENCH_COUNT,
+    "op_string_onebyte",
+    1,
+    "accum += op_string_onebyte(LARGE_STRING_1000000);",
+  );
+}
+
+/// A string function with a numeric return value.
+fn bench_op_string_bytestring(b: &mut Bencher) {
+  bench_op(b, BENCH_COUNT, "op_string_bytestring", 1, "accum += op_string_bytestring('this is a reasonably long string that we would like to get the length of!');");
+}
+
 /// A string function with a numeric return value.
 fn bench_op_string_large_utf8_1000(b: &mut Bencher) {
   bench_op(
@@ -309,9 +355,13 @@ benchmark_group!(
   bench_op_void,
   bench_op_u32,
   bench_op_option_u32,
+  bench_op_string_bytestring,
   bench_op_string,
   bench_op_string_large_1000,
   bench_op_string_large_1000000,
+  bench_op_string_onebyte,
+  bench_op_string_onebyte_large_1000,
+  bench_op_string_onebyte_large_1000000,
   bench_op_string_large_utf8_1000,
   bench_op_string_large_utf8_1000000,
   bench_op_string_old,
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 04b448d..a245cfb 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -20,6 +20,7 @@ use std::mem::MaybeUninit;
 use std::option::Option;
 use std::task::Context;
 use std::task::Poll;
+use v8::WriteOptions;
 
 /// The default string buffer size on the stack that prevents mallocs in some
 /// string functions. Keep in mind that Windows only offers 1MB stacks by default,
@@ -416,6 +417,12 @@ pub fn to_string_ptr(string: &v8::fast_api::FastApiOneByteString) -> String {
   }
 }
 
+pub fn to_cow_byte_ptr(
+  string: &v8::fast_api::FastApiOneByteString,
+) -> Cow<[u8]> {
+  string.as_bytes().into()
+}
+
 /// Converts a [`v8::Value`] to an owned string.
 #[inline(always)]
 pub fn to_string(scope: &mut v8::Isolate, string: &v8::Value) -> String {
@@ -445,6 +452,48 @@ pub fn to_str<'a, const N: usize>(
   string.to_rust_cow_lossy(scope, buffer)
 }
 
+#[inline(always)]
+pub fn to_cow_one_byte(
+  scope: &mut v8::Isolate,
+  string: &v8::Value,
+) -> Result<Cow<'static, [u8]>, &'static str> {
+  if !string.is_string() {
+    return Err("expected String");
+  }
+
+  // SAFETY: We checked is_string above
+  let string: &v8::String = unsafe { std::mem::transmute(string) };
+
+  let capacity = string.length();
+  if capacity == 0 {
+    return Ok(Cow::Borrowed(&[]));
+  }
+
+  if !string.is_onebyte() && !string.contains_only_onebyte() {
+    return Err("expected one-byte String");
+  }
+
+  // Create an uninitialized buffer of `capacity` bytes. We need to be careful here to avoid
+  // accidentally creating a slice of u8 which would be invalid.
+  unsafe {
+    let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
+    let out = std::alloc::alloc(layout);
+
+    // Write the buffer to a slice made from this uninitialized data
+    {
+      let buffer = std::slice::from_raw_parts_mut(out as _, capacity);
+      string.write_one_byte_uninit(
+        scope,
+        buffer,
+        0,
+        WriteOptions::NO_NULL_TERMINATION,
+      );
+    }
+
+    Ok(Vec::from_raw_parts(out, capacity, capacity).into())
+  }
+}
+
 /// Converts from a raw [`v8::Value`] to the expected V8 data type.
 #[inline(always)]
 #[allow(clippy::result_unit_err)]
@@ -653,9 +702,11 @@ mod tests {
       op_test_string_ref,
       op_test_string_cow,
       op_test_string_roundtrip_char,
+      op_test_string_roundtrip_char_onebyte,
       op_test_string_return,
       op_test_string_option_return,
       op_test_string_roundtrip,
+      op_test_string_roundtrip_onebyte,
       op_test_generics<String>,
       op_test_v8_types,
       op_test_v8_option_string,
@@ -1090,6 +1141,13 @@ mod tests {
     s.chars().next().unwrap() as u32
   }
 
+  #[op2(core, fast)]
+  pub fn op_test_string_roundtrip_char_onebyte(
+    #[string(onebyte)] s: Cow<[u8]>,
+  ) -> u32 {
+    s[0] as u32
+  }
+
   #[tokio::test]
   pub async fn test_op_strings() -> Result<(), Box<dyn std::error::Error>> {
     for op in [
@@ -1141,6 +1199,23 @@ mod tests {
       "op_test_string_roundtrip_char",
       "assert(op_test_string_roundtrip_char('\\u0100') == 0x100)",
     )?;
+
+    run_test2(
+      10000,
+      "op_test_string_roundtrip_char_onebyte",
+      "assert(op_test_string_roundtrip_char_onebyte('\\u00ff') == 0xff)",
+    )?;
+    run_test2(
+      10000,
+      "op_test_string_roundtrip_char_onebyte",
+      "assert(op_test_string_roundtrip_char_onebyte('\\u007f') == 0x7f)",
+    )?;
+    run_test2(
+      10,
+      "op_test_string_roundtrip_char_onebyte",
+      "try { op_test_string_roundtrip_char_onebyte('\\u1000'); assert(false); } catch (e) {}"
+    )?;
+
     Ok(())
   }
 
@@ -1171,6 +1246,14 @@ mod tests {
     s
   }
 
+  #[op2(core)]
+  #[string(onebyte)]
+  pub fn op_test_string_roundtrip_onebyte(
+    #[string(onebyte)] s: Cow<[u8]>,
+  ) -> Cow<[u8]> {
+    s
+  }
+
   #[tokio::test]
   pub async fn test_op_string_returns() -> Result<(), Box<dyn std::error::Error>>
   {
@@ -1194,6 +1277,11 @@ mod tests {
       "op_test_string_roundtrip",
       "assert(op_test_string_roundtrip('\\u0080\\u00a0\\u00ff') == '\\u0080\\u00a0\\u00ff')",
     )?;
+    run_test2(
+      1,
+      "op_test_string_roundtrip_onebyte",
+      "assert(op_test_string_roundtrip_onebyte('\\u0080\\u00a0\\u00ff') == '\\u0080\\u00a0\\u00ff')",
+    )?;
     Ok(())
   }
 
diff --git a/ops/op2/README.md b/ops/op2/README.md
index bf6c5a3..a0a57ae 100644
--- a/ops/op2/README.md
+++ b/ops/op2/README.md
@@ -284,6 +284,20 @@ Fastcall available only if string is Latin-1. Will create a `Cow::Owned` copy of
 <tr>
 <td>
 
+```text
+#[string(onebyte)] Cow<[u8]>
+```
+
+</td><td>
+✅
+</td><td>
+String
+</td><td>
+Fastest `String`-type method. If the string is not Latin-1, will throw a TypeError.
+</td></tr>
+<tr>
+<td>
+
 ```text
 &v8::Value
 ```
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index fa895d8..df756d9 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -10,6 +10,7 @@ use super::signature::ParsedSignature;
 use super::signature::RefType;
 use super::signature::RetVal;
 use super::signature::Special;
+use super::signature::Strings;
 use super::V8MappingError;
 use proc_macro2::Ident;
 use proc_macro2::TokenStream;
@@ -362,21 +363,24 @@ fn map_v8_fastcall_arg_to_arg(
         let #arg_ident = #arg_ident.try_borrow_mut::<#state>();
       }
     }
-    Arg::Special(Special::RefStr) => {
+    Arg::String(Strings::RefStr) => {
       quote! {
         let mut #arg_temp: [::std::mem::MaybeUninit<u8>; #deno_core::_ops::STRING_STACK_BUFFER_SIZE] = [::std::mem::MaybeUninit::uninit(); #deno_core::_ops::STRING_STACK_BUFFER_SIZE];
         let #arg_ident = &#deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
       }
     }
-    Arg::Special(Special::String) => {
+    Arg::String(Strings::String) => {
       quote!(let #arg_ident = #deno_core::_ops::to_string_ptr(unsafe { &mut *#arg_ident });)
     }
-    Arg::Special(Special::CowStr) => {
+    Arg::String(Strings::CowStr) => {
       quote! {
         let mut #arg_temp: [::std::mem::MaybeUninit<u8>; #deno_core::_ops::STRING_STACK_BUFFER_SIZE] = [::std::mem::MaybeUninit::uninit(); #deno_core::_ops::STRING_STACK_BUFFER_SIZE];
         let #arg_ident = #deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
       }
     }
+    Arg::String(Strings::CowByte) => {
+      quote!(let #arg_ident = #deno_core::_ops::to_cow_byte_ptr(unsafe { &mut *#arg_ident });)
+    }
     Arg::V8Local(v8)
     | Arg::OptionV8Local(v8)
     | Arg::V8Ref(_, v8)
@@ -423,12 +427,14 @@ fn map_arg_to_v8_fastcall_type(
     | Arg::State(..)
     | Arg::OptionState(..) => V8FastCallType::Virtual,
     // Other types + ref types are not handled
-    Arg::OptionNumeric(_) | Arg::Option(_) | Arg::SerdeV8(_) | Arg::Ref(..) => {
-      return Ok(None)
-    }
+    Arg::OptionNumeric(_)
+    | Arg::Option(_)
+    | Arg::OptionString(_)
+    | Arg::SerdeV8(_)
+    | Arg::Ref(..) => return Ok(None),
     // We don't support v8 global arguments
     Arg::V8Global(_) => return Ok(None),
-    // We don't support v8 type arguments
+    // We do support v8 type arguments (including Option<...>)
     Arg::V8Ref(RefType::Ref, _)
     | Arg::V8Local(_)
     | Arg::OptionV8Local(_)
@@ -452,11 +458,13 @@ fn map_arg_to_v8_fastcall_type(
     Arg::Numeric(NumericArg::f64) => V8FastCallType::F64,
     // Ref strings that are one byte internally may be passed as a SeqOneByteString,
     // which gives us a FastApiOneByteString.
-    Arg::Special(Special::RefStr) => V8FastCallType::SeqOneByteString,
+    Arg::String(Strings::RefStr) => V8FastCallType::SeqOneByteString,
     // Owned strings can be fast, but we'll have to copy them.
-    Arg::Special(Special::String) => V8FastCallType::SeqOneByteString,
+    Arg::String(Strings::String) => V8FastCallType::SeqOneByteString,
     // Cow strings can be fast, but may require copying
-    Arg::Special(Special::CowStr) => V8FastCallType::SeqOneByteString,
+    Arg::String(Strings::CowStr) => V8FastCallType::SeqOneByteString,
+    // Cow byte strings can be fast and don't require copying
+    Arg::String(Strings::CowByte) => V8FastCallType::SeqOneByteString,
     _ => return Err(V8MappingError::NoMapping("a fast argument", arg.clone())),
   };
   Ok(Some(rv))
@@ -490,7 +498,9 @@ fn map_retval_to_v8_fastcall_type(
     Arg::Numeric(NumericArg::f64) => V8FastCallType::F64,
     // We don't return special return types
     Arg::Option(_) => return Ok(None),
+    Arg::OptionString(_) => return Ok(None),
     Arg::Special(_) => return Ok(None),
+    Arg::String(_) => return Ok(None),
     // We don't support returning v8 types
     Arg::V8Ref(..)
     | Arg::V8Global(_)
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index 83367e4..f7e16aa 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -11,6 +11,7 @@ use super::signature::ParsedSignature;
 use super::signature::RefType;
 use super::signature::RetVal;
 use super::signature::Special;
+use super::signature::Strings;
 use super::V8MappingError;
 use crate::op2::generator_state::gs_extract;
 use crate::op2::generator_state::gs_quote;
@@ -242,7 +243,7 @@ pub fn from_arg(
         };
       }
     }
-    Arg::Option(Special::String) => {
+    Arg::OptionString(Strings::String) => {
       // Only requires isolate, not a full scope
       *needs_isolate = true;
       quote! {
@@ -253,14 +254,14 @@ pub fn from_arg(
         };
       }
     }
-    Arg::Special(Special::String) => {
+    Arg::String(Strings::String) => {
       // Only requires isolate, not a full scope
       *needs_isolate = true;
       quote! {
         let #arg_ident = #deno_core::_ops::to_string(&mut #scope, &#arg_ident);
       }
     }
-    Arg::Special(Special::RefStr) => {
+    Arg::String(Strings::RefStr) => {
       // Only requires isolate, not a full scope
       *needs_isolate = true;
       quote! {
@@ -269,7 +270,7 @@ pub fn from_arg(
         let #arg_ident = &#deno_core::_ops::to_str(&mut #scope, &#arg_ident, &mut #arg_temp);
       }
     }
-    Arg::Special(Special::CowStr) => {
+    Arg::String(Strings::CowStr) => {
       // Only requires isolate, not a full scope
       *needs_isolate = true;
       quote! {
@@ -278,6 +279,21 @@ pub fn from_arg(
         let #arg_ident = #deno_core::_ops::to_str(&mut #scope, &#arg_ident, &mut #arg_temp);
       }
     }
+    Arg::String(Strings::CowByte) => {
+      // Only requires isolate, not a full scope
+      *needs_isolate = true;
+      let throw_exception =
+        throw_type_error_static_string(generator_state, &arg_ident)?;
+      gs_quote!(generator_state(deno_core, scope) => {
+        // Trade stack space for potentially non-allocating strings
+        let #arg_ident = match #deno_core::_ops::to_cow_one_byte(&mut #scope, &#arg_ident) {
+          Ok(#arg_ident) => #arg_ident,
+          Err(#arg_ident) => {
+            #throw_exception
+          }
+        };
+      })
+    }
     Arg::Buffer(buffer) => {
       from_arg_buffer(generator_state, &arg_ident, buffer)?
     }
@@ -539,7 +555,7 @@ pub fn return_value_infallible(
     Arg::Numeric(NumericArg::f32 | NumericArg::f64) => {
       quote!(#retval.set_double(#result as _);)
     }
-    Arg::Special(Special::String) => {
+    Arg::String(Strings::String) => {
       *needs_scope = true;
       quote! {
         if #result.is_empty() {
@@ -553,38 +569,14 @@ pub fn return_value_infallible(
         }
       }
     }
-    Arg::OptionNumeric(n) => {
-      *needs_scope = true;
-      let some = return_value_infallible(generator_state, &Arg::Numeric(*n))?;
-      gs_quote!(generator_state(result, retval) => {
-        if let Some(#result) = #result {
-          #some
-        } else {
-          #retval.set_null();
-        }
-      })
-    }
-    Arg::Option(Special::String) => {
+    Arg::String(Strings::CowByte) => {
       *needs_scope = true;
-      let some = return_value_infallible(
-        generator_state,
-        &Arg::Special(Special::String),
-      )?;
-      gs_quote!(generator_state(result, retval) => {
-        if let Some(#result) = #result {
-          #some
-        } else {
-          #retval.set_null();
-        }
-      })
-    }
-    Arg::OptionV8Local(_) => {
       quote! {
-        if let Some(#result) = #result {
-          // We may have a non v8::Value here
-          #retval.set(#result.into())
+        if #result.is_empty() {
+          #retval.set_empty_string();
         } else {
-          #retval.set_null();
+          let temp = #deno_core::v8::String::new_from_one_byte(&mut #scope, &#result, #deno_core::v8::NewStringType::Normal).unwrap();
+          #retval.set(temp.into());
         }
       }
     }
@@ -621,6 +613,20 @@ pub fn return_value_infallible(
     ) => {
       quote! { #retval.set(#deno_core::_ops::ToV8Value::to_v8_value(#result, &mut #scope)); }
     }
+    arg if arg.is_option() => {
+      // We support all optional types by generating the infallible version in a branch
+      let some = return_value_infallible(
+        generator_state,
+        &ret_type.some_type().unwrap(),
+      )?;
+      gs_quote!(generator_state(result, retval) => {
+        if let Some(#result) = #result {
+          #some
+        } else {
+          #retval.set_null();
+        }
+      })
+    }
     _ => {
       return Err(V8MappingError::NoMapping(
         "a slow return value",
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
index 26a0bff..39d4a9b 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -139,10 +139,15 @@ impl ToTokens for V8Arg {
 pub enum Special {
   HandleScope,
   OpState,
+  FastApiCallbackOptions,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum Strings {
   String,
   CowStr,
   RefStr,
-  FastApiCallbackOptions,
+  CowByte,
 }
 
 /// Buffers are complicated and may be shared/owned, shared/unowned, a copy, or detached.
@@ -207,11 +212,13 @@ pub enum RefType {
 pub enum Arg {
   Void,
   Special(Special),
+  String(Strings),
   Buffer(Buffer),
   External(External),
   Ref(RefType, Special),
   RcRefCell(Special),
   Option(Special),
+  OptionString(Strings),
   OptionNumeric(NumericArg),
   OptionV8Local(V8Arg),
   OptionV8Global(V8Arg),
@@ -229,7 +236,7 @@ impl Arg {
   /// Is this argument virtual? ie: does it come from the æther rather than a concrete JavaScript input
   /// argument?
   #[allow(clippy::match_like_matches_macro)]
-  pub fn is_virtual(&self) -> bool {
+  pub const fn is_virtual(&self) -> bool {
     match self {
       Self::Special(
         Special::FastApiCallbackOptions
@@ -277,7 +284,7 @@ impl Arg {
   }
 
   /// Is this type an [`Option`]?
-  pub fn is_option(&self) -> bool {
+  pub const fn is_option(&self) -> bool {
     matches!(
       self,
       Arg::OptionV8Ref(..)
@@ -285,13 +292,29 @@ impl Arg {
         | Arg::OptionV8Global(..)
         | Arg::OptionNumeric(..)
         | Arg::Option(..)
+        | Arg::OptionString(..)
         | Arg::OptionState(..)
     )
   }
+
+  /// Return the `Some` part of this `Option` type, or `None` if it is not an `Option`.
+  pub fn some_type(&self) -> Option<Arg> {
+    Some(match self {
+      Arg::OptionV8Ref(r, t) => Arg::V8Ref(*r, *t),
+      Arg::OptionV8Local(t) => Arg::V8Local(*t),
+      Arg::OptionV8Global(t) => Arg::V8Global(*t),
+      Arg::OptionNumeric(t) => Arg::Numeric(*t),
+      Arg::Option(t) => Arg::Special(*t),
+      Arg::OptionString(t) => Arg::String(*t),
+      Arg::OptionState(r, t) => Arg::State(*r, t.clone()),
+      _ => return None,
+    })
+  }
 }
 
 pub enum ParsedType {
   TSpecial(Special),
+  TString(Strings),
   TBuffer(Buffer),
   TV8(V8Arg),
   // TODO(mmastrac): We need to carry the mut status through somehow
@@ -315,9 +338,10 @@ impl ParsedType {
         | NumericArg::isize,
       ) => Some(&[AttributeModifier::Bigint]),
       TBuffer(buffer) => Some(buffer.valid_modes(position)),
-      TSpecial(Special::CowStr | Special::RefStr | Special::String) => {
-        Some(&[AttributeModifier::String])
+      TString(Strings::CowByte) => {
+        Some(&[AttributeModifier::String(StringMode::OneByte)])
       }
+      TString(..) => Some(&[AttributeModifier::String(StringMode::Default)]),
       _ => None,
     }
   }
@@ -433,6 +457,14 @@ pub struct ParsedSignature {
   pub generic_bounds: BTreeMap<String, String>,
 }
 
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum StringMode {
+  /// Default mode.
+  Default,
+  /// One-byte strings (aka Latin-1).
+  OneByte,
+}
+
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum BufferMode {
   /// Default mode.
@@ -457,7 +489,7 @@ pub enum AttributeModifier {
   /// see https://medium.com/fhinkel/v8-internals-how-small-is-a-small-integer-e0badc18b6da).
   Smi,
   /// #[string], for strings.
-  String,
+  String(StringMode),
   /// #[state], for automatic OpState extraction.
   State,
   /// #[buffer], for buffers.
@@ -475,7 +507,7 @@ impl AttributeModifier {
       AttributeModifier::Buffer(_) => "buffer",
       AttributeModifier::Smi => "smi",
       AttributeModifier::Serde => "serde",
-      AttributeModifier::String => "string",
+      AttributeModifier::String(_) => "string",
       AttributeModifier::State => "state",
       AttributeModifier::Global => "global",
     }
@@ -575,7 +607,7 @@ pub enum Position {
 impl Attributes {
   pub fn string() -> Self {
     Self {
-      primary: Some(AttributeModifier::String),
+      primary: Some(AttributeModifier::String(StringMode::Default)),
     }
   }
 }
@@ -781,7 +813,8 @@ fn parse_attribute(
       (#[bigint]) => Some(AttributeModifier::Bigint),
       (#[serde]) => Some(AttributeModifier::Serde),
       (#[smi]) => Some(AttributeModifier::Smi),
-      (#[string]) => Some(AttributeModifier::String),
+      (#[string]) => Some(AttributeModifier::String(StringMode::Default)),
+      (#[string(onebyte)]) => Some(AttributeModifier::String(StringMode::OneByte)),
       (#[state]) => Some(AttributeModifier::State),
       (#[buffer]) => Some(AttributeModifier::Buffer(BufferMode::Default)),
       (#[buffer(unsafe)]) => Some(AttributeModifier::Buffer(BufferMode::Unsafe)),
@@ -840,14 +873,17 @@ fn parse_type_path(
     std::panic::catch_unwind(|| {
     rules!(tokens => {
       ( $( std :: str  :: )? String ) => {
-        Ok(CBare(TSpecial(Special::String)))
+        Ok(CBare(TString(Strings::String)))
       }
       // Note that the reference is checked below
       ( $( std :: str :: )? str ) => {
-        Ok(CBare(TSpecial(Special::RefStr)))
+        Ok(CBare(TString(Strings::RefStr)))
       }
       ( $( std :: borrow :: )? Cow < str > ) => {
-        Ok(CBare(TSpecial(Special::CowStr)))
+        Ok(CBare(TString(Strings::CowStr)))
+      }
+      ( $( std :: borrow :: )? Cow < [ u8 ] > ) => {
+        Ok(CBare(TString(Strings::CowByte)))
       }
       ( $( std :: vec ::)? Vec < $ty:path > ) => {
         Ok(CBare(TBuffer(Buffer::Vec(parse_numeric_type(&ty)?))))
@@ -874,6 +910,7 @@ fn parse_type_path(
       ( Option < $ty:ty > ) => {
         match parse_type(position, attrs, &ty)? {
           Arg::Special(special) => Ok(COption(TSpecial(special))),
+          Arg::String(string) => Ok(COption(TString(string))),
           Arg::Numeric(numeric) => Ok(COption(TNumeric(numeric))),
           Arg::Buffer(buffer) => Ok(COption(TBuffer(buffer))),
           Arg::V8Ref(RefType::Ref, v8) => Ok(COption(TV8(v8))),
@@ -893,7 +930,9 @@ fn parse_type_path(
   match res {
     // OpState appears in both ways
     CBare(TSpecial(Special::OpState)) => {}
-    CBare(TSpecial(Special::RefStr | Special::HandleScope) | TV8(_)) => {
+    CBare(
+      TString(Strings::RefStr) | TSpecial(Special::HandleScope) | TV8(_),
+    ) => {
       if !is_ref {
         return Err(ArgError::MissingReference(stringify_token(tp)));
       }
@@ -1005,7 +1044,7 @@ pub(crate) fn parse_type(
       AttributeModifier::State => {
         return parse_type_state(ty);
       }
-      AttributeModifier::String
+      AttributeModifier::String(_)
       | AttributeModifier::Buffer(_)
       | AttributeModifier::Bigint
       | AttributeModifier::Global => {
@@ -1051,9 +1090,9 @@ pub(crate) fn parse_type(
           }
         }
         Type::Path(of) => match parse_type_path(position, attrs, true, of)? {
-          CBare(TSpecial(Special::RefStr)) => Ok(Arg::Special(Special::RefStr)),
-          COption(TSpecial(Special::RefStr)) => {
-            Ok(Arg::Option(Special::RefStr))
+          CBare(TString(Strings::RefStr)) => Ok(Arg::String(Strings::RefStr)),
+          COption(TString(Strings::RefStr)) => {
+            Ok(Arg::OptionString(Strings::RefStr))
           }
           CBare(TV8(v8)) => Ok(Arg::V8Ref(mut_type, v8)),
           CBare(TSpecial(special)) => Ok(Arg::Ref(mut_type, special)),
@@ -1084,9 +1123,11 @@ pub(crate) fn parse_type(
     Type::Path(of) => match parse_type_path(position, attrs, false, of)? {
       CBare(TNumeric(numeric)) => Ok(Arg::Numeric(numeric)),
       CBare(TSpecial(special)) => Ok(Arg::Special(special)),
+      CBare(TString(string)) => Ok(Arg::String(string)),
       CBare(TBuffer(buffer)) => Ok(Arg::Buffer(buffer)),
       COption(TNumeric(special)) => Ok(Arg::OptionNumeric(special)),
       COption(TSpecial(special)) => Ok(Arg::Option(special)),
+      COption(TString(string)) => Ok(Arg::OptionString(string)),
       CRcRefCell(TSpecial(special)) => Ok(Arg::RcRefCell(special)),
       COptionV8Local(TV8(v8)) => Ok(Arg::OptionV8Local(v8)),
       COptionV8Global(TV8(v8)) => Ok(Arg::OptionV8Global(v8)),
@@ -1245,23 +1286,23 @@ mod tests {
   );
   test!(
     fn op_print(#[string] msg: &str, is_err: bool) -> Result<(), Error>;
-    (Special(RefStr), Numeric(bool)) -> Result(Void)
+    (String(RefStr), Numeric(bool)) -> Result(Void)
   );
   test!(
-    #[string] fn op_lots_of_strings(#[string] s: String, #[string] s2: Option<String>, #[string] s3: Cow<str>) -> String;
-    (Special(String), Option(String), Special(CowStr)) -> Infallible(Special(String))
+    #[string] fn op_lots_of_strings(#[string] s: String, #[string] s2: Option<String>, #[string] s3: Cow<str>, #[string(onebyte)] s4: Cow<[u8]>) -> String;
+    (String(String), OptionString(String), String(CowStr), String(CowByte)) -> Infallible(String(String))
   );
   test!(
     #[string] fn op_lots_of_option_strings(#[string] s: Option<String>, #[string] s2: Option<&str>, #[string] s3: Option<Cow<str>>) -> Option<String>;
-    (Option(String), Option(RefStr), Option(CowStr)) -> Infallible(Option(String))
+    (OptionString(String), OptionString(RefStr), OptionString(CowStr)) -> Infallible(OptionString(String))
   );
   test!(
     fn op_scope<'s>(#[string] msg: &'s str);
-    <'s> (Special(RefStr)) -> Infallible(Void)
+    <'s> (String(RefStr)) -> Infallible(Void)
   );
   test!(
     fn op_scope_and_generics<'s, AB, BC>(#[string] msg: &'s str) where AB: some::Trait, BC: OtherTrait;
-    <'s, AB: some::Trait, BC: OtherTrait> (Special(RefStr)) -> Infallible(Void)
+    <'s, AB: some::Trait, BC: OtherTrait> (String(RefStr)) -> Infallible(Void)
   );
   test!(
     fn op_v8_types(s: &mut v8::String, sopt: Option<&mut v8::String>, s2: v8::Local<v8::String>, #[global] s3: v8::Global<v8::String>);
diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md
index 5b291cf..cdc6a85 100644
--- a/ops/op2/valid_args.md
+++ b/ops/op2/valid_args.md
@@ -17,6 +17,7 @@
 | X         | #[string] String                 | X        | String                                              | Fastcall available only if string is Latin-1. Will always create an allocated, UTF-8 copy of the String data.                                                                                                  |
 | X         | #[string] &str                   | X        | String                                              | Fastcall available only if string is Latin-1. Will create an owned `String` copy of the String data if it doesn't fit on the stack. Will never allocate in a fastcall, but will copy Latin-1 -> UTF-8.         |
 | X         | #[string] Cow<str>               | X        | String                                              | Fastcall available only if string is Latin-1. Will create a `Cow::Owned` copy of the String data if it doesn't fit on the stack. Will always be `Cow::Borrowed` in a fastcall, but will copy Latin-1 -> UTF-8. |
+| X         | #[string(onebyte)] Cow<[u8]>     | X        | String                                              | Fastest `String`-type method. If the string is not Latin-1, will throw a TypeError.                                                                                                                            |
 | X         | &v8::Value                       | X        | any                                                 |                                                                                                                                                                                                                |
 | X         | &v8::**V8**                      | X        | **V8**                                              |                                                                                                                                                                                                                |
 | X         | v8::Local<v8::Value>             | X        | any                                                 |                                                                                                                                                                                                                |
diff --git a/ops/op2/valid_retvals.md b/ops/op2/valid_retvals.md
index a851608..f3ecac3 100644
--- a/ops/op2/valid_retvals.md
+++ b/ops/op2/valid_retvals.md
@@ -17,6 +17,7 @@
 | X         | #[string] String                 |          | String |                                                                                                                                                                                                  |
 |           | #[string] &str                   |          | String |                                                                                                                                                                                                  |
 |           | #[string] Cow<str>               |          | String |                                                                                                                                                                                                  |
+|           | #[string(onebyte)] Cow<[u8]>     |          | String |                                                                                                                                                                                                  |
 | X         | v8::Local<v8::Value>             |          | any    |                                                                                                                                                                                                  |
 | X         | v8::Local<v8::**V8**>            |          | **V8** |                                                                                                                                                                                                  |
 |           | #[global] v8::Global<v8::Value>  |          | any    |                                                                                                                                                                                                  |


Test patch:
diff --git a/ops/op2/test_cases/sync/string_onebyte.out b/ops/op2/test_cases/sync/string_onebyte.out
new file mode 100644
index 0000000..1fab1ff
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_onebyte.out
@@ -0,0 +1,80 @@
+#[allow(non_camel_case_types)]
+struct op_string_onebyte {
+    _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_onebyte {
+    const NAME: &'static str = stringify!(op_string_onebyte);
+    const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl::new_internal(
+        stringify!(op_string_onebyte),
+        false,
+        false,
+        false,
+        1usize as u8,
+        Self::v8_fn_ptr as _,
+        Some({
+            use deno_core::v8::fast_api::Type;
+            use deno_core::v8::fast_api::CType;
+            deno_core::v8::fast_api::FastFunction::new_with_bigint(
+                &[Type::V8Value, Type::SeqOneByteString],
+                CType::Uint32,
+                Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+            )
+        }),
+    );
+}
+impl op_string_onebyte {
+    pub const fn name() -> &'static str {
+        stringify!(op_string_onebyte)
+    }
+    #[deprecated(note = "Use the const op::DECL instead")]
+    pub const fn decl() -> deno_core::_ops::OpDecl {
+        <Self as deno_core::_ops::Op>::DECL
+    }
+    fn v8_fn_ptr_fast(
+        _: deno_core::v8::Local<deno_core::v8::Object>,
+        arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
+    ) -> u32 {
+        let result = {
+            let arg0 = deno_core::_ops::to_cow_byte_ptr(unsafe { &mut *arg0 });
+            Self::call(arg0)
+        };
+        result as _
+    }
+    extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+        let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+            &*info
+        });
+        let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+            &*info
+        });
+        let opctx = unsafe {
+            &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+                as *const deno_core::_ops::OpCtx)
+        };
+        let mut scope = unsafe { &mut *opctx.isolate };
+        let result = {
+            let arg0 = args.get(0usize as i32);
+            let arg0 = match deno_core::_ops::to_cow_one_byte(&mut scope, &arg0) {
+                Ok(arg0) => arg0,
+                Err(arg0) => {
+                    let mut scope = unsafe { deno_core::v8::CallbackScope::new(&*info) };
+                    let msg = deno_core::v8::String::new_from_one_byte(
+                            &mut scope,
+                            arg0.as_bytes(),
+                            deno_core::v8::NewStringType::Normal,
+                        )
+                        .unwrap();
+                    let exc = deno_core::v8::Exception::error(&mut scope, msg);
+                    scope.throw_exception(exc);
+                    return;
+                }
+            };
+            Self::call(arg0)
+        };
+        rv.set_uint32(result as u32);
+    }
+    #[inline(always)]
+    fn call(s: Cow<[u8]>) -> u32 {
+        s.len() as _
+    }
+}
diff --git a/ops/op2/test_cases/sync/string_onebyte.rs b/ops/op2/test_cases/sync/string_onebyte.rs
new file mode 100644
index 0000000..b0f9559
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_onebyte.rs
@@ -0,0 +1,10 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+#![deny(warnings)]
+deno_ops_compile_test_runner::prelude!();
+
+use std::borrow::Cow;
+
+#[op2(fast)]
+fn op_string_onebyte(#[string(onebyte)] s: Cow<[u8]>) -> u32 {
+    s.len() as _
+}