diff options
author | Matt Mastracci <matthew@mastracci.com> | 2023-07-01 16:07:05 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-01 22:07:05 +0000 |
commit | 6afdcf59b80b4a3ecf60f220ddff14f4309133d0 (patch) | |
tree | d6a6dad1945430e161fba9e86d93fe3d8893d89c | |
parent | 0f719aa79c2b471815c9d21014b37719c6557c1b (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.rs | 3 | ||||
-rw-r--r-- | core/runtime/ops.rs | 242 | ||||
-rw-r--r-- | ops/op2/dispatch_fast.rs | 52 | ||||
-rw-r--r-- | ops/op2/dispatch_slow.rs | 66 | ||||
-rw-r--r-- | ops/op2/signature.rs | 12 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/add.out | 4 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/smi.out | 4 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_cow.out | 75 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_cow.rs | 4 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_option_return.out | 53 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_option_return.rs | 5 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_owned.out | 73 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_owned.rs | 4 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_ref.out | 75 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_ref.rs | 4 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_return.out | 49 | ||||
-rw-r--r-- | ops/op2/test_cases/sync/string_return.rs | 5 |
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 {} |