1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-03 04:48:52 -05:00

refactor(ops): Rewrite fast call optimizer and codegen (#16514)

This commit is contained in:
Divy Srivastava 2022-11-10 03:53:31 -08:00 committed by GitHub
parent 92764c0dec
commit bc33a4b2e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1707 additions and 653 deletions

30
Cargo.lock generated
View file

@ -1166,11 +1166,14 @@ version = "0.36.0"
dependencies = [ dependencies = [
"deno_core", "deno_core",
"once_cell", "once_cell",
"pmutil",
"prettyplease",
"proc-macro-crate", "proc-macro-crate",
"proc-macro2 1.0.43", "proc-macro2 1.0.43",
"quote 1.0.21", "quote 1.0.21",
"regex", "regex",
"syn 1.0.99", "syn 1.0.99",
"testing_macros",
"trybuild", "trybuild",
] ]
@ -3337,6 +3340,16 @@ dependencies = [
"yansi", "yansi",
] ]
[[package]]
name = "prettyplease"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51"
dependencies = [
"proc-macro2 1.0.43",
"syn 1.0.99",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.2.1" version = "1.2.1"
@ -4759,6 +4772,23 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "testing_macros"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74ff09d2d4d4b7ea140ff67eb7ed8fd35a708e2c327bcde5a25707d66840099"
dependencies = [
"anyhow",
"glob",
"once_cell",
"pmutil",
"proc-macro2 1.0.43",
"quote 1.0.21",
"regex",
"relative-path",
"syn 1.0.99",
]
[[package]] [[package]]
name = "text-size" name = "text-size"
version = "1.1.0" version = "1.1.0"

View file

@ -12,6 +12,7 @@ proc-macro = true
[dependencies] [dependencies]
once_cell = "1.10.0" once_cell = "1.10.0"
pmutil = "0.5.3"
proc-macro-crate = "1.1.3" proc-macro-crate = "1.1.3"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
@ -20,4 +21,6 @@ syn = { version = "1", features = ["full", "extra-traits"] }
[dev-dependencies] [dev-dependencies]
deno_core = { path = "../core" } deno_core = { path = "../core" }
prettyplease = "0.1.21"
testing_macros = "0.2.7"
trybuild = "1.0.61" trybuild = "1.0.61"

37
ops/attrs.rs Normal file
View file

@ -0,0 +1,37 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Error, Ident, Result, Token,
};
#[derive(Copy, Clone, Debug, Default)]
pub struct Attributes {
pub is_unstable: bool,
pub is_v8: bool,
pub must_be_fast: bool,
pub deferred: bool,
}
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"].contains(var) {
return Err(Error::new(
input.span(),
"invalid attribute, expected one of: unstable, v8, fast, deferred",
));
}
}
Ok(Self {
is_unstable: vars.contains(&"unstable"),
is_v8: vars.contains(&"v8"),
must_be_fast: vars.contains(&"fast"),
deferred: vars.contains(&"deferred"),
})
}
}

32
ops/deno.rs Normal file
View file

@ -0,0 +1,32 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use proc_macro2::{Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::Ident;
/// Identifier to the `deno_core` crate.
///
/// If macro called in deno_core, `crate` is used.
/// If macro called outside deno_core, `deno_core` OR the renamed
/// version from Cargo.toml is used.
pub(crate) fn import() -> TokenStream {
let found_crate =
crate_name("deno_core").expect("deno_core not present in `Cargo.toml`");
match found_crate {
FoundCrate::Itself => {
// TODO(@littledivy): This won't work for `deno_core` examples
// since `crate` does not refer to `deno_core`.
// examples must re-export deno_core to make this work
// until Span inspection APIs are stabalized.
//
// https://github.com/rust-lang/rust/issues/54725
quote!(crate)
}
FoundCrate::Name(name) => {
let ident = Ident::new(&name, Span::call_site());
quote!(#ident)
}
}
}

399
ops/fast_call.rs Normal file
View file

@ -0,0 +1,399 @@
/// Code generation for V8 fast calls.
use crate::optimizer::FastValue;
use crate::optimizer::Optimizer;
use pmutil::{q, Quote, ToTokensExt};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse_quote, punctuated::Punctuated, token::Comma, GenericParam, Generics,
Ident, ItemFn, ItemImpl, Path, PathArguments, PathSegment, Type, TypePath,
};
pub(crate) struct FastImplItems {
pub(crate) impl_and_fn: TokenStream,
pub(crate) decl: TokenStream,
pub(crate) active: bool,
}
pub(crate) fn generate(
core: &TokenStream,
optimizer: &mut Optimizer,
item_fn: &ItemFn,
) -> FastImplItems {
if !optimizer.fast_compatible {
return FastImplItems {
impl_and_fn: TokenStream::new(),
decl: quote! { None },
active: false,
};
}
// TODO(@littledivy): Use `let..else` on 1.65.0
let output_ty = match &optimizer.fast_result {
Some(ty) => ty,
None => {
return FastImplItems {
impl_and_fn: TokenStream::new(),
decl: quote! { None },
active: false,
}
}
};
// We've got 3 idents.
//
// - op_foo, the public op declaration contains the user function.
// - op_foo_fast, the fast call type.
// - op_foo_fast_fn, the fast call function.
let ident = item_fn.sig.ident.clone();
let fast_ident = Ident::new(&format!("{}_fast", ident), Span::call_site());
let fast_fn_ident =
Ident::new(&format!("{}_fast_fn", ident), Span::call_site());
// Deal with generics.
let generics = &item_fn.sig.generics;
let (impl_generics, _, where_clause) = generics.split_for_impl();
// struct op_foo_fast <T, U> { ... }
let struct_generics = exclude_lifetime_params(&generics.params);
// std::marker::PhantomData <A>
let phantom_generics: Quote = match struct_generics {
Some(ref params) => q!(Vars { params }, { params }),
None => q!({ <()> }),
};
// op_foo_fast_fn :: <T>
let caller_generics: Quote = match struct_generics {
Some(ref params) => q!(Vars { params }, { ::params }),
None => q!({}),
};
// This goes in the FastFunction impl block.
let mut segments = Punctuated::new();
{
let mut arguments = PathArguments::None;
if let Some(ref struct_generics) = struct_generics {
arguments = PathArguments::AngleBracketed(parse_quote! {
#struct_generics
});
}
segments.push_value(PathSegment {
ident: fast_ident.clone(),
arguments,
});
}
// struct T <A> {
// _phantom: ::std::marker::PhantomData<A>,
// }
let fast_ty: Quote = q!(Vars { Type: &fast_ident, generics: &struct_generics, phantom_generics }, {
struct Type generics {
_phantom: ::std::marker::PhantomData phantom_generics,
}
});
// Original inputs.
let mut inputs = item_fn.sig.inputs.clone();
let mut transforms = q!({});
let mut pre_transforms = q!({});
// Apply parameter transforms
for (index, input) in inputs.iter_mut().enumerate() {
if let Some(transform) = optimizer.transforms.get(&index) {
let quo: Quote = transform.apply_for_fast_call(core, input);
transforms.push_tokens(&quo);
}
}
// Collect idents to be passed into function call, we can now freely
// modify the inputs.
let idents = inputs
.iter()
.map(|input| match input {
syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(),
_ => panic!("unexpected pattern"),
},
_ => panic!("unexpected argument"),
})
.collect::<Punctuated<_, Comma>>();
// Retain only *pure* parameters.
let mut fast_fn_inputs = if optimizer.has_opstate_in_parameters() {
inputs.iter().skip(1).cloned().collect()
} else {
inputs.clone()
};
let mut input_variants = optimizer
.fast_parameters
.iter()
.map(q_fast_ty_variant)
.collect::<Punctuated<_, Comma>>();
// Apply *hard* optimizer hints.
if optimizer.has_fast_callback_option || optimizer.needs_opstate() {
fast_fn_inputs.push(parse_quote! {
fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions
});
input_variants.push(q!({ CallbackOptions }));
}
let mut output_transforms = q!({});
if optimizer.needs_opstate() {
// Grab the op_state identifier, the first one. ¯\_(ツ)_/¯
let op_state = match idents.first() {
Some(ident) if optimizer.has_opstate_in_parameters() => ident.clone(),
// fn op_foo() -> Result<...>
_ => Ident::new("op_state", Span::call_site()),
};
// Dark arts 🪄 ✨
//
// - V8 calling convention guarantees that the callback options pointer is non-null.
// - `data` union is always initialized as the `v8::Local<v8::Value>` variant.
// - deno_core guarantees that `data` is a v8 External pointing to an OpCtx for the
// isolate's lifetime.
let prelude = q!(
Vars {
op_state: &op_state
},
{
let __opts: &mut v8::fast_api::FastApiCallbackOptions =
unsafe { &mut *fast_api_callback_options };
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data })
.value() as *const _ops::OpCtx)
};
let op_state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
}
);
pre_transforms.push_tokens(&prelude);
if optimizer.returns_result {
// Magic fallback 🪄
//
// If Result<T, E> is Ok(T), return T as fast value.
//
// Err(E) gets put into `last_fast_op_error` slot and
//
// V8 calls the slow path so we can take the slot
// value and throw.
let result_wrap = q!(Vars { op_state }, {
match result {
Ok(result) => result,
Err(err) => {
op_state.last_fast_op_error.replace(err);
__opts.fallback = true;
Default::default()
}
}
});
output_transforms.push_tokens(&result_wrap);
}
}
if !optimizer.returns_result {
let default_output = q!({ result });
output_transforms.push_tokens(&default_output);
}
let output = q_fast_ty(output_ty);
// Generate the function body.
//
// fn f <S> (_: Local<Object>, a: T, b: U) -> R {
// /* Transforms */
// let a = a.into();
// let b = b.into();
//
// let r = op::call(a, b);
//
// /* Return transform */
// r.into()
// }
let fast_fn = q!(
Vars { core, pre_transforms, op_name_fast: &fast_fn_ident, op_name: &ident, fast_fn_inputs, generics, call_generics: &caller_generics, where_clause, idents, transforms, output_transforms, output: &output },
{
fn op_name_fast generics (_: core::v8::Local<core::v8::Object>, fast_fn_inputs) -> output where_clause {
use core::v8;
use core::_ops;
pre_transforms
transforms
let result = op_name::call call_generics (idents);
output_transforms
}
}
);
let output_variant = q_fast_ty_variant(output_ty);
let mut generics: Generics = parse_quote! { #impl_generics };
generics.where_clause = where_clause.cloned();
// impl <A> fast_api::FastFunction for T <A> where A: B {
// fn function(&self) -> *const ::std::ffi::c_void {
// f as *const ::std::ffi::c_void
// }
// fn args(&self) -> &'static [fast_api::Type] {
// &[ CType::T, CType::U ]
// }
// fn return_type(&self) -> fast_api::CType {
// CType::T
// }
// }
let item: ItemImpl = ItemImpl {
attrs: vec![],
defaultness: None,
unsafety: None,
impl_token: Default::default(),
generics,
trait_: Some((
None,
parse_quote!(#core::v8::fast_api::FastFunction),
Default::default(),
)),
self_ty: Box::new(Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments,
},
})),
brace_token: Default::default(),
items: vec![
parse_quote! {
fn function(&self) -> *const ::std::ffi::c_void {
#fast_fn_ident #caller_generics as *const ::std::ffi::c_void
}
},
parse_quote! {
fn args(&self) -> &'static [#core::v8::fast_api::Type] {
use #core::v8::fast_api::Type::*;
use #core::v8::fast_api::CType;
&[ #input_variants ]
}
},
parse_quote! {
fn return_type(&self) -> #core::v8::fast_api::CType {
#core::v8::fast_api::CType::#output_variant
}
},
],
};
let mut tts = q!({});
tts.push_tokens(&fast_ty);
tts.push_tokens(&item);
tts.push_tokens(&fast_fn);
let impl_and_fn = tts.dump();
let decl = q!(
Vars { fast_ident, caller_generics },
{
Some(Box::new(fast_ident caller_generics { _phantom: ::std::marker::PhantomData }))
}
).dump();
FastImplItems {
impl_and_fn,
decl,
active: true,
}
}
/// Quote fast value type.
fn q_fast_ty(v: &FastValue) -> Quote {
match v {
FastValue::Void => q!({ () }),
FastValue::U32 => q!({ u32 }),
FastValue::I32 => q!({ i32 }),
FastValue::U64 => q!({ u64 }),
FastValue::I64 => q!({ i64 }),
FastValue::F32 => q!({ f32 }),
FastValue::F64 => q!({ f64 }),
FastValue::Bool => q!({ bool }),
FastValue::V8Value => q!({ v8::Local<v8::Value> }),
FastValue::Uint8Array | FastValue::Uint32Array => unreachable!(),
}
}
/// Quote fast value type's variant.
fn q_fast_ty_variant(v: &FastValue) -> Quote {
match v {
FastValue::Void => q!({ Void }),
FastValue::U32 => q!({ Uint32 }),
FastValue::I32 => q!({ Int32 }),
FastValue::U64 => q!({ Uint64 }),
FastValue::I64 => q!({ Int64 }),
FastValue::F32 => q!({ Float32 }),
FastValue::F64 => q!({ Float64 }),
FastValue::Bool => q!({ Bool }),
FastValue::V8Value => q!({ V8Value }),
FastValue::Uint8Array => q!({ TypedArray(CType::Uint8) }),
FastValue::Uint32Array => q!({ TypedArray(CType::Uint32) }),
}
}
fn exclude_lifetime_params(
generic_params: &Punctuated<GenericParam, Comma>,
) -> Option<Generics> {
let params = generic_params
.iter()
.filter(|t| !matches!(t, GenericParam::Lifetime(_)))
.cloned()
.collect::<Punctuated<GenericParam, Comma>>();
if params.is_empty() {
// <()>
return None;
}
Some(Generics {
lt_token: Some(Default::default()),
params,
gt_token: Some(Default::default()),
where_clause: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Op;
use std::path::PathBuf;
#[testing_macros::fixture("optimizer_tests/**/*.rs")]
fn test_fast_call_codegen(input: PathBuf) {
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
let core = crate::deno::import();
let source =
std::fs::read_to_string(&input).expect("Failed to read test file");
let item = syn::parse_str(&source).expect("Failed to parse test file");
let mut op = Op::new(item, Default::default());
let mut optimizer = Optimizer::new();
if optimizer.analyze(&mut op).is_err() {
// Tested by optimizer::test tests.
return;
}
let expected = std::fs::read_to_string(input.with_extension("out"))
.expect("Failed to read expected file");
let FastImplItems {
impl_and_fn: actual,
..
} = generate(&core, &mut optimizer, &op.item);
// Validate syntax tree.
let tree = syn::parse2(actual).unwrap();
let actual = prettyplease::unparse(&tree);
if update_expected {
std::fs::write(input.with_extension("out"), actual)
.expect("Failed to write expected file");
} else {
assert_eq!(actual, expected);
}
}
}

View file

@ -1,189 +1,192 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use core::panic; use attrs::Attributes;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use optimizer::{BailoutReason, Optimizer};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Span; use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens};
use proc_macro_crate::crate_name;
use proc_macro_crate::FoundCrate;
use quote::format_ident;
use quote::quote;
use quote::ToTokens;
use regex::Regex; use regex::Regex;
use std::collections::HashMap; use syn::{
use syn::punctuated::Punctuated; parse, parse_macro_input, punctuated::Punctuated, token::Comma, FnArg,
use syn::token::Comma; GenericParam, Ident, ItemFn, Lifetime, LifetimeDef,
use syn::FnArg; };
use syn::GenericParam;
use syn::Ident; mod attrs;
mod deno;
mod fast_call;
mod optimizer;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
// Identifier to the `deno_core` crate. const SCOPE_LIFETIME: &str = "'scope";
//
// If macro called in deno_core, `crate` is used.
// If macro called outside deno_core, `deno_core` OR the renamed
// version from Cargo.toml is used.
fn core_import() -> TokenStream2 {
let found_crate =
crate_name("deno_core").expect("deno_core not present in `Cargo.toml`");
match found_crate { /// Add the 'scope lifetime to the function signature.
FoundCrate::Itself => { fn add_scope_lifetime(func: &mut ItemFn) {
// TODO(@littledivy): This won't work for `deno_core` examples let span = Span::call_site();
// since `crate` does not refer to `deno_core`. let lifetime = LifetimeDef::new(Lifetime::new(SCOPE_LIFETIME, span));
// examples must re-export deno_core to make this work let generics = &mut func.sig.generics;
// until Span inspection APIs are stabalized. if !generics.lifetimes().any(|def| *def == lifetime) {
// generics.params.push(GenericParam::Lifetime(lifetime));
// https://github.com/rust-lang/rust/issues/54725
quote!(crate)
}
FoundCrate::Name(name) => {
let ident = Ident::new(&name, Span::call_site());
quote!(#ident)
}
} }
} }
#[derive(Copy, Clone, Debug, Default)] struct Op {
struct MacroArgs { orig: ItemFn,
is_unstable: bool, item: ItemFn,
is_v8: bool, /// Is this an async op?
must_be_fast: bool, /// - `async fn`
deferred: bool, /// - returns a Future
is_async: bool,
type_params: Punctuated<GenericParam, Comma>,
// optimizer: Optimizer,
core: TokenStream2,
attrs: Attributes,
} }
impl syn::parse::Parse for MacroArgs { impl Op {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn new(mut item: ItemFn, attrs: Attributes) -> Self {
let vars = add_scope_lifetime(&mut item);
syn::punctuated::Punctuated::<Ident, syn::Token![,]>::parse_terminated(
input, // Preserve the original function. Change the name to `call`.
)?; //
let vars: Vec<_> = vars.iter().map(Ident::to_string).collect(); // impl op_foo {
let vars: Vec<_> = vars.iter().map(String::as_str).collect(); // fn call() {}
for var in vars.iter() { // ...
if !["unstable", "v8", "fast", "deferred"].contains(var) { // }
return Err(syn::Error::new( let mut orig = item.clone();
input.span(), orig.sig.ident = Ident::new("call", Span::call_site());
"Ops expect #[op] or #[op(unstable)]",
)); let is_async = item.sig.asyncness.is_some() || is_future(&item.sig.output);
} let type_params = exclude_lifetime_params(&item.sig.generics.params);
let core = deno::import();
Self {
orig,
item,
type_params,
is_async,
core,
attrs,
}
}
fn gen(mut self) -> TokenStream2 {
let mut optimizer = Optimizer::new();
match optimizer.analyze(&mut self) {
Ok(_) | Err(BailoutReason::MustBeSingleSegment) => {}
Err(BailoutReason::FastUnsupportedParamType) => {
optimizer.fast_compatible = false;
}
Err(err) => return quote!(compile_error!(#err);),
};
let Self {
core,
item,
is_async,
orig,
attrs,
type_params,
} = self;
let name = &item.sig.ident;
let generics = &item.sig.generics;
let where_clause = &item.sig.generics.where_clause;
// First generate fast call bindings to opt-in to error handling in slow call
let fast_call::FastImplItems {
impl_and_fn,
decl,
active,
} = fast_call::generate(&core, &mut optimizer, &item);
let has_fallible_fast_call = active && optimizer.returns_result;
let (v8_body, argc) = if is_async {
codegen_v8_async(
&core,
&item,
attrs,
item.sig.asyncness.is_some(),
attrs.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)]
#[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;
Self::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: #argc,
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
#orig
pub fn v8_func #generics (
scope: &mut #core::v8::HandleScope<'scope>,
args: #core::v8::FunctionCallbackArguments,
mut rv: #core::v8::ReturnValue,
) #where_clause {
#v8_body
}
}
#impl_and_fn
} }
Ok(Self {
is_unstable: vars.contains(&"unstable"),
is_v8: vars.contains(&"v8"),
must_be_fast: vars.contains(&"fast"),
deferred: vars.contains(&"deferred"),
})
} }
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
let margs = syn::parse_macro_input!(attr as MacroArgs); let margs = parse_macro_input!(attr as Attributes);
let MacroArgs { let func = parse::<ItemFn>(item).expect("expected a function");
is_unstable, let op = Op::new(func, margs);
is_v8, op.gen().into()
must_be_fast,
deferred,
} = margs;
let func = syn::parse::<syn::ItemFn>(item).expect("expected a function");
let name = &func.sig.ident;
let mut generics = func.sig.generics.clone();
let scope_lifetime =
syn::LifetimeDef::new(syn::Lifetime::new("'scope", Span::call_site()));
if !generics.lifetimes().any(|def| *def == scope_lifetime) {
generics
.params
.push(syn::GenericParam::Lifetime(scope_lifetime));
}
let type_params = exclude_lifetime_params(&func.sig.generics.params);
let where_clause = &func.sig.generics.where_clause;
// Preserve the original func as op_foo::call()
let original_func = {
let mut func = func.clone();
func.sig.ident = quote::format_ident!("call");
func
};
let core = core_import();
let asyncness = func.sig.asyncness.is_some();
let is_async = asyncness || is_future(&func.sig.output);
// First generate fast call bindings to opt-in to error handling in slow call
let (has_fallible_fast_call, fast_impl, fast_field) =
codegen_fast_impl(&core, &func, name, is_async, must_be_fast);
let (v8_body, argc) = if is_async {
codegen_v8_async(&core, &func, margs, asyncness, deferred)
} else {
codegen_v8_sync(&core, &func, margs, has_fallible_fast_call)
};
let docline = format!("Use `{name}::decl()` to get an op-declaration");
// Generate wrapper
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;
Self::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: #fast_field,
is_async: #is_async,
is_unstable: #is_unstable,
is_v8: #is_v8,
argc: #argc,
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
#original_func
pub fn v8_func #generics (
scope: &mut #core::v8::HandleScope<'scope>,
args: #core::v8::FunctionCallbackArguments,
mut rv: #core::v8::ReturnValue,
) #where_clause {
#v8_body
}
}
#fast_impl
}.into()
} }
/// Generate the body of a v8 func for an async op /// Generate the body of a v8 func for an async op
fn codegen_v8_async( fn codegen_v8_async(
core: &TokenStream2, core: &TokenStream2,
f: &syn::ItemFn, f: &syn::ItemFn,
margs: MacroArgs, margs: Attributes,
asyncness: bool, asyncness: bool,
deferred: bool, deferred: bool,
) -> (TokenStream2, usize) { ) -> (TokenStream2, usize) {
let MacroArgs { is_v8, .. } = margs; let Attributes { is_v8, .. } = margs;
let special_args = f let special_args = f
.sig .sig
.inputs .inputs
@ -287,241 +290,14 @@ fn opstate_arg(arg: &FnArg) -> Option<TokenStream2> {
} }
} }
fn codegen_fast_impl(
core: &TokenStream2,
f: &syn::ItemFn,
name: &syn::Ident,
is_async: bool,
must_be_fast: bool,
) -> (bool, TokenStream2, TokenStream2) {
if is_async {
if must_be_fast {
panic!("async op cannot be a fast api. enforced by #[op(fast)]")
}
return (false, quote! {}, quote! { None });
}
let fast_info = can_be_fast_api(core, f);
if must_be_fast && fast_info.is_none() {
panic!("op cannot be a fast api. enforced by #[op(fast)]")
}
if !is_async {
if let Some(FastApiSyn {
args,
ret,
use_op_state,
use_fast_cb_opts,
v8_values,
returns_result,
slices,
}) = fast_info
{
let offset = if use_op_state { 1 } else { 0 };
let mut inputs = f
.sig
.inputs
.iter()
.skip(offset)
.enumerate()
.map(|(idx, arg)| {
let ident = match arg {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(t) => match &*t.pat {
syn::Pat::Ident(i) => format_ident!("{}", i.ident),
_ => unreachable!(),
},
};
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 > };
}
quote!(#arg)
})
.collect::<Vec<_>>();
if (!slices.is_empty() || use_op_state || returns_result)
&& !use_fast_cb_opts
{
inputs.push(quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions });
}
let input_idents = f
.sig
.inputs
.iter()
.enumerate()
.map(|(idx, a)| {
let ident = match a {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(t) => match &*t.pat {
syn::Pat::Ident(i) => format_ident!("{}", i.ident),
_ => 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 * fast_api_callback_options }) };
}
if v8_values.contains(&idx) {
return quote! {
#core::serde_v8::Value {
v8_value: #ident,
}
};
}
quote! { #ident }
})
.collect::<Vec<_>>();
let generics = &f.sig.generics;
let (impl_generics, ty_generics, where_clause) =
generics.split_for_impl();
let type_params = exclude_lifetime_params(&f.sig.generics.params);
let (trampoline, raw_block) = if is_async {
// TODO(@littledivy): Fast async calls.
(
quote! {
fn func(recv: #core::v8::Local<#core::v8::Object>, __promise_id: u32, #(#inputs),*) {
// SAFETY: V8 calling convention guarantees that the callback options pointer is non-null.
let opts: &#core::v8::fast_api::FastApiCallbackOptions = unsafe { &*fast_api_callback_options };
// SAFETY: data union is always created as the `v8::Local<v8::Value>` version
let data = unsafe { opts.data.data };
// SAFETY: #core guarantees data is a v8 External pointing to an OpCtx for the isolates lifetime
let ctx = unsafe {
&*(#core::v8::Local::<#core::v8::External>::cast(data).value()
as *const #core::_ops::OpCtx)
};
let op_id = ctx.op_id;
#core::_ops::queue_async_op(scope, async move {
let result = Self::call(#args);
(__promise_id, __op_id, #core::_ops::OpResult::Ok(result))
});
}
func as *const _
},
quote! {},
)
} else {
let output = if returns_result {
get_fast_result_return_type(&f.sig.output)
} else {
let output = &f.sig.output;
quote! { #output }
};
let func_name = format_ident!("func_{}", name);
let op_state_name = if use_op_state {
input_idents.first().unwrap().clone()
} else {
quote! { op_state }
};
let recv_decl = if use_op_state || returns_result {
quote! {
// SAFETY: V8 calling convention guarantees that the callback options pointer is non-null.
let opts: &mut #core::v8::fast_api::FastApiCallbackOptions = unsafe { &mut *fast_api_callback_options };
// SAFETY: data union is always created as the `v8::Local<v8::Value>` version.
let data = unsafe { opts.data.data };
// SAFETY: #core guarantees data is a v8 External pointing to an OpCtx for the isolates lifetime
let ctx = unsafe {
&*(#core::v8::Local::<#core::v8::External>::cast(data).value()
as *const #core::_ops::OpCtx)
};
let #op_state_name = &mut std::cell::RefCell::borrow_mut(&ctx.state);
}
} else {
quote! {}
};
let result_handling = if returns_result {
quote! {
match result {
Ok(result) => {
result
},
Err(err) => {
#op_state_name.last_fast_op_error.replace(err);
opts.fallback = true;
Default::default()
},
}
}
} else {
quote! { result }
};
(
quote! {
fn #func_name #generics (_recv: #core::v8::Local<#core::v8::Object>, #(#inputs),*) #output #where_clause {
#recv_decl
let result = #name::call::<#type_params>(#(#input_idents),*);
#result_handling
}
},
quote! {
#func_name::<#type_params> as *const _
},
)
};
let fast_struct = format_ident!("fast_{}", name);
let (type_params, ty_generics, struct_generics) =
if type_params.is_empty() {
(quote! { () }, quote! {}, quote! {})
} else {
(
quote! { #type_params },
quote! { #ty_generics },
quote! { ::<#type_params> },
)
};
return (
returns_result,
quote! {
#[allow(non_camel_case_types)]
#[doc(hidden)]
struct #fast_struct #ty_generics {
_phantom: ::std::marker::PhantomData<#type_params>,
}
#trampoline
impl #impl_generics #core::v8::fast_api::FastFunction for #fast_struct #ty_generics #where_clause {
fn function(&self) -> *const ::std::ffi::c_void {
#raw_block
}
fn args(&self) -> &'static [#core::v8::fast_api::Type] {
&[ #args ]
}
fn return_type(&self) -> #core::v8::fast_api::CType {
#ret
}
}
},
quote! { Some(Box::new(#fast_struct #struct_generics { _phantom: ::std::marker::PhantomData })) },
);
}
}
// Default impl to satisfy generic bounds for non-fast ops
(false, quote! {}, quote! { None })
}
/// Generate the body of a v8 func for a sync op /// Generate the body of a v8 func for a sync op
fn codegen_v8_sync( fn codegen_v8_sync(
core: &TokenStream2, core: &TokenStream2,
f: &syn::ItemFn, f: &syn::ItemFn,
margs: MacroArgs, margs: Attributes,
has_fallible_fast_call: bool, has_fallible_fast_call: bool,
) -> (TokenStream2, usize) { ) -> (TokenStream2, usize) {
let MacroArgs { is_v8, .. } = margs; let Attributes { is_v8, .. } = margs;
let special_args = f let special_args = f
.sig .sig
.inputs .inputs
@ -574,242 +350,6 @@ fn codegen_v8_sync(
) )
} }
struct FastApiSyn {
args: TokenStream2,
ret: TokenStream2,
use_op_state: bool,
use_fast_cb_opts: bool,
v8_values: Vec<usize>,
returns_result: bool,
slices: HashMap<usize, TokenStream2>,
}
fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
let inputs = &f.sig.inputs;
let mut returns_result = false;
let ret = match &f.sig.output {
syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void),
syn::ReturnType::Type(_, ty) => match is_fast_return_type(core, ty) {
Some((ret, is_result)) => {
returns_result = is_result;
ret
}
None => return None,
},
};
let mut use_op_state = 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) {
use_fast_cb_opts = true;
continue;
}
if pos == 0 && is_mut_ref_opstate(input) {
use_op_state = true;
continue;
}
let ty = match input {
syn::FnArg::Typed(pat) => &pat.ty,
_ => unreachable!(),
};
if let Some(arg) = is_fast_v8_value(core, ty) {
args.push(arg);
v8_values.push(pos);
} else {
match is_fast_scalar(core, ty, false) {
None => match is_fast_arg_sequence(core, ty) {
Some(arg) => {
args.push(arg);
}
None => match is_ref_slice(&ty) {
Some(SliceType::U32Mut) => {
args.push(quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint32) });
slices.insert(pos, quote!(u32));
}
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);
}
}
}
}
if use_fast_cb_opts || use_op_state {
// Push CallbackOptions into args; it must be the last argument.
args.push(quote! { #core::v8::fast_api::Type::CallbackOptions });
}
let args = args
.iter()
.map(|arg| format!("{}", arg))
.collect::<Vec<_>>()
.join(", ");
Some(FastApiSyn {
args: args.parse().unwrap(),
ret,
use_op_state,
slices,
v8_values,
use_fast_cb_opts,
returns_result,
})
}
// A v8::Local<v8::Array> or FastApiTypedArray<T>
fn is_fast_arg_sequence(
core: &TokenStream2,
ty: impl ToTokens,
) -> Option<TokenStream2> {
// TODO(@littledivy): Make `v8::` parts optional.
if is_fast_typed_array(&ty) {
return Some(
quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint32) },
);
}
if is_local_array(&ty) {
return Some(
quote! { #core::v8::fast_api::Type::Sequence(#core::v8::fast_api::CType::Void) },
);
}
None
}
fn is_fast_v8_value(
core: &TokenStream2,
arg: impl ToTokens,
) -> Option<TokenStream2> {
if tokens(&arg).contains("serde_v8 :: Value") {
return Some(quote! { #core::v8::fast_api::Type::V8Value });
}
None
}
fn is_local_array(arg: impl ToTokens) -> bool {
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^v8::Local<v8::Array>$").unwrap());
RE.is_match(&tokens(arg))
}
fn is_fast_typed_array(arg: impl ToTokens) -> bool {
static RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#": (?:deno_core :: )?FastApiTypedArray$"#).unwrap()
});
RE.is_match(&tokens(arg))
}
fn is_fast_return_type(
core: &TokenStream2,
ty: impl ToTokens,
) -> Option<(TokenStream2, bool)> {
if is_result(&ty) {
if tokens(&ty).contains("Result < u32") || is_resource_id(&ty) {
Some((quote! { #core::v8::fast_api::CType::Uint32 }, true))
} else if tokens(&ty).contains("Result < i32") {
Some((quote! { #core::v8::fast_api::CType::Int32 }, true))
} else if tokens(&ty).contains("Result < f32") {
Some((quote! { #core::v8::fast_api::CType::Float32 }, true))
} else if tokens(&ty).contains("Result < f64") {
Some((quote! { #core::v8::fast_api::CType::Float64 }, true))
} else if tokens(&ty).contains("Result < bool") {
Some((quote! { #core::v8::fast_api::CType::Bool }, true))
} else if tokens(&ty).contains("Result < ()") {
Some((quote! { #core::v8::fast_api::CType::Void }, true))
} else {
None
}
} else {
is_fast_scalar(core, ty, true).map(|s| (s, false))
}
}
fn get_fast_result_return_type(ty: impl ToTokens) -> TokenStream2 {
if tokens(&ty).contains("Result < u32") || is_resource_id(&ty) {
quote! { -> u32 }
} else if tokens(&ty).contains("Result < i32") {
quote! { -> i32 }
} else if tokens(&ty).contains("Result < f32") {
quote! { -> f32 }
} else if tokens(&ty).contains("Result < f64") {
quote! { -> f64 }
} else if tokens(&ty).contains("Result < bool") {
quote! { -> bool }
} else if tokens(&ty).contains("Result < ()") {
quote! {}
} else {
unreachable!()
}
}
fn is_fast_scalar(
core: &TokenStream2,
ty: impl ToTokens,
is_ret: bool,
) -> Option<TokenStream2> {
let cty = if is_ret {
quote! { CType }
} else {
quote! { Type }
};
if is_resource_id(&ty) {
return Some(quote! { #core::v8::fast_api::#cty::Uint32 });
}
if is_void(&ty) {
return Some(quote! { #core::v8::fast_api::#cty::Void });
}
// TODO(@littledivy): Support u8, i8, u16, i16 by casting.
match tokens(&ty).as_str() {
"u32" => Some(quote! { #core::v8::fast_api::#cty::Uint32 }),
"i32" => Some(quote! { #core::v8::fast_api::#cty::Int32 }),
"u64" => {
if is_ret {
None
} else {
Some(quote! { #core::v8::fast_api::#cty::Uint64 })
}
}
"i64" => {
if is_ret {
None
} else {
Some(quote! { #core::v8::fast_api::#cty::Int64 })
}
}
// TODO(@aapoalas): Support 32 bit machines
"usize" => {
if is_ret {
None
} else {
Some(quote! { #core::v8::fast_api::#cty::Uint64 })
}
}
"isize" => {
if is_ret {
None
} else {
Some(quote! { #core::v8::fast_api::#cty::Int64 })
}
}
"f32" => Some(quote! { #core::v8::fast_api::#cty::Float32 }),
"f64" => Some(quote! { #core::v8::fast_api::#cty::Float64 }),
"bool" => Some(quote! { #core::v8::fast_api::#cty::Bool }),
_ => None,
}
}
/// (full declarations, idents, v8 argument count) /// (full declarations, idents, v8 argument count)
type ArgumentDecl = (TokenStream2, TokenStream2, usize); type ArgumentDecl = (TokenStream2, TokenStream2, usize);

600
ops/optimizer.rs Normal file
View file

@ -0,0 +1,600 @@
/// Optimizer for #[op]
use crate::Op;
use pmutil::{q, Quote};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Formatter;
use syn::{
parse_quote, punctuated::Punctuated, token::Colon2,
AngleBracketedGenericArguments, FnArg, GenericArgument, PatType, Path,
PathArguments, PathSegment, ReturnType, Signature, Type, TypePath,
TypeReference, TypeSlice,
};
#[derive(Debug)]
pub(crate) enum BailoutReason {
// Recoverable errors
MustBeSingleSegment,
FastUnsupportedParamType,
FastAsync,
}
impl ToTokens for BailoutReason {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
BailoutReason::FastAsync => {
tokens.extend(quote! { "fast async calls are not supported" });
}
BailoutReason::MustBeSingleSegment
| BailoutReason::FastUnsupportedParamType => {
unreachable!("error not recovered");
}
}
}
}
#[derive(Debug, PartialEq)]
enum TransformKind {
// serde_v8::Value
V8Value,
SliceU32(bool),
SliceU8(bool),
}
impl Transform {
fn serde_v8_value(index: usize) -> Self {
Transform {
kind: TransformKind::V8Value,
index,
}
}
fn slice_u32(index: usize, is_mut: bool) -> Self {
Transform {
kind: TransformKind::SliceU32(is_mut),
index,
}
}
fn slice_u8(index: usize, is_mut: bool) -> Self {
Transform {
kind: TransformKind::SliceU8(is_mut),
index,
}
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct Transform {
kind: TransformKind,
index: usize,
}
impl Transform {
pub(crate) fn apply_for_fast_call(
&self,
core: &TokenStream,
input: &mut FnArg,
) -> Quote {
let (ty, ident) = match input {
FnArg::Typed(PatType {
ref mut ty,
ref pat,
..
}) => {
let ident = match &**pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => unreachable!("error not recovered"),
};
(ty, ident)
}
_ => unreachable!("error not recovered"),
};
match &self.kind {
// serde_v8::Value
TransformKind::V8Value => {
*ty = parse_quote! { #core::v8::Local<v8::Value> };
q!(Vars { var: &ident }, {
let var = serde_v8::Value { v8_value: var };
})
}
// &[u32]
TransformKind::SliceU32(_) => {
*ty =
parse_quote! { *const #core::v8::fast_api::FastApiTypedArray<u32> };
q!(Vars { var: &ident }, {
let var = match unsafe { &*var }.get_storage_if_aligned() {
Some(v) => v,
None => {
unsafe { &mut *fast_api_callback_options }.fallback = true;
return Default::default();
}
};
})
}
// &[u8]
TransformKind::SliceU8(_) => {
*ty =
parse_quote! { *const #core::v8::fast_api::FastApiTypedArray<u8> };
q!(Vars { var: &ident }, {
let var = match unsafe { &*var }.get_storage_if_aligned() {
Some(v) => v,
None => {
unsafe { &mut *fast_api_callback_options }.fallback = true;
return Default::default();
}
};
})
}
}
}
}
fn get_fast_scalar(s: &str) -> Option<FastValue> {
match s {
"u32" => Some(FastValue::U32),
"i32" => Some(FastValue::I32),
"u64" => Some(FastValue::U64),
"i64" => Some(FastValue::I64),
"f32" => Some(FastValue::F32),
"f64" => Some(FastValue::F64),
"bool" => Some(FastValue::Bool),
"ResourceId" => Some(FastValue::U32),
_ => None,
}
}
fn can_return_fast(v: &FastValue) -> bool {
!matches!(
v,
FastValue::U64
| FastValue::I64
| FastValue::Uint8Array
| FastValue::Uint32Array
)
}
#[derive(Debug, PartialEq, Clone)]
pub(crate) enum FastValue {
Void,
U32,
I32,
U64,
I64,
F32,
F64,
Bool,
V8Value,
Uint8Array,
Uint32Array,
}
impl Default for FastValue {
fn default() -> Self {
Self::Void
}
}
#[derive(Default, PartialEq)]
pub(crate) struct Optimizer {
pub(crate) returns_result: bool,
pub(crate) has_ref_opstate: bool,
pub(crate) has_rc_opstate: bool,
pub(crate) has_fast_callback_option: bool,
pub(crate) fast_result: Option<FastValue>,
pub(crate) fast_parameters: Vec<FastValue>,
pub(crate) transforms: HashMap<usize, Transform>,
pub(crate) fast_compatible: bool,
}
impl Debug for Optimizer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "=== Optimizer Dump ===")?;
writeln!(f, "returns_result: {}", self.returns_result)?;
writeln!(f, "has_ref_opstate: {}", self.has_ref_opstate)?;
writeln!(f, "has_rc_opstate: {}", self.has_rc_opstate)?;
writeln!(
f,
"has_fast_callback_option: {}",
self.has_fast_callback_option
)?;
writeln!(f, "fast_result: {:?}", self.fast_result)?;
writeln!(f, "fast_parameters: {:?}", self.fast_parameters)?;
writeln!(f, "transforms: {:?}", self.transforms)?;
Ok(())
}
}
impl Optimizer {
pub(crate) fn new() -> Self {
Default::default()
}
pub(crate) const fn has_opstate_in_parameters(&self) -> bool {
self.has_ref_opstate || self.has_rc_opstate
}
pub(crate) const fn needs_opstate(&self) -> bool {
self.has_ref_opstate || self.has_rc_opstate || self.returns_result
}
pub(crate) fn analyze(&mut self, op: &mut Op) -> Result<(), BailoutReason> {
if op.is_async && op.attrs.must_be_fast {
self.fast_compatible = false;
return Err(BailoutReason::FastAsync);
}
if op.attrs.is_v8 || op.is_async {
self.fast_compatible = false;
return Ok(());
}
self.fast_compatible = true;
let sig = &op.item.sig;
// Analyze return type
match &sig {
Signature {
output: ReturnType::Default,
..
} => self.fast_result = Some(FastValue::default()),
Signature {
output: ReturnType::Type(_, ty),
..
} => self.analyze_return_type(ty)?,
};
// The reciever, which we don't actually care about.
self.fast_parameters.push(FastValue::V8Value);
// Analyze parameters
for (index, param) in sig.inputs.iter().enumerate() {
self.analyze_param_type(index, param)?;
}
Ok(())
}
fn analyze_return_type(&mut self, ty: &Type) -> Result<(), BailoutReason> {
match ty {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
let segment = single_segment(segments)?;
match segment {
// Result<T, E>
PathSegment {
ident, arguments, ..
} if ident == "Result" => {
self.returns_result = true;
if let PathArguments::AngleBracketed(
AngleBracketedGenericArguments { args, .. },
) = arguments
{
match args.first() {
Some(GenericArgument::Type(Type::Path(TypePath {
path: Path { segments, .. },
..
}))) => {
let PathSegment { ident, .. } = single_segment(segments)?;
// Is `T` a scalar FastValue?
if let Some(val) = get_fast_scalar(ident.to_string().as_str())
{
if can_return_fast(&val) {
self.fast_result = Some(val);
return Ok(());
}
}
self.fast_compatible = false;
return Err(BailoutReason::FastUnsupportedParamType);
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
}
// Is `T` a scalar FastValue?
PathSegment { ident, .. } => {
if let Some(val) = get_fast_scalar(ident.to_string().as_str()) {
self.fast_result = Some(val);
return Ok(());
}
self.fast_compatible = false;
return Err(BailoutReason::FastUnsupportedParamType);
}
};
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
};
Ok(())
}
fn analyze_param_type(
&mut self,
index: usize,
arg: &FnArg,
) -> Result<(), BailoutReason> {
match arg {
FnArg::Typed(typed) => match &*typed.ty {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) if segments.len() == 2 => {
match double_segment(segments)? {
// -> serde_v8::Value
[PathSegment { ident: first, .. }, PathSegment { ident: last, .. }]
if first == "serde_v8" && last == "Value" =>
{
self.fast_parameters.push(FastValue::V8Value);
assert!(self
.transforms
.insert(index, Transform::serde_v8_value(index))
.is_none());
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
let segment = single_segment(segments)?;
match segment {
// -> Option<T>
PathSegment {
ident, arguments, ..
} if ident == "Option" => {
if let PathArguments::AngleBracketed(
AngleBracketedGenericArguments { args, .. },
) = arguments
{
// -> Option<&mut T>
if let Some(GenericArgument::Type(Type::Reference(
TypeReference { elem, .. },
))) = args.last()
{
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = &**elem
{
let segment = single_segment(segments)?;
match segment {
// Is `T` a FastApiCallbackOption?
PathSegment { ident, .. }
if ident == "FastApiCallbackOption" =>
{
self.has_fast_callback_option = true;
}
_ => {}
}
}
}
}
}
// -> Rc<T>
PathSegment {
ident, arguments, ..
} if ident == "Rc" => {
if let PathArguments::AngleBracketed(
AngleBracketedGenericArguments { args, .. },
) = arguments
{
match args.last() {
Some(GenericArgument::Type(Type::Path(TypePath {
path: Path { segments, .. },
..
}))) => {
let segment = single_segment(segments)?;
match segment {
// -> Rc<RefCell<T>>
PathSegment { ident, .. } if ident == "RefCell" => {
if let PathArguments::AngleBracketed(
AngleBracketedGenericArguments { args, .. },
) = arguments
{
match args.last() {
// -> Rc<RefCell<OpState>>
Some(GenericArgument::Type(Type::Path(
TypePath {
path: Path { segments, .. },
..
},
))) => {
let segment = single_segment(segments)?;
match segment {
PathSegment { ident, .. }
if ident == "OpState" =>
{
self.has_rc_opstate = true;
}
_ => {
return Err(
BailoutReason::FastUnsupportedParamType,
)
}
}
}
_ => {
return Err(
BailoutReason::FastUnsupportedParamType,
)
}
}
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
}
// Is `T` a fast scalar?
PathSegment { ident, .. } => {
if let Some(val) = get_fast_scalar(ident.to_string().as_str()) {
self.fast_parameters.push(val);
} else {
return Err(BailoutReason::FastUnsupportedParamType);
}
}
};
}
// &mut T
Type::Reference(TypeReference {
elem, mutability, ..
}) => match &**elem {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
let segment = single_segment(segments)?;
match segment {
// Is `T` a OpState?
PathSegment { ident, .. } if ident == "OpState" => {
self.has_ref_opstate = true;
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
// &mut [T]
Type::Slice(TypeSlice { elem, .. }) => match &**elem {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => {
let segment = single_segment(segments)?;
let is_mut_ref = mutability.is_some();
match segment {
// Is `T` a u8?
PathSegment { ident, .. } if ident == "u8" => {
self.has_fast_callback_option = true;
self.fast_parameters.push(FastValue::Uint8Array);
assert!(self
.transforms
.insert(index, Transform::slice_u8(index, is_mut_ref))
.is_none());
}
// Is `T` a u32?
PathSegment { ident, .. } if ident == "u32" => {
self.has_fast_callback_option = true;
self.fast_parameters.push(FastValue::Uint32Array);
assert!(self
.transforms
.insert(index, Transform::slice_u32(index, is_mut_ref))
.is_none());
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
}
}
_ => return Err(BailoutReason::FastUnsupportedParamType),
},
_ => return Err(BailoutReason::FastUnsupportedParamType),
},
_ => return Err(BailoutReason::FastUnsupportedParamType),
},
_ => return Err(BailoutReason::FastUnsupportedParamType),
};
Ok(())
}
}
fn single_segment(
segments: &Punctuated<PathSegment, Colon2>,
) -> Result<&PathSegment, BailoutReason> {
if segments.len() != 1 {
return Err(BailoutReason::MustBeSingleSegment);
}
match segments.last() {
Some(segment) => Ok(segment),
None => Err(BailoutReason::MustBeSingleSegment),
}
}
fn double_segment(
segments: &Punctuated<PathSegment, Colon2>,
) -> Result<[&PathSegment; 2], BailoutReason> {
match (segments.first(), segments.last()) {
(Some(first), Some(last)) => Ok([first, last]),
// Caller ensures that there are only two segments.
_ => unreachable!(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Op;
use std::path::PathBuf;
use syn::parse_quote;
#[test]
fn test_single_segment() {
let segments = parse_quote!(foo);
assert!(single_segment(&segments).is_ok());
let segments = parse_quote!(foo::bar);
assert!(single_segment(&segments).is_err());
}
#[test]
fn test_double_segment() {
let segments = parse_quote!(foo::bar);
assert!(double_segment(&segments).is_ok());
assert_eq!(double_segment(&segments).unwrap()[0].ident, "foo");
assert_eq!(double_segment(&segments).unwrap()[1].ident, "bar");
}
#[testing_macros::fixture("optimizer_tests/**/*.rs")]
fn test_analyzer(input: PathBuf) {
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();
let source =
std::fs::read_to_string(&input).expect("Failed to read test file");
let expected = std::fs::read_to_string(input.with_extension("expected"))
.expect("Failed to read expected file");
let item = syn::parse_str(&source).expect("Failed to parse test file");
let mut op = Op::new(item, Default::default());
let mut optimizer = Optimizer::new();
if let Err(e) = optimizer.analyze(&mut op) {
let e_str = format!("{:?}", e);
if update_expected {
std::fs::write(input.with_extension("expected"), e_str)
.expect("Failed to write expected file");
} else {
assert_eq!(e_str, expected);
}
return;
}
if update_expected {
std::fs::write(
input.with_extension("expected"),
format!("{:#?}", optimizer),
)
.expect("Failed to write expected file");
} else {
assert_eq!(format!("{:#?}", optimizer), expected);
}
}
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: false
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(Void)
fast_parameters: [V8Value]
transforms: {}

View file

@ -0,0 +1,25 @@
struct op_fallback_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for op_fallback_fast {
fn function(&self) -> *const ::std::ffi::c_void {
op_fallback_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Void
}
}
fn op_fallback_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
options: Option<&mut FastApiCallbackOptions>,
) -> () {
use deno_core::v8;
use deno_core::_ops;
let result = op_fallback::call(options);
result
}

View file

@ -0,0 +1,5 @@
fn op_fallback(options: Option<&mut FastApiCallbackOptions>) {
if let Some(options) = options {
options.fallback = true;
}
}

View file

@ -0,0 +1 @@
FastUnsupportedParamType

View file

@ -0,0 +1,9 @@
fn op_sync_serialize_object_with_numbers_as_keys(
value: serde_json::Value,
) -> Result<(), Error> {
assert_eq!(
value.to_string(),
r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"#
);
Ok(())
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: true
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(Void)
fast_parameters: [V8Value, I32]
transforms: {}

View file

@ -0,0 +1,34 @@
struct op_set_exit_code_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for op_set_exit_code_fast {
fn function(&self) -> *const ::std::ffi::c_void {
op_set_exit_code_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, Int32, CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Void
}
}
fn op_set_exit_code_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
code: i32,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> () {
use deno_core::v8;
use deno_core::_ops;
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
&mut *fast_api_callback_options
};
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
as *const _ops::OpCtx)
};
let state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
let result = op_set_exit_code::call(state, code);
result
}

View file

@ -0,0 +1,3 @@
fn op_set_exit_code(state: &mut OpState, code: i32) {
state.borrow_mut::<ExitCode>().set(code);
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: true
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(U32)
fast_parameters: [V8Value, U32, U32]
transforms: {}

View file

@ -0,0 +1,35 @@
struct foo_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for foo_fast {
fn function(&self) -> *const ::std::ffi::c_void {
foo_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, Uint32, Uint32, CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Uint32
}
}
fn foo_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
a: u32,
b: u32,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> u32 {
use deno_core::v8;
use deno_core::_ops;
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
&mut *fast_api_callback_options
};
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
as *const _ops::OpCtx)
};
let state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
let result = foo::call(state, a, b);
result
}

View file

@ -0,0 +1,3 @@
fn foo(state: &mut OpState, a: u32, b: u32) -> u32 {
a + b
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: true
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(Void)
fast_parameters: [V8Value]
transforms: {}

View file

@ -0,0 +1,39 @@
struct op_foo_fast<SP> {
_phantom: ::std::marker::PhantomData<SP>,
}
impl<'scope, SP> deno_core::v8::fast_api::FastFunction for op_foo_fast<SP>
where
SP: SomePermission + 'static,
{
fn function(&self) -> *const ::std::ffi::c_void {
op_foo_fast_fn::<SP> as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Void
}
}
fn op_foo_fast_fn<'scope, SP>(
_: deno_core::v8::Local<deno_core::v8::Object>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> ()
where
SP: SomePermission + 'static,
{
use deno_core::v8;
use deno_core::_ops;
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
&mut *fast_api_callback_options
};
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
as *const _ops::OpCtx)
};
let state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
let result = op_foo::call::<SP>(state);
result
}

View file

@ -0,0 +1,5 @@
pub fn op_foo<SP>(state: &mut OpState)
where
SP: SomePermission + 'static,
{
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: true
has_ref_opstate: true
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(U32)
fast_parameters: [V8Value, U32, U32]
transforms: {}

View file

@ -0,0 +1,42 @@
struct foo_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for foo_fast {
fn function(&self) -> *const ::std::ffi::c_void {
foo_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, Uint32, Uint32, CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Uint32
}
}
fn foo_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
a: u32,
b: u32,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> u32 {
use deno_core::v8;
use deno_core::_ops;
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
&mut *fast_api_callback_options
};
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
as *const _ops::OpCtx)
};
let state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
let result = foo::call(state, a, b);
match result {
Ok(result) => result,
Err(err) => {
state.last_fast_op_error.replace(err);
__opts.fallback = true;
Default::default()
}
}
}

View file

@ -0,0 +1,3 @@
fn foo(state: &mut OpState, a: u32, b: u32) -> Result<u32, AnyError> {
Ok(a + b)
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: true
has_rc_opstate: false
has_fast_callback_option: true
fast_result: Some(Void)
fast_parameters: [V8Value, Uint8Array]
transforms: {1: Transform { kind: SliceU8(true), index: 1 }}

View file

@ -0,0 +1,47 @@
struct op_now_fast<TP> {
_phantom: ::std::marker::PhantomData<TP>,
}
impl<'scope, TP> deno_core::v8::fast_api::FastFunction for op_now_fast<TP>
where
TP: TimersPermission + 'static,
{
fn function(&self) -> *const ::std::ffi::c_void {
op_now_fast_fn::<TP> as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, TypedArray(CType::Uint8), CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Void
}
}
fn op_now_fast_fn<'scope, TP>(
_: deno_core::v8::Local<deno_core::v8::Object>,
buf: *const deno_core::v8::fast_api::FastApiTypedArray<u8>,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> ()
where
TP: TimersPermission + 'static,
{
use deno_core::v8;
use deno_core::_ops;
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
&mut *fast_api_callback_options
};
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
as *const _ops::OpCtx)
};
let state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
let buf = match unsafe { &*buf }.get_storage_if_aligned() {
Some(v) => v,
None => {
unsafe { &mut *fast_api_callback_options }.fallback = true;
return Default::default();
}
};
let result = op_now::call::<TP>(state, buf);
result
}

View file

@ -0,0 +1,5 @@
pub fn op_now<TP>(state: &mut OpState, buf: &mut [u8])
where
TP: TimersPermission + 'static,
{
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: true
has_ref_opstate: false
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(U32)
fast_parameters: [V8Value, U32, U32, U32, U32]
transforms: {}

View file

@ -0,0 +1,44 @@
struct op_add_4_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for op_add_4_fast {
fn function(&self) -> *const ::std::ffi::c_void {
op_add_4_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, Uint32, Uint32, Uint32, Uint32, CallbackOptions]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Uint32
}
}
fn op_add_4_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
x1: u32,
x2: u32,
x3: u32,
x4: u32,
fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
) -> u32 {
use deno_core::v8;
use deno_core::_ops;
let __opts: &mut v8::fast_api::FastApiCallbackOptions = unsafe {
&mut *fast_api_callback_options
};
let __ctx = unsafe {
&*(v8::Local::<v8::External>::cast(unsafe { __opts.data.data }).value()
as *const _ops::OpCtx)
};
let op_state = &mut ::std::cell::RefCell::borrow_mut(&__ctx.state);
let result = op_add_4::call(x1, x2, x3, x4);
match result {
Ok(result) => result,
Err(err) => {
op_state.last_fast_op_error.replace(err);
__opts.fallback = true;
Default::default()
}
}
}

View file

@ -0,0 +1,3 @@
fn op_add_4(x1: u32, x2: u32, x3: u32, x4: u32) -> Result<u32, anyhow::Error> {
Ok(x1 + x2 + x3 + x4)
}

View file

@ -0,0 +1 @@
FastUnsupportedParamType

View file

@ -0,0 +1,11 @@
fn op_read_sync(
state: &mut OpState,
rid: ResourceId,
mut buf: ZeroCopyBuf,
) -> Result<u32, AnyError> {
// Should not warn about unused `mut buf` binding.
//
// This was caused due to incorrect codegen by fast_call.rs
// on an incompatible op function.
Ok(23)
}

View file

@ -0,0 +1,8 @@
=== Optimizer Dump ===
returns_result: false
has_ref_opstate: false
has_rc_opstate: false
has_fast_callback_option: false
fast_result: Some(Bool)
fast_parameters: [V8Value, V8Value]
transforms: {0: Transform { kind: V8Value, index: 0 }}

View file

@ -0,0 +1,26 @@
struct op_is_proxy_fast {
_phantom: ::std::marker::PhantomData<()>,
}
impl<'scope> deno_core::v8::fast_api::FastFunction for op_is_proxy_fast {
fn function(&self) -> *const ::std::ffi::c_void {
op_is_proxy_fast_fn as *const ::std::ffi::c_void
}
fn args(&self) -> &'static [deno_core::v8::fast_api::Type] {
use deno_core::v8::fast_api::Type::*;
use deno_core::v8::fast_api::CType;
&[V8Value, V8Value]
}
fn return_type(&self) -> deno_core::v8::fast_api::CType {
deno_core::v8::fast_api::CType::Bool
}
}
fn op_is_proxy_fast_fn<'scope>(
_: deno_core::v8::Local<deno_core::v8::Object>,
value: deno_core::v8::Local<v8::Value>,
) -> bool {
use deno_core::v8;
use deno_core::_ops;
let value = serde_v8::Value { v8_value: value };
let result = op_is_proxy::call(value);
result
}

View file

@ -0,0 +1,3 @@
fn op_is_proxy(value: serde_v8::Value) -> bool {
value.v8_value.is_proxy()
}

View file

@ -0,0 +1 @@
FastUnsupportedParamType

View file

@ -0,0 +1,5 @@
fn op_bench_now(state: &mut OpState) -> Result<u64, AnyError> {
let ns = state.borrow::<time::Instant>().elapsed().as_nanos();
let ns_u64 = u64::try_from(ns)?;
Ok(ns_u64)
}

View file

@ -1,39 +1,30 @@
error: custom attribute panicked error: fast async calls are not supported
--> tests/compile_fail/unsupported.rs:5:1
|
5 | #[op(fast)]
| ^^^^^^^^^^^
|
= help: message: op cannot be a fast api. enforced by #[op(fast)]
error: custom attribute panicked
--> tests/compile_fail/unsupported.rs:10:1
|
10 | #[op(fast)]
| ^^^^^^^^^^^
|
= help: message: op cannot be a fast api. enforced by #[op(fast)]
error: custom attribute panicked
--> tests/compile_fail/unsupported.rs:17:1
|
17 | #[op(fast)]
| ^^^^^^^^^^^
|
= help: message: op cannot be a fast api. enforced by #[op(fast)]
error: custom attribute panicked
--> tests/compile_fail/unsupported.rs:22:1 --> tests/compile_fail/unsupported.rs:22:1
| |
22 | #[op(fast)] 22 | #[op(fast)]
| ^^^^^^^^^^^ | ^^^^^^^^^^^
| |
= help: message: async op cannot be a fast api. enforced by #[op(fast)] = note: this error originates in the attribute macro `op` (in Nightly builds, run with -Z macro-backtrace for more info)
warning: unused import: `deno_core::v8::fast_api::FastApiCallbackOptions` error[E0277]: the trait bound `&mut FastApiCallbackOptions<'_>: Deserialize<'_>` is not satisfied
--> tests/compile_fail/unsupported.rs:15:5 --> tests/compile_fail/unsupported.rs:17:1
| |
15 | use deno_core::v8::fast_api::FastApiCallbackOptions; 17 | #[op(fast)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `&mut FastApiCallbackOptions<'_>`
| |
= note: `#[warn(unused_imports)]` on by default = help: the following other types implement trait `Deserialize<'de>`:
&'a Path
&'a [u8]
&'a str
()
(T0, T1)
(T0, T1, T2)
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
and 143 others
note: required by a bound in `from_v8`
--> $WORKSPACE/serde_v8/de.rs
|
| T: Deserialize<'de>,
| ^^^^^^^^^^^^^^^^ required by this bound in `from_v8`
= note: this error originates in the attribute macro `op` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -112,7 +112,15 @@ async function clippy() {
} }
const { success } = await Deno.spawn("cargo", { const { success } = await Deno.spawn("cargo", {
args: [...cmd, "--", "-D", "warnings"], args: [
...cmd,
"--",
"-D",
"warnings",
"-A",
// https://github.com/rust-lang/rust-clippy/issues/407
"clippy::extra_unused_lifetimes",
],
stdout: "inherit", stdout: "inherit",
stderr: "inherit", stderr: "inherit",
}); });