From 027d4d433dce32a3b715184b54e7fe6403dedec2 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 7 Sep 2022 16:21:47 +0530 Subject: [PATCH] perf(ops): inline &[u8] arguments and enable fast API (#15731) --- core/ops_builtin.rs | 4 +- core/ops_builtin_v8.rs | 2 +- ext/ffi/lib.rs | 3 +- ext/web/02_timers.js | 10 ++-- ext/web/compression.rs | 14 +++--- ext/web/lib.rs | 20 ++++---- ext/web/timers.rs | 32 ++++--------- ops/lib.rs | 103 ++++++++++++++++++++++++++++++++++++++--- 8 files changed, 128 insertions(+), 60 deletions(-) diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs index 26ab4bed5a..02ecabc9cc 100644 --- a/core/ops_builtin.rs +++ b/core/ops_builtin.rs @@ -134,12 +134,12 @@ impl Resource for WasmStreamingResource { pub fn op_wasm_streaming_feed( state: &mut OpState, rid: ResourceId, - bytes: ZeroCopyBuf, + bytes: &[u8], ) -> Result<(), Error> { let wasm_streaming = state.resource_table.get::(rid)?; - wasm_streaming.0.borrow_mut().on_bytes_received(&bytes); + wasm_streaming.0.borrow_mut().on_bytes_received(bytes); Ok(()) } diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs index abdbdedc8e..3900c0641e 100644 --- a/core/ops_builtin_v8.rs +++ b/core/ops_builtin_v8.rs @@ -231,7 +231,7 @@ fn op_encode<'a>( #[op(v8)] fn op_decode<'a>( scope: &mut v8::HandleScope<'a>, - zero_copy: ZeroCopyBuf, + zero_copy: &[u8], ) -> Result, Error> { let buf = &zero_copy; diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index b93638c886..bc3a582096 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -18,7 +18,6 @@ use deno_core::Extension; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; use dlopen::raw::Library; use libffi::middle::Arg; use libffi::middle::Cif; @@ -2154,7 +2153,7 @@ where fn op_ffi_buf_copy_into( state: &mut deno_core::OpState, src: usize, - mut dst: ZeroCopyBuf, + dst: &mut [u8], len: usize, ) -> Result<(), AnyError> where diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index 6cbc706e67..5d7ee49e03 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -13,6 +13,7 @@ MapPrototypeGet, MapPrototypeHas, MapPrototypeSet, + Uint8Array, Uint32Array, // deno-lint-ignore camelcase NumberPOSITIVE_INFINITY, @@ -27,13 +28,10 @@ const { reportException } = window.__bootstrap.event; const { assert } = window.__bootstrap.infra; - let hr; + const hrU8 = new Uint8Array(8); + const hr = new Uint32Array(hrU8.buffer); function opNow() { - if (!hr) { - hr = new Uint32Array(2); - ops.op_now_set_buf(hr); - } - ops.op_now.fast(); + ops.op_now.fast(hrU8); return (hr[0] * 1000 + hr[1] / 1e6); } diff --git a/ext/web/compression.rs b/ext/web/compression.rs index f3610e2ea0..d2647e4988 100644 --- a/ext/web/compression.rs +++ b/ext/web/compression.rs @@ -68,38 +68,38 @@ pub fn op_compression_new( pub fn op_compression_write( state: &mut OpState, rid: ResourceId, - input: ZeroCopyBuf, + input: &[u8], ) -> Result { let resource = state.resource_table.get::(rid)?; let mut inner = resource.0.borrow_mut(); let out: Vec = match &mut *inner { Inner::DeflateDecoder(d) => { - d.write_all(&input)?; + d.write_all(input)?; d.flush()?; d.get_mut().drain(..) } Inner::DeflateEncoder(d) => { - d.write_all(&input)?; + d.write_all(input)?; d.flush()?; d.get_mut().drain(..) } Inner::DeflateRawDecoder(d) => { - d.write_all(&input)?; + d.write_all(input)?; d.flush()?; d.get_mut().drain(..) } Inner::DeflateRawEncoder(d) => { - d.write_all(&input)?; + d.write_all(input)?; d.flush()?; d.get_mut().drain(..) } Inner::GzDecoder(d) => { - d.write_all(&input)?; + d.write_all(input)?; d.flush()?; d.get_mut().drain(..) } Inner::GzEncoder(d) => { - d.write_all(&input)?; + d.write_all(input)?; d.flush()?; d.get_mut().drain(..) } diff --git a/ext/web/lib.rs b/ext/web/lib.rs index e71ed6d14b..9c1e85952a 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -50,7 +50,6 @@ pub use crate::message_port::JsMessageData; pub use crate::message_port::MessagePort; use crate::timers::op_now; -use crate::timers::op_now_set_buf; use crate::timers::op_sleep; use crate::timers::op_timer_handle; use crate::timers::StartTime; @@ -106,7 +105,6 @@ pub fn init( compression::op_compression_new::decl(), compression::op_compression_write::decl(), compression::op_compression_finish::decl(), - op_now_set_buf::decl(), op_now::decl::

(), op_timer_handle::decl(), op_cancel_handle::decl(), @@ -149,8 +147,8 @@ fn forgiving_base64_decode(input: &mut [u8]) -> Result { } #[op] -fn op_base64_encode(s: ZeroCopyBuf) -> String { - forgiving_base64_encode(s.as_ref()) +fn op_base64_encode(s: &[u8]) -> String { + forgiving_base64_encode(s) } #[op] @@ -179,7 +177,7 @@ fn op_encoding_normalize_label(label: String) -> Result { #[op] fn op_encoding_decode_single( - data: ZeroCopyBuf, + data: &[u8], label: String, fatal: bool, ignore_bom: bool, @@ -205,7 +203,7 @@ fn op_encoding_decode_single( if fatal { let (result, _, written) = - decoder.decode_to_utf16_without_replacement(&data, &mut output, true); + decoder.decode_to_utf16_without_replacement(data, &mut output, true); match result { DecoderResult::InputEmpty => { output.truncate(written); @@ -220,7 +218,7 @@ fn op_encoding_decode_single( } } else { let (result, _, written, _) = - decoder.decode_to_utf16(&data, &mut output, true); + decoder.decode_to_utf16(data, &mut output, true); match result { CoderResult::InputEmpty => { output.truncate(written); @@ -262,7 +260,7 @@ fn op_encoding_new_decoder( #[op] fn op_encoding_decode( state: &mut OpState, - data: ZeroCopyBuf, + data: &[u8], rid: ResourceId, stream: bool, ) -> Result { @@ -279,7 +277,7 @@ fn op_encoding_decode( if fatal { let (result, _, written) = - decoder.decode_to_utf16_without_replacement(&data, &mut output, !stream); + decoder.decode_to_utf16_without_replacement(data, &mut output, !stream); match result { DecoderResult::InputEmpty => { output.truncate(written); @@ -294,7 +292,7 @@ fn op_encoding_decode( } } else { let (result, _, written, _) = - decoder.decode_to_utf16(&data, &mut output, !stream); + decoder.decode_to_utf16(data, &mut output, !stream); match result { CoderResult::InputEmpty => { output.truncate(written); @@ -326,7 +324,7 @@ struct EncodeIntoResult { #[op] fn op_encoding_encode_into( input: String, - mut buffer: ZeroCopyBuf, + buffer: &mut [u8], ) -> EncodeIntoResult { // Since `input` is already UTF-8, we can simply find the last UTF-8 code // point boundary from input that fits in `buffer`, and copy the bytes up to diff --git a/ext/web/timers.rs b/ext/web/timers.rs index f6b2cc9e73..ba5e12d62d 100644 --- a/ext/web/timers.rs +++ b/ext/web/timers.rs @@ -4,7 +4,6 @@ use deno_core::error::AnyError; use deno_core::op; -use deno_core::ZeroCopyBuf; use deno_core::CancelFuture; use deno_core::CancelHandle; @@ -24,24 +23,12 @@ pub trait TimersPermission { pub type StartTime = Instant; -static mut NOW_BUF: *mut u32 = std::ptr::null_mut(); - -#[op] -pub fn op_now_set_buf(buf: ZeroCopyBuf) { - assert_eq!(buf.len(), 8); - // SAFETY: This is safe because this is the only place where we initialize - // NOW_BUF. - unsafe { - NOW_BUF = buf.as_ptr() as *mut u32; - } -} - // Returns a milliseconds and nanoseconds subsec // since the start time of the deno runtime. // If the High precision flag is not set, the // nanoseconds are rounded on 2ms. #[op(fast)] -pub fn op_now(state: &mut OpState) +pub fn op_now(state: &mut OpState, buf: &mut [u8]) where TP: TimersPermission + 'static, { @@ -57,17 +44,14 @@ where let reduced_time_precision = 2_000_000; // 2ms in nanoseconds subsec_nanos -= subsec_nanos % reduced_time_precision; } - - // SAFETY: This is safe because we initialize NOW_BUF in op_now_set_buf, its a null pointer - // otherwise. - // op_now_set_buf guarantees that the buffer is 8 bytes long. - unsafe { - if !NOW_BUF.is_null() { - let buf = std::slice::from_raw_parts_mut(NOW_BUF, 2); - buf[0] = seconds as u32; - buf[1] = subsec_nanos as u32; - } + if buf.len() < 8 { + return; } + let buf: &mut [u32] = + // SAFETY: buffer is at least 8 bytes long. + unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as _, 2) }; + buf[0] = seconds as u32; + buf[1] = subsec_nanos as u32; } pub struct TimerHandle(Rc); diff --git a/ops/lib.rs b/ops/lib.rs index a41ba03207..1163d426af 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -11,6 +11,7 @@ use quote::format_ident; use quote::quote; use quote::ToTokens; use regex::Regex; +use std::collections::HashMap; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::FnArg; @@ -310,10 +311,11 @@ fn codegen_fast_impl( use_recv, use_fast_cb_opts, v8_values, + slices, }) = fast_info { let offset = if use_recv { 1 } else { 0 }; - let inputs = &f + let mut inputs = f .sig .inputs .iter() @@ -327,8 +329,11 @@ fn codegen_fast_impl( _ => unreachable!(), }, }; - if use_fast_cb_opts && idx == f.sig.inputs.len() - 1 { - return quote! { #ident: *mut #core::v8::fast_api::FastApiCallbackOptions }; + if let Some(ty) = slices.get(&(idx + offset)) { + return quote! { #ident: *const #core::v8::fast_api::FastApiTypedArray< #ty > }; + } + if use_fast_cb_opts && idx + offset == f.sig.inputs.len() - 1 { + return quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions }; } if v8_values.contains(&idx) { return quote! { #ident: #core::v8::Local < #core::v8::Value > }; @@ -336,6 +341,9 @@ fn codegen_fast_impl( quote!(#arg) }) .collect::>(); + if !slices.is_empty() && !use_fast_cb_opts { + inputs.push(quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions }); + } let input_idents = f .sig .inputs @@ -349,8 +357,19 @@ fn codegen_fast_impl( _ => unreachable!(), }, }; + if slices.get(&idx).is_some() { + return quote! { + match unsafe { &* #ident }.get_storage_if_aligned() { + Some(s) => s, + None => { + unsafe { &mut * fast_api_callback_options }.fallback = true; + return Default::default(); + }, + } + }; + } if use_fast_cb_opts && idx == f.sig.inputs.len() - 1 { - return quote! { Some(unsafe { &mut * #ident }) }; + return quote! { Some(unsafe { &mut * fast_api_callback_options }) }; } if v8_values.contains(&idx) { return quote! { @@ -494,6 +513,7 @@ struct FastApiSyn { use_recv: bool, use_fast_cb_opts: bool, v8_values: Vec, + slices: HashMap, } fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option { @@ -509,11 +529,12 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option { let mut use_recv = false; let mut use_fast_cb_opts = false; let mut v8_values = Vec::new(); + let mut slices = HashMap::new(); let mut args = vec![quote! { #core::v8::fast_api::Type::V8Value }]; for (pos, input) in inputs.iter().enumerate() { if pos == inputs.len() - 1 && is_optional_fast_callback_option(input) { - args.push(quote! { #core::v8::fast_api::Type::CallbackOptions }); use_fast_cb_opts = true; + args.push(quote! { #core::v8::fast_api::Type::CallbackOptions }); continue; } @@ -536,8 +557,14 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option { Some(arg) => { args.push(arg); } - // early return, this function cannot be a fast call. - None => return None, + None => match is_ref_slice(&ty) { + Some(_) => { + args.push(quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint8) }); + slices.insert(pos, quote!(u8)); + } + // early return, this function cannot be a fast call. + None => return None, + }, }, Some(arg) => { args.push(arg); @@ -555,6 +582,7 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option { args: args.parse().unwrap(), ret, use_recv, + slices, v8_values, use_fast_cb_opts, }) @@ -692,6 +720,44 @@ fn codegen_arg( }; }; } + // Fast path for &/&mut [u8] and &/&mut [u32] + if let Some(ty) = is_ref_slice(&**ty) { + let (ptr_ty, mutability) = match ty { + SliceType::U8 => (quote!([u8]), quote!(&)), + SliceType::U8Mut => (quote!([u8]), quote!(&mut)), + }; + return quote! { + let #ident = { + let value = args.get(#idx as i32); + if let Ok(view) = #core::v8::Local::<#core::v8::ArrayBufferView>::try_from(value) { + let (offset, len) = (view.byte_offset(), view.byte_length()); + let buffer = match view.buffer(scope) { + Some(v) => v, + None => { + return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx)); + } + }; + let store = buffer.get_backing_store(); + if store.is_shared() { + return #core::_ops::throw_type_error(scope, format!("Expected non-shared ArrayBufferView at position {}", #idx)); + } + unsafe { #mutability *(&store[offset..offset + len] as *const _ as *mut #ptr_ty) } + } else { + let b: #core::v8::Local<#core::v8::ArrayBuffer> = match value.try_into() { + Ok(v) => v, + Err(_) => { + return #core::_ops::throw_type_error(scope, format!("Expected ArrayBuffer at position {}", #idx)); + } + }; + let store = b.get_backing_store(); + if store.is_shared() { + return #core::_ops::throw_type_error(scope, format!("Expected non-shared ArrayBufferView at position {}", #idx)); + } + unsafe { #mutability *(&store[0..b.byte_length()] as *const _ as *mut #ptr_ty) } + } + }; + }; + } // Otherwise deserialize it via serde_v8 quote! { let #ident = args.get(#idx as i32); @@ -780,6 +846,29 @@ fn is_option_string(ty: impl ToTokens) -> bool { tokens(ty) == "Option < String >" } +enum SliceType { + U8, + U8Mut, +} + +fn is_ref_slice(ty: impl ToTokens) -> Option { + if is_u8_slice(&ty) { + return Some(SliceType::U8); + } + if is_u8_slice_mut(&ty) { + return Some(SliceType::U8Mut); + } + None +} + +fn is_u8_slice(ty: impl ToTokens) -> bool { + tokens(ty) == "& [u8]" +} + +fn is_u8_slice_mut(ty: impl ToTokens) -> bool { + tokens(ty) == "& mut [u8]" +} + fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool { tokens(&ty).contains("Option < & mut FastApiCallbackOptions") }