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:
parent
92764c0dec
commit
bc33a4b2e0
39 changed files with 1707 additions and 653 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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
37
ops/attrs.rs
Normal 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
32
ops/deno.rs
Normal 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
399
ops/fast_call.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
782
ops/lib.rs
782
ops/lib.rs
|
@ -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
600
ops/optimizer.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
ops/optimizer_tests/callback_options.expected
Normal file
8
ops/optimizer_tests/callback_options.expected
Normal 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: {}
|
25
ops/optimizer_tests/callback_options.out
Normal file
25
ops/optimizer_tests/callback_options.out
Normal 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
|
||||||
|
}
|
5
ops/optimizer_tests/callback_options.rs
Normal file
5
ops/optimizer_tests/callback_options.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn op_fallback(options: Option<&mut FastApiCallbackOptions>) {
|
||||||
|
if let Some(options) = options {
|
||||||
|
options.fallback = true;
|
||||||
|
}
|
||||||
|
}
|
1
ops/optimizer_tests/incompatible_1.expected
Normal file
1
ops/optimizer_tests/incompatible_1.expected
Normal file
|
@ -0,0 +1 @@
|
||||||
|
FastUnsupportedParamType
|
9
ops/optimizer_tests/incompatible_1.rs
Normal file
9
ops/optimizer_tests/incompatible_1.rs
Normal 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(())
|
||||||
|
}
|
8
ops/optimizer_tests/op_state.expected
Normal file
8
ops/optimizer_tests/op_state.expected
Normal 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: {}
|
34
ops/optimizer_tests/op_state.out
Normal file
34
ops/optimizer_tests/op_state.out
Normal 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
|
||||||
|
}
|
3
ops/optimizer_tests/op_state.rs
Normal file
3
ops/optimizer_tests/op_state.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn op_set_exit_code(state: &mut OpState, code: i32) {
|
||||||
|
state.borrow_mut::<ExitCode>().set(code);
|
||||||
|
}
|
8
ops/optimizer_tests/op_state_basic1.expected
Normal file
8
ops/optimizer_tests/op_state_basic1.expected
Normal 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: {}
|
35
ops/optimizer_tests/op_state_basic1.out
Normal file
35
ops/optimizer_tests/op_state_basic1.out
Normal 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
|
||||||
|
}
|
3
ops/optimizer_tests/op_state_basic1.rs
Normal file
3
ops/optimizer_tests/op_state_basic1.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn foo(state: &mut OpState, a: u32, b: u32) -> u32 {
|
||||||
|
a + b
|
||||||
|
}
|
8
ops/optimizer_tests/op_state_generics.expected
Normal file
8
ops/optimizer_tests/op_state_generics.expected
Normal 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: {}
|
39
ops/optimizer_tests/op_state_generics.out
Normal file
39
ops/optimizer_tests/op_state_generics.out
Normal 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
|
||||||
|
}
|
5
ops/optimizer_tests/op_state_generics.rs
Normal file
5
ops/optimizer_tests/op_state_generics.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub fn op_foo<SP>(state: &mut OpState)
|
||||||
|
where
|
||||||
|
SP: SomePermission + 'static,
|
||||||
|
{
|
||||||
|
}
|
8
ops/optimizer_tests/op_state_result.expected
Normal file
8
ops/optimizer_tests/op_state_result.expected
Normal 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: {}
|
42
ops/optimizer_tests/op_state_result.out
Normal file
42
ops/optimizer_tests/op_state_result.out
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
ops/optimizer_tests/op_state_result.rs
Normal file
3
ops/optimizer_tests/op_state_result.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn foo(state: &mut OpState, a: u32, b: u32) -> Result<u32, AnyError> {
|
||||||
|
Ok(a + b)
|
||||||
|
}
|
8
ops/optimizer_tests/op_state_with_transforms.expected
Normal file
8
ops/optimizer_tests/op_state_with_transforms.expected
Normal 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 }}
|
47
ops/optimizer_tests/op_state_with_transforms.out
Normal file
47
ops/optimizer_tests/op_state_with_transforms.out
Normal 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
|
||||||
|
}
|
5
ops/optimizer_tests/op_state_with_transforms.rs
Normal file
5
ops/optimizer_tests/op_state_with_transforms.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub fn op_now<TP>(state: &mut OpState, buf: &mut [u8])
|
||||||
|
where
|
||||||
|
TP: TimersPermission + 'static,
|
||||||
|
{
|
||||||
|
}
|
8
ops/optimizer_tests/opstate_with_arity.expected
Normal file
8
ops/optimizer_tests/opstate_with_arity.expected
Normal 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: {}
|
44
ops/optimizer_tests/opstate_with_arity.out
Normal file
44
ops/optimizer_tests/opstate_with_arity.out
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
ops/optimizer_tests/opstate_with_arity.rs
Normal file
3
ops/optimizer_tests/opstate_with_arity.rs
Normal 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)
|
||||||
|
}
|
1
ops/optimizer_tests/param_mut_binding_warning.expected
Normal file
1
ops/optimizer_tests/param_mut_binding_warning.expected
Normal file
|
@ -0,0 +1 @@
|
||||||
|
FastUnsupportedParamType
|
11
ops/optimizer_tests/param_mut_binding_warning.rs
Normal file
11
ops/optimizer_tests/param_mut_binding_warning.rs
Normal 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)
|
||||||
|
}
|
8
ops/optimizer_tests/serde_v8_value.expected
Normal file
8
ops/optimizer_tests/serde_v8_value.expected
Normal 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 }}
|
26
ops/optimizer_tests/serde_v8_value.out
Normal file
26
ops/optimizer_tests/serde_v8_value.out
Normal 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
|
||||||
|
}
|
3
ops/optimizer_tests/serde_v8_value.rs
Normal file
3
ops/optimizer_tests/serde_v8_value.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn op_is_proxy(value: serde_v8::Value) -> bool {
|
||||||
|
value.v8_value.is_proxy()
|
||||||
|
}
|
1
ops/optimizer_tests/u64_result.expected
Normal file
1
ops/optimizer_tests/u64_result.expected
Normal file
|
@ -0,0 +1 @@
|
||||||
|
FastUnsupportedParamType
|
5
ops/optimizer_tests/u64_result.rs
Normal file
5
ops/optimizer_tests/u64_result.rs
Normal 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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue