feat(ops): relational ops (#18023)

Join two independent ops into one. A fast impl of one + a slow callback
of another. Here's an example showing optimized paths for latin-1 via
fast call and the next-best fallback using V8 apis.

```rust
#[op(v8)]
fn op_encoding_encode_into_fallback(
  scope: &mut v8::HandleScope,
  input: serde_v8::Value,
// ...

#[op(fast, slow = op_encoding_encode_into_fallback)]
fn op_encoding_encode_into(
  input: Cow<'_, str>,
// ...
```

Benchmark results of the fallback path:
```
time target/release/deno run -A --unstable ./cli/tests/testdata/benches/text_encoder_into_perf.js
________________________________________________________
Executed in   70.90 millis    fish           external
   usr time   57.76 millis    0.23 millis   57.53 millis
   sys time   17.02 millis    1.28 millis   15.74 millis

target/release/deno_main run -A --unstable ./cli/tests/testdata/benches/text_encoder_into_perf.js
________________________________________________________
Executed in  154.00 millis    fish           external
   usr time   67.14 millis    0.26 millis   66.88 millis
   sys time   38.82 millis    1.47 millis   37.35 millis
```
This commit is contained in:
Divy Srivastava 2023-03-05 13:30:22 +05:30 committed by GitHub
parent 888ceac7fd
commit 0910be4d64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 27 deletions

View file

@ -350,7 +350,28 @@ impl Resource for TextDecoderResource {
}
}
#[op]
#[op(v8)]
fn op_encoding_encode_into_fallback(
scope: &mut v8::HandleScope,
input: serde_v8::Value,
buffer: &mut [u8],
out_buf: &mut [u32],
) -> Result<(), AnyError> {
let s = v8::Local::<v8::String>::try_from(input.v8_value)?;
let mut nchars = 0;
out_buf[1] = s.write_utf8(
scope,
buffer,
Some(&mut nchars),
v8::WriteOptions::NO_NULL_TERMINATION
| v8::WriteOptions::REPLACE_INVALID_UTF8,
) as u32;
out_buf[0] = nchars as u32;
Ok(())
}
#[op(fast, slow = op_encoding_encode_into_fallback)]
fn op_encoding_encode_into(
input: Cow<'_, str>,
buffer: &mut [u8],

View file

@ -1,44 +1,54 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use syn::parse::Parse;
use syn::parse::ParseStream;
use syn::punctuated::Punctuated;
use syn::Error;
use syn::Ident;
use syn::Result;
use syn::Token;
#[derive(Copy, Clone, Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Attributes {
pub is_unstable: bool,
pub is_v8: bool,
pub must_be_fast: bool,
pub deferred: bool,
pub is_wasm: bool,
pub relation: Option<Ident>,
}
impl Parse for Attributes {
fn parse(input: ParseStream) -> Result<Self> {
let vars = Punctuated::<Ident, Token![,]>::parse_terminated(input)?;
let vars: Vec<_> = vars.iter().map(Ident::to_string).collect();
let vars: Vec<_> = vars.iter().map(String::as_str).collect();
for var in vars.iter() {
if !["unstable", "v8", "fast", "deferred", "wasm"].contains(var) {
return Err(Error::new(
input.span(),
"invalid attribute, expected one of: unstable, v8, fast, deferred, wasm",
));
}
let mut self_ = Self::default();
let mut fast = false;
while let Ok(v) = input.parse::<Ident>() {
match v.to_string().as_str() {
"unstable" => self_.is_unstable = true,
"v8" => self_.is_v8 = true,
"fast" => fast = true,
"deferred" => self_.deferred = true,
"wasm" => self_.is_wasm = true,
"slow" => {
if !fast {
return Err(Error::new(
input.span(),
"relational attributes can only be used with fast attribute",
));
}
input.parse::<Token![=]>()?;
self_.relation = Some(input.parse()?);
}
_ => {
return Err(Error::new(
input.span(),
"invalid attribute, expected one of: unstable, v8, fast, deferred, wasm",
));
}
};
let _ = input.parse::<Token![,]>();
}
let is_wasm = vars.contains(&"wasm");
self_.must_be_fast = self_.is_wasm || fast;
Ok(Self {
is_unstable: vars.contains(&"unstable"),
is_v8: vars.contains(&"v8"),
deferred: vars.contains(&"deferred"),
must_be_fast: is_wasm || vars.contains(&"fast"),
is_wasm,
})
Ok(self_)
}
}

View file

@ -111,24 +111,68 @@ impl Op {
active,
} = fast_call::generate(&core, &mut optimizer, &item);
let docline = format!("Use `{name}::decl()` to get an op-declaration");
let is_v8 = attrs.is_v8;
let is_unstable = attrs.is_unstable;
if let Some(v8_fn) = attrs.relation {
return quote! {
#[allow(non_camel_case_types)]
#[doc="Auto-generated by `deno_ops`, i.e: `#[op]`"]
#[doc=""]
#[doc=#docline]
#[doc="you can include in a `deno_core::Extension`."]
pub struct #name;
#[doc(hidden)]
impl #name {
pub fn name() -> &'static str {
stringify!(#name)
}
pub fn v8_fn_ptr #generics () -> #core::v8::FunctionCallback #where_clause {
use #core::v8::MapFnTo;
#v8_fn::v8_func::<#type_params>.map_fn_to()
}
pub fn decl #generics () -> #core::OpDecl #where_clause {
#core::OpDecl {
name: Self::name(),
v8_fn_ptr: Self::v8_fn_ptr::<#type_params>(),
enabled: true,
fast_fn: #decl,
is_async: #is_async,
is_unstable: #is_unstable,
is_v8: #is_v8,
argc: 0,
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
#orig
}
#impl_and_fn
};
}
let has_fallible_fast_call = active && optimizer.returns_result;
let (v8_body, argc) = if is_async {
let deferred = attrs.deferred;
codegen_v8_async(
&core,
&item,
attrs,
item.sig.asyncness.is_some(),
attrs.deferred,
deferred,
)
} else {
codegen_v8_sync(&core, &item, attrs, has_fallible_fast_call)
};
let is_v8 = attrs.is_v8;
let is_unstable = attrs.is_unstable;
let docline = format!("Use `{name}::decl()` to get an op-declaration");
// Generate wrapper
quote! {
#[allow(non_camel_case_types)]