summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2023-07-01 16:07:05 -0600
committerGitHub <noreply@github.com>2023-07-01 22:07:05 +0000
commit6afdcf59b80b4a3ecf60f220ddff14f4309133d0 (patch)
treed6a6dad1945430e161fba9e86d93fe3d8893d89c
parent0f719aa79c2b471815c9d21014b37719c6557c1b (diff)
refactor(ops): op2 supports strings in argument and return position (#19613)
Support strings (&str, String, and Cow) in the argument position and String in the return position. Avoids copies where possible, though this is not always something we can do.
-rw-r--r--core/lib.rs3
-rw-r--r--core/runtime/ops.rs242
-rw-r--r--ops/op2/dispatch_fast.rs52
-rw-r--r--ops/op2/dispatch_slow.rs66
-rw-r--r--ops/op2/signature.rs12
-rw-r--r--ops/op2/test_cases/sync/add.out4
-rw-r--r--ops/op2/test_cases/sync/smi.out4
-rw-r--r--ops/op2/test_cases/sync/string_cow.out75
-rw-r--r--ops/op2/test_cases/sync/string_cow.rs4
-rw-r--r--ops/op2/test_cases/sync/string_option_return.out53
-rw-r--r--ops/op2/test_cases/sync/string_option_return.rs5
-rw-r--r--ops/op2/test_cases/sync/string_owned.out73
-rw-r--r--ops/op2/test_cases/sync/string_owned.rs4
-rw-r--r--ops/op2/test_cases/sync/string_ref.out75
-rw-r--r--ops/op2/test_cases/sync/string_ref.rs4
-rw-r--r--ops/op2/test_cases/sync/string_return.out49
-rw-r--r--ops/op2/test_cases/sync/string_return.rs5
17 files changed, 710 insertions, 20 deletions
diff --git a/core/lib.rs b/core/lib.rs
index 30a14f3e4..2f8d9142c 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -144,6 +144,9 @@ pub mod _ops {
pub use super::runtime::ops::queue_async_op;
pub use super::runtime::ops::queue_fast_async_op;
pub use super::runtime::ops::to_i32;
+ pub use super::runtime::ops::to_str;
+ pub use super::runtime::ops::to_str_ptr;
+ pub use super::runtime::ops::to_string_ptr;
pub use super::runtime::ops::to_u32;
pub use super::runtime::V8_WRAPPER_OBJECT_INDEX;
pub use super::runtime::V8_WRAPPER_TYPE_INDEX;
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 9e37977c8..5ecab5edf 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -7,8 +7,10 @@ use futures::future::Either;
use futures::future::Future;
use futures::future::FutureExt;
use futures::task::noop_waker_ref;
+use std::borrow::Cow;
use std::cell::RefCell;
use std::future::ready;
+use std::mem::MaybeUninit;
use std::option::Option;
use std::task::Context;
use std::task::Poll;
@@ -197,6 +199,104 @@ pub fn to_i64(number: &v8::Value) -> i32 {
0
}
+/// Expands `inbuf` to `outbuf`, assuming that `outbuf` has at least 2x `input_length`.
+#[inline(always)]
+unsafe fn latin1_to_utf8(
+ input_length: usize,
+ inbuf: *const u8,
+ outbuf: *mut u8,
+) -> usize {
+ let mut output = 0;
+ let mut input = 0;
+ while input < input_length {
+ let char = *(inbuf.add(input));
+ if char < 0x80 {
+ *(outbuf.add(output)) = char;
+ output += 1;
+ } else {
+ // Top two bits
+ *(outbuf.add(output)) = (char >> 6) | 0b1100_0000;
+ // Bottom six bits
+ *(outbuf.add(output + 1)) = (char & 0b0011_1111) | 0b1000_0000;
+ output += 2;
+ }
+ input += 1;
+ }
+ output
+}
+
+/// Converts a [`v8::fast_api::FastApiOneByteString`] to either an owned string, or a borrowed string, depending on whether it fits into the
+/// provided buffer.
+pub fn to_str_ptr<'a, const N: usize>(
+ string: &mut v8::fast_api::FastApiOneByteString,
+ buffer: &'a mut [MaybeUninit<u8>; N],
+) -> Cow<'a, str> {
+ let input_buf = string.as_bytes();
+ let input_len = input_buf.len();
+ let output_len = buffer.len();
+
+ // We know that this string is full of either one or two-byte UTF-8 chars, so if it's < 1/2 of N we
+ // can skip the ASCII check and just start copying.
+ if input_len < N / 2 {
+ debug_assert!(output_len >= input_len * 2);
+ let buffer = buffer.as_mut_ptr() as *mut u8;
+
+ let written =
+ // SAFETY: We checked that buffer is at least 2x the size of input_buf
+ unsafe { latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), buffer) };
+
+ debug_assert!(written <= output_len);
+
+ let slice = std::ptr::slice_from_raw_parts(buffer, written);
+ // SAFETY: We know it's valid UTF-8, so make a string
+ Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(&*slice) })
+ } else {
+ // TODO(mmastrac): We could be smarter here about not allocating
+ Cow::Owned(to_string_ptr(string))
+ }
+}
+
+/// Converts a [`v8::fast_api::FastApiOneByteString`] to an owned string. May over-allocate to avoid
+/// re-allocation.
+pub fn to_string_ptr(
+ string: &mut v8::fast_api::FastApiOneByteString,
+) -> String {
+ let input_buf = string.as_bytes();
+ let capacity = input_buf.len() * 2;
+
+ // SAFETY: We're allocating a buffer of 2x the input size, writing valid UTF-8, then turning that into a string
+ unsafe {
+ // 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.
+ let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
+ let out = std::alloc::alloc(layout);
+
+ let written = latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), out);
+
+ debug_assert!(written <= capacity);
+ // We know it's valid UTF-8, so make a string
+ String::from_raw_parts(out, written, capacity)
+ }
+}
+
+/// Converts a [`v8::String`] to either an owned string, or a borrowed string, depending on whether it fits into the
+/// provided buffer.
+#[inline(always)]
+pub fn to_str<'a, const N: usize>(
+ scope: &mut v8::Isolate,
+ string: &v8::Value,
+ buffer: &'a mut [MaybeUninit<u8>; N],
+) -> Cow<'a, str> {
+ if !string.is_string() {
+ return Cow::Borrowed("");
+ }
+
+ // SAFETY: We checked is_string above
+ let string: &v8::String = unsafe { std::mem::transmute(string) };
+
+ string.to_rust_cow_lossy(scope, buffer)
+}
+
#[cfg(test)]
mod tests {
use crate::error::generic_error;
@@ -206,6 +306,7 @@ mod tests {
use crate::JsRuntime;
use crate::RuntimeOptions;
use deno_ops::op2;
+ use std::borrow::Cow;
use std::cell::Cell;
crate::extension!(
@@ -219,6 +320,13 @@ mod tests {
op_test_result_void_err,
op_test_result_primitive_ok,
op_test_result_primitive_err,
+ op_test_string_owned,
+ op_test_string_ref,
+ op_test_string_cow,
+ op_test_string_roundtrip_char,
+ op_test_string_return,
+ op_test_string_option_return,
+ op_test_string_roundtrip,
op_test_generics<String>,
]
);
@@ -229,18 +337,11 @@ mod tests {
#[op2(core, fast)]
pub fn op_test_fail() {
- FAIL.with(|b| {
- println!("fail");
- b.set(true)
- })
+ FAIL.with(|b| b.set(true))
}
/// Run a test for a single op.
- fn run_test2(
- repeat: usize,
- op: &'static str,
- test: &'static str,
- ) -> Result<(), AnyError> {
+ fn run_test2(repeat: usize, op: &str, test: &str) -> Result<(), AnyError> {
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![testing::init_ops_and_esm()],
..Default::default()
@@ -278,7 +379,7 @@ mod tests {
),
)?;
if FAIL.with(|b| b.get()) {
- Err(generic_error("test failed"))
+ Err(generic_error(format!("{op} test failed ({test})")))
} else {
Ok(())
}
@@ -406,6 +507,127 @@ mod tests {
Ok(())
}
+ #[op2(core, fast)]
+ pub fn op_test_string_owned(#[string] s: String) -> u32 {
+ s.len() as _
+ }
+
+ #[op2(core, fast)]
+ pub fn op_test_string_ref(#[string] s: &str) -> u32 {
+ s.len() as _
+ }
+
+ #[op2(core, fast)]
+ pub fn op_test_string_cow(#[string] s: Cow<str>) -> u32 {
+ s.len() as _
+ }
+
+ #[op2(core, fast)]
+ pub fn op_test_string_roundtrip_char(#[string] s: Cow<str>) -> u32 {
+ s.chars().next().unwrap() as u32
+ }
+
+ #[tokio::test]
+ pub async fn test_op_strings() -> Result<(), Box<dyn std::error::Error>> {
+ for op in [
+ "op_test_string_owned",
+ "op_test_string_cow",
+ "op_test_string_ref",
+ ] {
+ for (len, str) in [
+ // ASCII
+ (3, "'abc'"),
+ // Latin-1 (one byte but two UTF-8 chars)
+ (2, "'\\u00a0'"),
+ // ASCII
+ (10000, "'a'.repeat(10000)"),
+ // Latin-1
+ (20000, "'\\u00a0'.repeat(10000)"),
+ // 4-byte UTF-8 emoji (1F995 = 🦕)
+ (40000, "'\\u{1F995}'.repeat(10000)"),
+ ] {
+ let test = format!("assert({op}({str}) == {len})");
+ run_test2(10000, op, &test)?;
+ }
+ }
+
+ // Ensure that we're correctly encoding UTF-8
+ run_test2(
+ 10000,
+ "op_test_string_roundtrip_char",
+ "assert(op_test_string_roundtrip_char('\\u00a0') == 0xa0)",
+ )?;
+ run_test2(
+ 10000,
+ "op_test_string_roundtrip_char",
+ "assert(op_test_string_roundtrip_char('\\u00ff') == 0xff)",
+ )?;
+ run_test2(
+ 10000,
+ "op_test_string_roundtrip_char",
+ "assert(op_test_string_roundtrip_char('\\u0080') == 0x80)",
+ )?;
+ run_test2(
+ 10000,
+ "op_test_string_roundtrip_char",
+ "assert(op_test_string_roundtrip_char('\\u0100') == 0x100)",
+ )?;
+ Ok(())
+ }
+
+ #[op2(core)]
+ #[string]
+ pub fn op_test_string_return(
+ #[string] a: Cow<str>,
+ #[string] b: Cow<str>,
+ ) -> String {
+ (a + b).to_string()
+ }
+
+ #[op2(core)]
+ #[string]
+ pub fn op_test_string_option_return(
+ #[string] a: Cow<str>,
+ #[string] b: Cow<str>,
+ ) -> Option<String> {
+ if a == "none" {
+ return None;
+ }
+ Some((a + b).to_string())
+ }
+
+ #[op2(core)]
+ #[string]
+ pub fn op_test_string_roundtrip(#[string] s: String) -> String {
+ s
+ }
+
+ #[tokio::test]
+ pub async fn test_op_string_returns() -> Result<(), Box<dyn std::error::Error>>
+ {
+ run_test2(
+ 1,
+ "op_test_string_return",
+ "assert(op_test_string_return('a', 'b') == 'ab')",
+ )?;
+ run_test2(
+ 1,
+ "op_test_string_option_return",
+ "assert(op_test_string_option_return('a', 'b') == 'ab')",
+ )?;
+ run_test2(
+ 1,
+ "op_test_string_option_return",
+ "assert(op_test_string_option_return('none', 'b') == null)",
+ )?;
+ run_test2(
+ 1,
+ "op_test_string_roundtrip",
+ "assert(op_test_string_roundtrip('\\u0080\\u00a0\\u00ff') == '\\u0080\\u00a0\\u00ff')",
+ )?;
+ Ok(())
+ }
+
// We don't actually test this one -- we just want it to compile
#[op2(core, fast)]
pub fn op_test_generics<T: Clone>() {}
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 5262196f4..f9d74416a 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -4,10 +4,13 @@ use super::signature::Arg;
use super::signature::NumericArg;
use super::signature::ParsedSignature;
use super::signature::RetVal;
+use super::signature::Special;
use super::V8MappingError;
+use proc_macro2::Ident;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
+use std::iter::zip;
#[allow(unused)]
#[derive(Debug, Default, PartialEq, Clone)]
@@ -49,10 +52,12 @@ impl V8FastCallType {
V8FastCallType::CallbackOptions => {
quote!(*mut #deno_core::v8::fast_api::FastApiCallbackOptions)
}
+ V8FastCallType::SeqOneByteString => {
+ quote!(*mut #deno_core::v8::fast_api::FastApiOneByteString)
+ }
V8FastCallType::Uint8Array
| V8FastCallType::Uint32Array
- | V8FastCallType::Float64Array
- | V8FastCallType::SeqOneByteString => unreachable!(),
+ | V8FastCallType::Float64Array => unreachable!(),
}
}
@@ -190,7 +195,10 @@ pub fn generate_dispatch_fast(
.map(|rv| rv.quote_rust_type(deno_core))
.collect::<Vec<_>>();
- let call_args = names.clone();
+ let call_idents = names.clone();
+ let call_args = zip(names.iter(), signature.args.iter())
+ .map(|(name, arg)| map_v8_fastcall_arg_to_arg(deno_core, name, arg))
+ .collect::<Vec<_>>();
let with_fast_api_callback_options = if *needs_fast_api_callback_options {
types.push(V8FastCallType::CallbackOptions.quote_rust_type(deno_core));
@@ -219,7 +227,8 @@ pub fn generate_dispatch_fast(
) -> #output_type {
#with_fast_api_callback_options
#with_opctx
- let #result = Self::call(#(#call_args as _),*);
+ #(#call_args)*
+ let #result = Self::call(#(#call_idents),*);
#handle_error
#result
}
@@ -228,6 +237,32 @@ pub fn generate_dispatch_fast(
Ok(Some((fast_definition, fast_fn)))
}
+fn map_v8_fastcall_arg_to_arg(
+ deno_core: &TokenStream,
+ arg_ident: &Ident,
+ arg: &Arg,
+) -> TokenStream {
+ let arg_temp = format_ident!("{}_temp", arg_ident);
+ match arg {
+ Arg::Special(Special::RefStr) => {
+ quote! {
+ let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let #arg_ident = &#deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
+ }
+ }
+ Arg::Special(Special::String) => {
+ quote!(let #arg_ident = #deno_core::_ops::to_string_ptr(unsafe { &mut *#arg_ident });)
+ }
+ Arg::Special(Special::CowStr) => {
+ quote! {
+ let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let #arg_ident = #deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
+ }
+ }
+ _ => quote!(let #arg_ident = #arg_ident as _;),
+ }
+}
+
fn map_arg_to_v8_fastcall_type(
arg: &Arg,
) -> Result<Option<V8FastCallType>, V8MappingError> {
@@ -247,6 +282,13 @@ fn map_arg_to_v8_fastcall_type(
Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
V8FastCallType::I64
}
+ // Ref strings that are one byte internally may be passed as a SeqOneByteString,
+ // which gives us a FastApiOneByteString.
+ Arg::Special(Special::RefStr) => V8FastCallType::SeqOneByteString,
+ // Owned strings can be fast, but we'll have to copy them.
+ Arg::Special(Special::String) => V8FastCallType::SeqOneByteString,
+ // Cow strings can be fast, but may require copying
+ Arg::Special(Special::CowStr) => V8FastCallType::SeqOneByteString,
_ => return Err(V8MappingError::NoMapping("a fast argument", arg.clone())),
};
Ok(Some(rv))
@@ -271,6 +313,8 @@ fn map_retval_to_v8_fastcall_type(
Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
V8FastCallType::I64
}
+ // We don't return special return types
+ Arg::Option(_) => return Ok(None),
Arg::Special(_) => return Ok(None),
_ => {
return Err(V8MappingError::NoMapping(
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index bf36e5d83..2ec67cc76 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -8,6 +8,7 @@ use super::signature::Special;
use super::MacroConfig;
use super::V8MappingError;
use proc_macro2::TokenStream;
+use quote::format_ident;
use quote::quote;
pub(crate) fn generate_dispatch_slow(
@@ -151,10 +152,14 @@ pub fn from_arg(
arg: &Arg,
) -> Result<TokenStream, V8MappingError> {
let GeneratorState {
- deno_core, args, ..
+ deno_core,
+ args,
+ scope,
+ needs_scope,
+ ..
} = &mut generator_state;
let arg_ident = args.get_mut(index).expect("Argument at index was missing");
-
+ let arg_temp = format_ident!("{}_temp", arg_ident);
let res = match arg {
Arg::Numeric(NumericArg::bool) => quote! {
let #arg_ident = #arg_ident.is_true();
@@ -198,13 +203,31 @@ pub fn from_arg(
}
}
Arg::Option(Special::String) => {
+ *needs_scope = true;
+ quote! {
+ let #arg_ident = #arg_ident.to_rust_string_lossy(#scope);
+ }
+ }
+ Arg::Special(Special::String) => {
+ *needs_scope = true;
quote! {
- let #arg_ident = #arg_ident.to_rust_string_lossy();
+ let #arg_ident = #arg_ident.to_rust_string_lossy(#scope);
}
}
Arg::Special(Special::RefStr) => {
+ *needs_scope = true;
+ quote! {
+ // Trade 1024 bytes of stack space for potentially non-allocating strings
+ let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let #arg_ident = &#deno_core::_ops::to_str(#scope, &#arg_ident, &mut #arg_temp);
+ }
+ }
+ Arg::Special(Special::CowStr) => {
+ *needs_scope = true;
quote! {
- let #arg_ident = #arg_ident.to_rust_string_lossy();
+ // Trade 1024 bytes of stack space for potentially non-allocating strings
+ let mut #arg_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let #arg_ident = #deno_core::_ops::to_str(#scope, &#arg_ident, &mut #arg_temp);
}
}
_ => return Err(V8MappingError::NoMapping("a slow argument", arg.clone())),
@@ -243,9 +266,12 @@ pub fn return_value_infallible(
ret_type: &Arg,
) -> Result<TokenStream, V8MappingError> {
let GeneratorState {
+ deno_core,
+ scope,
result,
retval,
needs_retval,
+ needs_scope,
..
} = generator_state;
@@ -265,6 +291,38 @@ pub fn return_value_infallible(
*needs_retval = true;
quote!(#retval.set_int32(#result as i32);)
}
+ Arg::Special(Special::String) => {
+ *needs_retval = true;
+ *needs_scope = true;
+ quote! {
+ if #result.is_empty() {
+ #retval.set_empty_string();
+ } else {
+ // This should not fail in normal cases
+ // TODO(mmastrac): This has extra allocations that we need to get rid of, especially if the string
+ // is ASCII. We could make an "external Rust String" string in V8 from these and re-use the allocation.
+ let temp = #deno_core::v8::String::new(#scope, &#result).unwrap();
+ #retval.set(temp.into());
+ }
+ }
+ }
+ Arg::Option(Special::String) => {
+ *needs_retval = true;
+ *needs_scope = true;
+ // End the generator_state borrow
+ let (result, retval) = (result.clone(), retval.clone());
+ let some = return_value_infallible(
+ generator_state,
+ &Arg::Special(Special::String),
+ )?;
+ quote! {
+ 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 15c40e007..5d472fcf3 100644
--- a/ops/op2/signature.rs
+++ b/ops/op2/signature.rs
@@ -106,6 +106,7 @@ pub enum Special {
HandleScope,
OpState,
String,
+ CowStr,
RefStr,
FastApiCallbackOptions,
}
@@ -431,6 +432,17 @@ fn parse_type_path(attrs: Attributes, tp: &TypePath) -> Result<Arg, ArgError> {
Err(ArgError::MissingStringAttribute)
}
}
+ ( $( std :: str :: )? str ) => {
+ // We should not hit this path with a #[string] argument
+ Err(ArgError::MissingStringAttribute)
+ }
+ ( $( std :: borrow :: )? Cow < str > ) => {
+ if attrs.primary == Some(AttributeModifier::String) {
+ Ok(Arg::Special(Special::CowStr))
+ } else {
+ Err(ArgError::MissingStringAttribute)
+ }
+ }
( $( std :: ffi :: )? c_void ) => Ok(Arg::Numeric(NumericArg::__VOID__)),
( OpState ) => Ok(Arg::Special(Special::OpState)),
( v8 :: HandleScope ) => Ok(Arg::Special(Special::HandleScope)),
diff --git a/ops/op2/test_cases/sync/add.out b/ops/op2/test_cases/sync/add.out
index c8f77ab92..a73f032aa 100644
--- a/ops/op2/test_cases/sync/add.out
+++ b/ops/op2/test_cases/sync/add.out
@@ -52,7 +52,9 @@ impl op_add {
arg0: u32,
arg1: u32,
) -> u32 {
- let result = Self::call(arg0 as _, arg1 as _);
+ let arg0 = arg0 as _;
+ let arg1 = arg1 as _;
+ let result = Self::call(arg0, arg1);
result
}
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
diff --git a/ops/op2/test_cases/sync/smi.out b/ops/op2/test_cases/sync/smi.out
index 85db2576e..24b81ae47 100644
--- a/ops/op2/test_cases/sync/smi.out
+++ b/ops/op2/test_cases/sync/smi.out
@@ -52,7 +52,9 @@ impl op_add {
arg0: i32,
arg1: u32,
) -> u32 {
- let result = Self::call(arg0 as _, arg1 as _);
+ let arg0 = arg0 as _;
+ let arg1 = arg1 as _;
+ let result = Self::call(arg0, arg1);
result
}
extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
diff --git a/ops/op2/test_cases/sync/string_cow.out b/ops/op2/test_cases/sync/string_cow.out
new file mode 100644
index 000000000..7d388e598
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_cow.out
@@ -0,0 +1,75 @@
+#[allow(non_camel_case_types)]
+struct op_string_cow {
+ _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_cow {
+ const NAME: &'static str = stringify!(op_string_cow);
+ const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
+ name: stringify!(op_string_cow),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::SeqOneByteString],
+ CType::Uint32,
+ Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+ )
+ }),
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 1usize as u8,
+ };
+}
+impl op_string_cow {
+ pub const fn name() -> &'static str {
+ stringify!(op_string_cow)
+ }
+ pub const fn decl() -> deno_core::_ops::OpDecl {
+ deno_core::_ops::OpDecl {
+ name: stringify!(op_string_cow),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::SeqOneByteString],
+ CType::Uint32,
+ Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+ )
+ }),
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 1usize as u8,
+ }
+ }
+ fn v8_fn_ptr_fast(
+ _: deno_core::v8::Local<deno_core::v8::Object>,
+ arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
+ ) -> u32 {
+ let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let arg0 = deno_core::_ops::to_str_ptr(unsafe { &mut *arg0 }, &mut arg0_temp);
+ let result = Self::call(arg0);
+ result
+ }
+ extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ 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 arg0 = args.get(0usize as i32);
+ let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let arg0 = deno_core::_ops::to_str(scope, &arg0, &mut arg0_temp);
+ let result = Self::call(arg0);
+ rv.set_uint32(result as u32);
+ }
+ #[inline(always)]
+ fn call(s: Cow<str>) -> u32 {}
+}
diff --git a/ops/op2/test_cases/sync/string_cow.rs b/ops/op2/test_cases/sync/string_cow.rs
new file mode 100644
index 000000000..ed4dfca82
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_cow.rs
@@ -0,0 +1,4 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2(fast)]
+fn op_string_cow(#[string] s: Cow<str>) -> u32 {}
diff --git a/ops/op2/test_cases/sync/string_option_return.out b/ops/op2/test_cases/sync/string_option_return.out
new file mode 100644
index 000000000..6143ac217
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_option_return.out
@@ -0,0 +1,53 @@
+#[allow(non_camel_case_types)]
+pub struct op_string_return {
+ _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_return {
+ const NAME: &'static str = stringify!(op_string_return);
+ const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
+ name: stringify!(op_string_return),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: None,
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 0usize as u8,
+ };
+}
+impl op_string_return {
+ pub const fn name() -> &'static str {
+ stringify!(op_string_return)
+ }
+ pub const fn decl() -> deno_core::_ops::OpDecl {
+ deno_core::_ops::OpDecl {
+ name: stringify!(op_string_return),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: None,
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 0usize as u8,
+ }
+ }
+ extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+ &*info
+ });
+ let result = Self::call();
+ if let Some(result) = result {
+ if result.is_empty() {
+ rv.set_empty_string();
+ } else {
+ let temp = deno_core::v8::String::new(scope, &result).unwrap();
+ rv.set(temp.into());
+ }
+ } else {
+ rv.set_null();
+ }
+ }
+ #[inline(always)]
+ pub fn call() -> Option<String> {}
+}
diff --git a/ops/op2/test_cases/sync/string_option_return.rs b/ops/op2/test_cases/sync/string_option_return.rs
new file mode 100644
index 000000000..932836d2f
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_option_return.rs
@@ -0,0 +1,5 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2]
+#[string]
+pub fn op_string_return() -> Option<String> {}
diff --git a/ops/op2/test_cases/sync/string_owned.out b/ops/op2/test_cases/sync/string_owned.out
new file mode 100644
index 000000000..7418a311c
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_owned.out
@@ -0,0 +1,73 @@
+#[allow(non_camel_case_types)]
+struct op_string_owned {
+ _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_owned {
+ const NAME: &'static str = stringify!(op_string_owned);
+ const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
+ name: stringify!(op_string_owned),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::SeqOneByteString],
+ CType::Uint32,
+ Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+ )
+ }),
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 1usize as u8,
+ };
+}
+impl op_string_owned {
+ pub const fn name() -> &'static str {
+ stringify!(op_string_owned)
+ }
+ pub const fn decl() -> deno_core::_ops::OpDecl {
+ deno_core::_ops::OpDecl {
+ name: stringify!(op_string_owned),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::SeqOneByteString],
+ CType::Uint32,
+ Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+ )
+ }),
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 1usize as u8,
+ }
+ }
+ fn v8_fn_ptr_fast(
+ _: deno_core::v8::Local<deno_core::v8::Object>,
+ arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
+ ) -> u32 {
+ let arg0 = deno_core::_ops::to_string_ptr(unsafe { &mut *arg0 });
+ let result = Self::call(arg0);
+ result
+ }
+ extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ 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 arg0 = args.get(0usize as i32);
+ let arg0 = arg0.to_rust_string_lossy(scope);
+ let result = Self::call(arg0);
+ rv.set_uint32(result as u32);
+ }
+ #[inline(always)]
+ fn call(s: String) -> u32 {}
+}
diff --git a/ops/op2/test_cases/sync/string_owned.rs b/ops/op2/test_cases/sync/string_owned.rs
new file mode 100644
index 000000000..b81d7ece9
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_owned.rs
@@ -0,0 +1,4 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2(fast)]
+fn op_string_owned(#[string] s: String) -> u32 {}
diff --git a/ops/op2/test_cases/sync/string_ref.out b/ops/op2/test_cases/sync/string_ref.out
new file mode 100644
index 000000000..1b853fccc
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_ref.out
@@ -0,0 +1,75 @@
+#[allow(non_camel_case_types)]
+struct op_string_owned {
+ _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_owned {
+ const NAME: &'static str = stringify!(op_string_owned);
+ const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
+ name: stringify!(op_string_owned),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::SeqOneByteString],
+ CType::Uint32,
+ Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+ )
+ }),
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 1usize as u8,
+ };
+}
+impl op_string_owned {
+ pub const fn name() -> &'static str {
+ stringify!(op_string_owned)
+ }
+ pub const fn decl() -> deno_core::_ops::OpDecl {
+ deno_core::_ops::OpDecl {
+ name: stringify!(op_string_owned),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::SeqOneByteString],
+ CType::Uint32,
+ Self::v8_fn_ptr_fast as *const ::std::ffi::c_void,
+ )
+ }),
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 1usize as u8,
+ }
+ }
+ fn v8_fn_ptr_fast(
+ _: deno_core::v8::Local<deno_core::v8::Object>,
+ arg0: *mut deno_core::v8::fast_api::FastApiOneByteString,
+ ) -> u32 {
+ let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let arg0 = &deno_core::_ops::to_str_ptr(unsafe { &mut *arg0 }, &mut arg0_temp);
+ let result = Self::call(arg0);
+ result
+ }
+ extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ 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 arg0 = args.get(0usize as i32);
+ let mut arg0_temp: [::std::mem::MaybeUninit<u8>; 1024] = [::std::mem::MaybeUninit::uninit(); 1024];
+ let arg0 = &deno_core::_ops::to_str(scope, &arg0, &mut arg0_temp);
+ let result = Self::call(arg0);
+ rv.set_uint32(result as u32);
+ }
+ #[inline(always)]
+ fn call(s: &str) -> u32 {}
+}
diff --git a/ops/op2/test_cases/sync/string_ref.rs b/ops/op2/test_cases/sync/string_ref.rs
new file mode 100644
index 000000000..a7efa9f0c
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_ref.rs
@@ -0,0 +1,4 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2(fast)]
+fn op_string_owned(#[string] s: &str) -> u32 {}
diff --git a/ops/op2/test_cases/sync/string_return.out b/ops/op2/test_cases/sync/string_return.out
new file mode 100644
index 000000000..5e68b9314
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_return.out
@@ -0,0 +1,49 @@
+#[allow(non_camel_case_types)]
+pub struct op_string_return {
+ _unconstructable: ::std::marker::PhantomData<()>,
+}
+impl deno_core::_ops::Op for op_string_return {
+ const NAME: &'static str = stringify!(op_string_return);
+ const DECL: deno_core::_ops::OpDecl = deno_core::_ops::OpDecl {
+ name: stringify!(op_string_return),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: None,
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 0usize as u8,
+ };
+}
+impl op_string_return {
+ pub const fn name() -> &'static str {
+ stringify!(op_string_return)
+ }
+ pub const fn decl() -> deno_core::_ops::OpDecl {
+ deno_core::_ops::OpDecl {
+ name: stringify!(op_string_return),
+ v8_fn_ptr: Self::v8_fn_ptr as _,
+ enabled: true,
+ fast_fn: None,
+ is_async: false,
+ is_unstable: false,
+ is_v8: false,
+ arg_count: 0usize as u8,
+ }
+ }
+ extern "C" fn v8_fn_ptr(info: *const deno_core::v8::FunctionCallbackInfo) {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
+ &*info
+ });
+ let result = Self::call();
+ if result.is_empty() {
+ rv.set_empty_string();
+ } else {
+ let temp = deno_core::v8::String::new(scope, &result).unwrap();
+ rv.set(temp.into());
+ }
+ }
+ #[inline(always)]
+ pub fn call() -> String {}
+}
diff --git a/ops/op2/test_cases/sync/string_return.rs b/ops/op2/test_cases/sync/string_return.rs
new file mode 100644
index 000000000..667b68a14
--- /dev/null
+++ b/ops/op2/test_cases/sync/string_return.rs
@@ -0,0 +1,5 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#[op2]
+#[string]
+pub fn op_string_return() -> String {}