2022-03-14 13:44:15 -04:00
|
|
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
2022-08-21 08:07:53 -04:00
|
|
|
|
|
|
|
use core::panic;
|
2022-06-16 13:40:26 -04:00
|
|
|
use once_cell::sync::Lazy;
|
2022-03-14 13:44:15 -04:00
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use proc_macro2::Span;
|
|
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
|
|
use proc_macro_crate::crate_name;
|
|
|
|
use proc_macro_crate::FoundCrate;
|
2022-08-21 08:07:53 -04:00
|
|
|
use quote::format_ident;
|
2022-03-14 13:44:15 -04:00
|
|
|
use quote::quote;
|
2022-03-15 19:33:46 -04:00
|
|
|
use quote::ToTokens;
|
2022-06-16 13:40:26 -04:00
|
|
|
use regex::Regex;
|
2022-09-07 06:51:47 -04:00
|
|
|
use std::collections::HashMap;
|
2022-06-07 05:25:10 -04:00
|
|
|
use syn::punctuated::Punctuated;
|
|
|
|
use syn::token::Comma;
|
2022-05-12 13:06:42 -04:00
|
|
|
use syn::FnArg;
|
2022-06-07 05:25:10 -04:00
|
|
|
use syn::GenericParam;
|
2022-03-14 13:44:15 -04:00
|
|
|
use syn::Ident;
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
2022-06-02 08:28:19 -04:00
|
|
|
// Identifier to the `deno_core` crate.
|
2022-03-14 13:44:15 -04:00
|
|
|
//
|
|
|
|
// 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 {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 13:06:42 -04:00
|
|
|
#[derive(Copy, Clone, Debug, Default)]
|
2022-04-01 18:09:21 -04:00
|
|
|
struct MacroArgs {
|
|
|
|
is_unstable: bool,
|
2022-05-12 13:06:42 -04:00
|
|
|
is_v8: bool,
|
2022-08-21 08:07:53 -04:00
|
|
|
must_be_fast: bool,
|
2022-09-06 13:38:37 -04:00
|
|
|
deferred: bool,
|
2022-04-01 18:09:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl syn::parse::Parse for MacroArgs {
|
|
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
|
let vars =
|
|
|
|
syn::punctuated::Punctuated::<Ident, syn::Token![,]>::parse_terminated(
|
|
|
|
input,
|
|
|
|
)?;
|
|
|
|
let vars: Vec<_> = vars.iter().map(Ident::to_string).collect();
|
|
|
|
let vars: Vec<_> = vars.iter().map(String::as_str).collect();
|
2022-05-12 13:06:42 -04:00
|
|
|
for var in vars.iter() {
|
2022-09-06 13:38:37 -04:00
|
|
|
if !["unstable", "v8", "fast", "deferred"].contains(var) {
|
2022-05-12 13:06:42 -04:00
|
|
|
return Err(syn::Error::new(
|
|
|
|
input.span(),
|
|
|
|
"Ops expect #[op] or #[op(unstable)]",
|
|
|
|
));
|
|
|
|
}
|
2022-04-01 18:09:21 -04:00
|
|
|
}
|
2022-05-12 13:06:42 -04:00
|
|
|
Ok(Self {
|
|
|
|
is_unstable: vars.contains(&"unstable"),
|
|
|
|
is_v8: vars.contains(&"v8"),
|
2022-08-21 08:07:53 -04:00
|
|
|
must_be_fast: vars.contains(&"fast"),
|
2022-09-06 13:38:37 -04:00
|
|
|
deferred: vars.contains(&"deferred"),
|
2022-05-12 13:06:42 -04:00
|
|
|
})
|
2022-04-01 18:09:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
#[proc_macro_attribute]
|
2022-04-01 18:09:21 -04:00
|
|
|
pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
|
2022-05-12 13:06:42 -04:00
|
|
|
let margs = syn::parse_macro_input!(attr as MacroArgs);
|
2022-08-21 08:07:53 -04:00
|
|
|
let MacroArgs {
|
|
|
|
is_unstable,
|
|
|
|
is_v8,
|
|
|
|
must_be_fast,
|
2022-09-06 13:38:37 -04:00
|
|
|
deferred,
|
2022-08-21 08:07:53 -04:00
|
|
|
} = margs;
|
2022-03-14 13:44:15 -04:00
|
|
|
let func = syn::parse::<syn::ItemFn>(item).expect("expected a function");
|
|
|
|
let name = &func.sig.ident;
|
2022-06-16 11:04:55 -04:00
|
|
|
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));
|
|
|
|
}
|
2022-06-07 05:25:10 -04:00
|
|
|
let type_params = exclude_lifetime_params(&func.sig.generics.params);
|
2022-03-14 13:44:15 -04:00
|
|
|
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();
|
|
|
|
|
2022-06-07 22:28:26 -04:00
|
|
|
let asyncness = func.sig.asyncness.is_some();
|
|
|
|
let is_async = asyncness || is_future(&func.sig.output);
|
2022-09-22 22:55:37 -04:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2022-03-22 11:39:58 -04:00
|
|
|
let v8_body = if is_async {
|
2022-09-06 13:38:37 -04:00
|
|
|
codegen_v8_async(&core, &func, margs, asyncness, deferred)
|
2022-03-14 13:44:15 -04:00
|
|
|
} else {
|
2022-09-22 22:55:37 -04:00
|
|
|
codegen_v8_sync(&core, &func, margs, has_fallible_fast_call)
|
2022-03-14 13:44:15 -04:00
|
|
|
};
|
|
|
|
|
2022-03-19 17:13:53 -04:00
|
|
|
let docline = format!("Use `{name}::decl()` to get an op-declaration");
|
2022-03-14 13:44:15 -04:00
|
|
|
// Generate wrapper
|
|
|
|
quote! {
|
|
|
|
#[allow(non_camel_case_types)]
|
2022-03-19 17:13:53 -04:00
|
|
|
#[doc="Auto-generated by `deno_ops`, i.e: `#[op]`"]
|
|
|
|
#[doc=""]
|
|
|
|
#[doc=#docline]
|
|
|
|
#[doc="you can include in a `deno_core::Extension`."]
|
2022-03-14 13:44:15 -04:00
|
|
|
pub struct #name;
|
|
|
|
|
2022-03-19 17:13:53 -04:00
|
|
|
#[doc(hidden)]
|
2022-03-14 13:44:15 -04:00
|
|
|
impl #name {
|
|
|
|
pub fn name() -> &'static str {
|
|
|
|
stringify!(#name)
|
|
|
|
}
|
|
|
|
|
2022-03-15 18:43:17 -04:00
|
|
|
pub fn v8_fn_ptr #generics () -> #core::v8::FunctionCallback #where_clause {
|
2022-03-14 13:44:15 -04:00
|
|
|
use #core::v8::MapFnTo;
|
|
|
|
Self::v8_func::<#type_params>.map_fn_to()
|
|
|
|
}
|
|
|
|
|
2022-03-15 18:43:17 -04:00
|
|
|
pub fn decl #generics () -> #core::OpDecl #where_clause {
|
|
|
|
#core::OpDecl {
|
|
|
|
name: Self::name(),
|
|
|
|
v8_fn_ptr: Self::v8_fn_ptr::<#type_params>(),
|
2022-03-22 11:39:58 -04:00
|
|
|
enabled: true,
|
2022-08-21 08:07:53 -04:00
|
|
|
fast_fn: #fast_field,
|
2022-03-22 11:39:58 -04:00
|
|
|
is_async: #is_async,
|
2022-04-01 18:09:21 -04:00
|
|
|
is_unstable: #is_unstable,
|
2022-05-12 13:06:42 -04:00
|
|
|
is_v8: #is_v8,
|
2022-03-15 18:43:17 -04:00
|
|
|
}
|
2022-03-14 13:44:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2022-04-23 12:49:06 -04:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-03-14 13:44:15 -04:00
|
|
|
#original_func
|
|
|
|
|
|
|
|
pub fn v8_func #generics (
|
2022-06-16 11:04:55 -04:00
|
|
|
scope: &mut #core::v8::HandleScope<'scope>,
|
2022-03-14 13:44:15 -04:00
|
|
|
args: #core::v8::FunctionCallbackArguments,
|
|
|
|
mut rv: #core::v8::ReturnValue,
|
|
|
|
) #where_clause {
|
|
|
|
#v8_body
|
|
|
|
}
|
|
|
|
}
|
2022-08-21 08:07:53 -04:00
|
|
|
|
|
|
|
#fast_impl
|
2022-03-14 13:44:15 -04:00
|
|
|
}.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate the body of a v8 func for an async op
|
2022-05-12 13:06:42 -04:00
|
|
|
fn codegen_v8_async(
|
|
|
|
core: &TokenStream2,
|
|
|
|
f: &syn::ItemFn,
|
2022-06-16 11:04:55 -04:00
|
|
|
margs: MacroArgs,
|
2022-06-07 22:28:26 -04:00
|
|
|
asyncness: bool,
|
2022-09-06 13:38:37 -04:00
|
|
|
deferred: bool,
|
2022-05-12 13:06:42 -04:00
|
|
|
) -> TokenStream2 {
|
2022-06-16 11:04:55 -04:00
|
|
|
let MacroArgs { is_v8, .. } = margs;
|
|
|
|
let special_args = f
|
|
|
|
.sig
|
|
|
|
.inputs
|
|
|
|
.iter()
|
|
|
|
.map_while(|a| {
|
|
|
|
(if is_v8 { scope_arg(a) } else { None }).or_else(|| opstate_arg(a))
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
let rust_i0 = special_args.len();
|
|
|
|
let args_head = special_args.into_iter().collect::<TokenStream2>();
|
|
|
|
|
2022-04-08 04:32:48 -04:00
|
|
|
let (arg_decls, args_tail) = codegen_args(core, f, rust_i0, 1);
|
2022-06-07 05:25:10 -04:00
|
|
|
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-06-16 11:04:55 -04:00
|
|
|
let (pre_result, mut result_fut) = match asyncness {
|
2022-06-07 22:28:26 -04:00
|
|
|
true => (
|
|
|
|
quote! {},
|
2022-06-16 11:04:55 -04:00
|
|
|
quote! { Self::call::<#type_params>(#args_head #args_tail).await; },
|
2022-06-07 22:28:26 -04:00
|
|
|
),
|
|
|
|
false => (
|
|
|
|
quote! { let result_fut = Self::call::<#type_params>(#args_head #args_tail); },
|
2022-06-16 11:04:55 -04:00
|
|
|
quote! { result_fut.await; },
|
2022-06-07 22:28:26 -04:00
|
|
|
),
|
|
|
|
};
|
2022-05-12 13:13:25 -04:00
|
|
|
let result_wrapper = match is_result(&f.sig.output) {
|
2022-06-16 11:04:55 -04:00
|
|
|
true => {
|
|
|
|
// Support `Result<impl Future<Output = Result<T, AnyError>> + 'static, AnyError>`
|
|
|
|
if !asyncness {
|
|
|
|
result_fut = quote! { result_fut; };
|
|
|
|
quote! {
|
|
|
|
let result = match result {
|
|
|
|
Ok(fut) => fut.await,
|
feat(core): Add support for async ops in realms (#14734)
Pull request #14019 enabled initial support for realms, but it did not
include support for async ops anywhere other than the main realm. The
main issue was that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.
This change creates a `ContextState` struct, similar to
`JsRuntimeState` but stored in a slot of each `v8::Context`, which
contains a `js_recv_cb` callback for each realm. Combined with a new
list of known realms, which stores them as `v8::Weak<v8::Context>`,
and a change in the `#[op]` macro to pass the current context to
`queue_async_op`, this makes it possible to send the results of
promises for different realms to their realm, and prevent the ID's
from getting mixed up.
Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.
Co-authored-by: Luis Malheiro <luismalheiro@gmail.com>
2022-08-10 14:04:20 -04:00
|
|
|
Err(e) => return (context, promise_id, op_id, #core::_ops::to_op_result::<()>(get_class, Err(e))),
|
2022-06-16 11:04:55 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {}
|
|
|
|
}
|
|
|
|
}
|
2022-05-12 13:13:25 -04:00
|
|
|
false => quote! { let result = Ok(result); },
|
|
|
|
};
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
quote! {
|
|
|
|
use #core::futures::FutureExt;
|
2022-04-08 04:32:48 -04:00
|
|
|
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
|
|
|
let ctx = unsafe {
|
|
|
|
&*(#core::v8::Local::<#core::v8::External>::cast(args.data().unwrap_unchecked()).value()
|
|
|
|
as *const #core::_ops::OpCtx)
|
|
|
|
};
|
|
|
|
let op_id = ctx.id;
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-04-08 04:32:48 -04:00
|
|
|
let promise_id = args.get(0);
|
2022-03-14 13:44:15 -04:00
|
|
|
let promise_id = #core::v8::Local::<#core::v8::Integer>::try_from(promise_id)
|
|
|
|
.map(|l| l.value() as #core::PromiseId)
|
|
|
|
.map_err(#core::anyhow::Error::from);
|
|
|
|
// Fail if promise id invalid (not an int)
|
|
|
|
let promise_id: #core::PromiseId = match promise_id {
|
|
|
|
Ok(promise_id) => promise_id,
|
|
|
|
Err(err) => {
|
|
|
|
#core::_ops::throw_type_error(scope, format!("invalid promise id: {}", err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-14 18:38:53 -04:00
|
|
|
#arg_decls
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-04-08 04:32:48 -04:00
|
|
|
let state = ctx.state.clone();
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
// Track async call & get copy of get_error_class_fn
|
|
|
|
let get_class = {
|
|
|
|
let state = state.borrow();
|
|
|
|
state.tracker.track_async(op_id);
|
|
|
|
state.get_error_class_fn
|
|
|
|
};
|
|
|
|
|
feat(core): Add support for async ops in realms (#14734)
Pull request #14019 enabled initial support for realms, but it did not
include support for async ops anywhere other than the main realm. The
main issue was that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.
This change creates a `ContextState` struct, similar to
`JsRuntimeState` but stored in a slot of each `v8::Context`, which
contains a `js_recv_cb` callback for each realm. Combined with a new
list of known realms, which stores them as `v8::Weak<v8::Context>`,
and a change in the `#[op]` macro to pass the current context to
`queue_async_op`, this makes it possible to send the results of
promises for different realms to their realm, and prevent the ID's
from getting mixed up.
Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.
Co-authored-by: Luis Malheiro <luismalheiro@gmail.com>
2022-08-10 14:04:20 -04:00
|
|
|
let context = {
|
|
|
|
let local = scope.get_current_context();
|
|
|
|
#core::v8::Global::new(scope, local)
|
|
|
|
};
|
|
|
|
|
2022-06-07 22:28:26 -04:00
|
|
|
#pre_result
|
2022-09-06 13:38:37 -04:00
|
|
|
#core::_ops::queue_async_op(state, scope, #deferred, async move {
|
2022-06-16 11:04:55 -04:00
|
|
|
let result = #result_fut
|
2022-05-12 13:13:25 -04:00
|
|
|
#result_wrapper
|
feat(core): Add support for async ops in realms (#14734)
Pull request #14019 enabled initial support for realms, but it did not
include support for async ops anywhere other than the main realm. The
main issue was that the `js_recv_cb` callback, which resolves promises
corresponding to async ops, was only set for the main realm, so async
ops in other realms would never resolve. Furthermore, promise ID's are
specific to each realm, which meant that async ops from other realms
would result in a wrong promise from the main realm being resolved.
This change creates a `ContextState` struct, similar to
`JsRuntimeState` but stored in a slot of each `v8::Context`, which
contains a `js_recv_cb` callback for each realm. Combined with a new
list of known realms, which stores them as `v8::Weak<v8::Context>`,
and a change in the `#[op]` macro to pass the current context to
`queue_async_op`, this makes it possible to send the results of
promises for different realms to their realm, and prevent the ID's
from getting mixed up.
Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get
the total number of unrefed ops to compare in the event loop.
Co-authored-by: Luis Malheiro <luismalheiro@gmail.com>
2022-08-10 14:04:20 -04:00
|
|
|
(context, promise_id, op_id, #core::_ops::to_op_result(get_class, result))
|
2022-03-14 13:44:15 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-16 11:04:55 -04:00
|
|
|
fn scope_arg(arg: &FnArg) -> Option<TokenStream2> {
|
|
|
|
if is_handle_scope(arg) {
|
|
|
|
Some(quote! { scope, })
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn opstate_arg(arg: &FnArg) -> Option<TokenStream2> {
|
|
|
|
match arg {
|
|
|
|
arg if is_rc_refcell_opstate(arg) => Some(quote! { ctx.state.clone(), }),
|
|
|
|
arg if is_mut_ref_opstate(arg) => {
|
|
|
|
Some(quote! { &mut ctx.state.borrow_mut(), })
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
fn codegen_fast_impl(
|
|
|
|
core: &TokenStream2,
|
|
|
|
f: &syn::ItemFn,
|
|
|
|
name: &syn::Ident,
|
|
|
|
is_async: bool,
|
|
|
|
must_be_fast: bool,
|
2022-09-22 22:55:37 -04:00
|
|
|
) -> (bool, TokenStream2, TokenStream2) {
|
2022-09-22 00:35:24 -04:00
|
|
|
if is_async {
|
|
|
|
if must_be_fast {
|
|
|
|
panic!("async op cannot be a fast api. enforced by #[op(fast)]")
|
|
|
|
}
|
2022-09-22 22:55:37 -04:00
|
|
|
return (false, quote! {}, quote! { None });
|
2022-08-21 08:07:53 -04:00
|
|
|
}
|
|
|
|
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,
|
2022-09-22 00:35:24 -04:00
|
|
|
use_op_state,
|
2022-09-01 06:23:06 -04:00
|
|
|
use_fast_cb_opts,
|
2022-08-30 05:01:36 -04:00
|
|
|
v8_values,
|
2022-09-22 22:55:37 -04:00
|
|
|
returns_result,
|
2022-09-07 06:51:47 -04:00
|
|
|
slices,
|
2022-08-21 08:07:53 -04:00
|
|
|
}) = fast_info
|
|
|
|
{
|
2022-09-22 00:35:24 -04:00
|
|
|
let offset = if use_op_state { 1 } else { 0 };
|
2022-09-07 06:51:47 -04:00
|
|
|
let mut inputs = f
|
2022-08-21 08:07:53 -04:00
|
|
|
.sig
|
|
|
|
.inputs
|
|
|
|
.iter()
|
2022-09-01 06:23:06 -04:00
|
|
|
.skip(offset)
|
2022-08-30 05:01:36 -04:00
|
|
|
.enumerate()
|
|
|
|
.map(|(idx, arg)| {
|
2022-09-01 06:23:06 -04:00
|
|
|
let ident = match arg {
|
|
|
|
FnArg::Receiver(_) => unreachable!(),
|
|
|
|
FnArg::Typed(t) => match &*t.pat {
|
|
|
|
syn::Pat::Ident(i) => format_ident!("{}", i.ident),
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
};
|
2022-09-07 06:51:47 -04:00
|
|
|
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 };
|
2022-09-01 06:23:06 -04:00
|
|
|
}
|
2022-08-30 05:01:36 -04:00
|
|
|
if v8_values.contains(&idx) {
|
|
|
|
return quote! { #ident: #core::v8::Local < #core::v8::Value > };
|
|
|
|
}
|
|
|
|
quote!(#arg)
|
|
|
|
})
|
2022-08-21 08:07:53 -04:00
|
|
|
.collect::<Vec<_>>();
|
2022-09-22 22:55:37 -04:00
|
|
|
if (!slices.is_empty() || use_op_state || returns_result)
|
|
|
|
&& !use_fast_cb_opts
|
|
|
|
{
|
2022-09-07 06:51:47 -04:00
|
|
|
inputs.push(quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions });
|
|
|
|
}
|
2022-08-21 08:07:53 -04:00
|
|
|
let input_idents = f
|
|
|
|
.sig
|
|
|
|
.inputs
|
|
|
|
.iter()
|
2022-08-30 05:01:36 -04:00
|
|
|
.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!(),
|
|
|
|
},
|
|
|
|
};
|
2022-09-07 06:51:47 -04:00
|
|
|
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();
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2022-09-01 06:23:06 -04:00
|
|
|
if use_fast_cb_opts && idx == f.sig.inputs.len() - 1 {
|
2022-09-07 06:51:47 -04:00
|
|
|
return quote! { Some(unsafe { &mut * fast_api_callback_options }) };
|
2022-09-01 06:23:06 -04:00
|
|
|
}
|
2022-08-30 05:01:36 -04:00
|
|
|
if v8_values.contains(&idx) {
|
|
|
|
return quote! {
|
|
|
|
#core::serde_v8::Value {
|
|
|
|
v8_value: #ident,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
quote! { #ident }
|
2022-08-21 08:07:53 -04:00
|
|
|
})
|
|
|
|
.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),*) {
|
2022-09-22 00:35:24 -04:00
|
|
|
// 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;
|
2022-08-21 08:07:53 -04:00
|
|
|
#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 {
|
2022-09-22 22:55:37 -04:00
|
|
|
let output = if returns_result {
|
|
|
|
get_fast_result_return_type(&f.sig.output)
|
|
|
|
} else {
|
|
|
|
let output = &f.sig.output;
|
|
|
|
quote! { #output }
|
|
|
|
};
|
2022-08-21 08:07:53 -04:00
|
|
|
let func_name = format_ident!("func_{}", name);
|
2022-09-22 22:55:37 -04:00
|
|
|
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 {
|
2022-08-21 08:07:53 -04:00
|
|
|
quote! {
|
2022-09-22 00:35:24 -04:00
|
|
|
// SAFETY: V8 calling convention guarantees that the callback options pointer is non-null.
|
2022-09-22 22:55:37 -04:00
|
|
|
let opts: &mut #core::v8::fast_api::FastApiCallbackOptions = unsafe { &mut *fast_api_callback_options };
|
2022-09-22 00:35:24 -04:00
|
|
|
// 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 ctx.state.borrow_mut();
|
2022-08-21 08:07:53 -04:00
|
|
|
}
|
|
|
|
} else {
|
2022-09-22 22:55:37 -04:00
|
|
|
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 }
|
2022-08-21 08:07:53 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
(
|
|
|
|
quote! {
|
2022-09-22 00:35:24 -04:00
|
|
|
fn #func_name #generics (_recv: #core::v8::Local<#core::v8::Object>, #(#inputs),*) #output #where_clause {
|
2022-08-21 08:07:53 -04:00
|
|
|
#recv_decl
|
2022-09-22 22:55:37 -04:00
|
|
|
let result = #name::call::<#type_params>(#(#input_idents),*);
|
|
|
|
#result_handling
|
2022-08-21 08:07:53 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
quote! {
|
2022-08-28 02:51:49 -04:00
|
|
|
#func_name::<#type_params> as *const _
|
2022-08-21 08:07:53 -04:00
|
|
|
},
|
|
|
|
)
|
|
|
|
};
|
2022-08-28 02:51:49 -04:00
|
|
|
|
|
|
|
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> },
|
|
|
|
)
|
|
|
|
};
|
2022-08-21 08:07:53 -04:00
|
|
|
return (
|
2022-09-22 22:55:37 -04:00
|
|
|
returns_result,
|
2022-08-21 08:07:53 -04:00
|
|
|
quote! {
|
2022-08-28 02:51:49 -04:00
|
|
|
#[allow(non_camel_case_types)]
|
|
|
|
#[doc(hidden)]
|
|
|
|
struct #fast_struct #ty_generics {
|
|
|
|
_phantom: ::std::marker::PhantomData<#type_params>,
|
|
|
|
}
|
2022-08-21 08:07:53 -04:00
|
|
|
#trampoline
|
2022-08-28 02:51:49 -04:00
|
|
|
impl #impl_generics #core::v8::fast_api::FastFunction for #fast_struct #ty_generics #where_clause {
|
|
|
|
fn function(&self) -> *const ::std::ffi::c_void {
|
2022-08-21 08:07:53 -04:00
|
|
|
#raw_block
|
|
|
|
}
|
|
|
|
fn args(&self) -> &'static [#core::v8::fast_api::Type] {
|
|
|
|
&[ #args ]
|
|
|
|
}
|
|
|
|
fn return_type(&self) -> #core::v8::fast_api::CType {
|
|
|
|
#ret
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-08-28 02:51:49 -04:00
|
|
|
quote! { Some(Box::new(#fast_struct #struct_generics { _phantom: ::std::marker::PhantomData })) },
|
2022-08-21 08:07:53 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default impl to satisfy generic bounds for non-fast ops
|
2022-09-22 22:55:37 -04:00
|
|
|
(false, quote! {}, quote! { None })
|
2022-08-21 08:07:53 -04:00
|
|
|
}
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
/// Generate the body of a v8 func for a sync op
|
2022-05-12 13:06:42 -04:00
|
|
|
fn codegen_v8_sync(
|
|
|
|
core: &TokenStream2,
|
|
|
|
f: &syn::ItemFn,
|
|
|
|
margs: MacroArgs,
|
2022-09-22 22:55:37 -04:00
|
|
|
has_fallible_fast_call: bool,
|
2022-05-12 13:06:42 -04:00
|
|
|
) -> TokenStream2 {
|
|
|
|
let MacroArgs { is_v8, .. } = margs;
|
|
|
|
let special_args = f
|
|
|
|
.sig
|
|
|
|
.inputs
|
|
|
|
.iter()
|
|
|
|
.map_while(|a| {
|
|
|
|
(if is_v8 { scope_arg(a) } else { None }).or_else(|| opstate_arg(a))
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
let rust_i0 = special_args.len();
|
|
|
|
let args_head = special_args.into_iter().collect::<TokenStream2>();
|
2022-04-08 04:32:48 -04:00
|
|
|
let (arg_decls, args_tail) = codegen_args(core, f, rust_i0, 0);
|
2022-03-14 13:44:15 -04:00
|
|
|
let ret = codegen_sync_ret(core, &f.sig.output);
|
2022-06-07 05:25:10 -04:00
|
|
|
let type_params = exclude_lifetime_params(&f.sig.generics.params);
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-09-22 22:55:37 -04:00
|
|
|
let fast_error_handler = if has_fallible_fast_call {
|
|
|
|
quote! {
|
|
|
|
{
|
|
|
|
let op_state = &mut ctx.state.borrow_mut();
|
|
|
|
if let Some(err) = op_state.last_fast_op_error.take() {
|
|
|
|
let exception = #core::error::to_v8_error(scope, op_state.get_error_class_fn, &err);
|
|
|
|
scope.throw_exception(exception);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {}
|
|
|
|
};
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
quote! {
|
2022-04-08 04:32:48 -04:00
|
|
|
// SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime
|
|
|
|
let ctx = unsafe {
|
|
|
|
&*(#core::v8::Local::<#core::v8::External>::cast(args.data().unwrap_unchecked()).value()
|
|
|
|
as *const #core::_ops::OpCtx)
|
|
|
|
};
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-09-22 22:55:37 -04:00
|
|
|
#fast_error_handler
|
2022-03-14 18:38:53 -04:00
|
|
|
#arg_decls
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-05-12 13:06:42 -04:00
|
|
|
let result = Self::call::<#type_params>(#args_head #args_tail);
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-09-04 21:59:36 -04:00
|
|
|
// use RefCell::borrow instead of state.borrow to avoid clash with std::borrow::Borrow
|
|
|
|
let op_state = ::std::cell::RefCell::borrow(&*ctx.state);
|
2022-04-08 04:32:48 -04:00
|
|
|
op_state.tracker.track_sync(ctx.id);
|
2022-03-14 13:44:15 -04:00
|
|
|
|
|
|
|
#ret
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
struct FastApiSyn {
|
|
|
|
args: TokenStream2,
|
|
|
|
ret: TokenStream2,
|
2022-09-22 00:35:24 -04:00
|
|
|
use_op_state: bool,
|
2022-09-01 06:23:06 -04:00
|
|
|
use_fast_cb_opts: bool,
|
2022-08-30 05:01:36 -04:00
|
|
|
v8_values: Vec<usize>,
|
2022-09-22 22:55:37 -04:00
|
|
|
returns_result: bool,
|
2022-09-07 06:51:47 -04:00
|
|
|
slices: HashMap<usize, TokenStream2>,
|
2022-08-21 08:07:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
|
|
|
|
let inputs = &f.sig.inputs;
|
2022-09-22 22:55:37 -04:00
|
|
|
let mut returns_result = false;
|
2022-08-21 08:07:53 -04:00
|
|
|
let ret = match &f.sig.output {
|
|
|
|
syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void),
|
2022-09-22 22:55:37 -04:00
|
|
|
syn::ReturnType::Type(_, ty) => match is_fast_return_type(core, ty) {
|
|
|
|
Some((ret, is_result)) => {
|
|
|
|
returns_result = is_result;
|
|
|
|
ret
|
|
|
|
}
|
2022-08-21 08:07:53 -04:00
|
|
|
None => return None,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-09-22 00:35:24 -04:00
|
|
|
let mut use_op_state = false;
|
2022-09-01 06:23:06 -04:00
|
|
|
let mut use_fast_cb_opts = false;
|
2022-08-30 05:01:36 -04:00
|
|
|
let mut v8_values = Vec::new();
|
2022-09-07 06:51:47 -04:00
|
|
|
let mut slices = HashMap::new();
|
2022-08-21 08:07:53 -04:00
|
|
|
let mut args = vec![quote! { #core::v8::fast_api::Type::V8Value }];
|
|
|
|
for (pos, input) in inputs.iter().enumerate() {
|
2022-09-01 06:23:06 -04:00
|
|
|
if pos == inputs.len() - 1 && is_optional_fast_callback_option(input) {
|
|
|
|
use_fast_cb_opts = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
if pos == 0 && is_mut_ref_opstate(input) {
|
2022-09-22 00:35:24 -04:00
|
|
|
use_op_state = true;
|
2022-08-21 08:07:53 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let ty = match input {
|
|
|
|
syn::FnArg::Typed(pat) => &pat.ty,
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
2022-08-30 05:01:36 -04:00
|
|
|
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);
|
|
|
|
}
|
2022-09-07 06:51:47 -04:00
|
|
|
None => match is_ref_slice(&ty) {
|
2022-09-23 00:05:45 -04:00
|
|
|
Some(SliceType::U32Mut) => {
|
|
|
|
args.push(quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint32) });
|
|
|
|
slices.insert(pos, quote!(u32));
|
|
|
|
}
|
2022-09-07 06:51:47 -04:00
|
|
|
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,
|
|
|
|
},
|
2022-08-30 05:01:36 -04:00
|
|
|
},
|
2022-08-21 08:07:53 -04:00
|
|
|
Some(arg) => {
|
|
|
|
args.push(arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 00:35:24 -04:00
|
|
|
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 });
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
let args = args
|
|
|
|
.iter()
|
|
|
|
.map(|arg| format!("{}", arg))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ");
|
|
|
|
Some(FastApiSyn {
|
|
|
|
args: args.parse().unwrap(),
|
|
|
|
ret,
|
2022-09-22 00:35:24 -04:00
|
|
|
use_op_state,
|
2022-09-07 06:51:47 -04:00
|
|
|
slices,
|
2022-08-30 05:01:36 -04:00
|
|
|
v8_values,
|
2022-09-01 06:23:06 -04:00
|
|
|
use_fast_cb_opts,
|
2022-09-22 22:55:37 -04:00
|
|
|
returns_result,
|
2022-08-21 08:07:53 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:01:36 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2022-09-22 22:55:37 -04:00
|
|
|
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!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
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 }),
|
|
|
|
"f32" => Some(quote! { #core::v8::fast_api::#cty::Float32 }),
|
|
|
|
"f64" => Some(quote! { #core::v8::fast_api::#cty::Float64 }),
|
2022-08-30 05:01:36 -04:00
|
|
|
"bool" => Some(quote! { #core::v8::fast_api::#cty::Bool }),
|
2022-08-21 08:07:53 -04:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 18:38:53 -04:00
|
|
|
fn codegen_args(
|
|
|
|
core: &TokenStream2,
|
|
|
|
f: &syn::ItemFn,
|
|
|
|
rust_i0: usize, // Index of first generic arg in rust
|
|
|
|
v8_i0: usize, // Index of first generic arg in v8/js
|
|
|
|
) -> (TokenStream2, TokenStream2) {
|
|
|
|
let inputs = &f.sig.inputs.iter().skip(rust_i0).enumerate();
|
|
|
|
let ident_seq: TokenStream2 = inputs
|
|
|
|
.clone()
|
|
|
|
.map(|(i, _)| format!("arg_{i}"))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ")
|
|
|
|
.parse()
|
|
|
|
.unwrap();
|
|
|
|
let decls: TokenStream2 = inputs
|
|
|
|
.clone()
|
|
|
|
.map(|(i, arg)| {
|
|
|
|
codegen_arg(core, arg, format!("arg_{i}").as_ref(), v8_i0 + i)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
(decls, ident_seq)
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
fn codegen_arg(
|
|
|
|
core: &TokenStream2,
|
|
|
|
arg: &syn::FnArg,
|
|
|
|
name: &str,
|
2022-03-14 18:38:53 -04:00
|
|
|
idx: usize,
|
2022-03-14 13:44:15 -04:00
|
|
|
) -> TokenStream2 {
|
|
|
|
let ident = quote::format_ident!("{name}");
|
2022-08-30 05:01:14 -04:00
|
|
|
let (pat, ty) = match arg {
|
2022-09-01 06:23:06 -04:00
|
|
|
syn::FnArg::Typed(pat) => {
|
|
|
|
if is_optional_fast_callback_option(&pat.ty) {
|
|
|
|
return quote! { let #ident = None; };
|
|
|
|
}
|
|
|
|
(&pat.pat, &pat.ty)
|
|
|
|
}
|
2022-03-14 13:44:15 -04:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
// Fast path if arg should be skipped
|
|
|
|
if matches!(**pat, syn::Pat::Wild(_)) {
|
|
|
|
return quote! { let #ident = (); };
|
|
|
|
}
|
2022-08-30 05:01:14 -04:00
|
|
|
// Fast path for `String`
|
|
|
|
if is_string(&**ty) {
|
|
|
|
return quote! {
|
|
|
|
let #ident = match #core::v8::Local::<#core::v8::String>::try_from(args.get(#idx as i32)) {
|
|
|
|
Ok(v8_string) => #core::serde_v8::to_utf8(v8_string, scope),
|
|
|
|
Err(_) => {
|
|
|
|
return #core::_ops::throw_type_error(scope, format!("Expected string at position {}", #idx));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
// Fast path for `Option<String>`
|
|
|
|
if is_option_string(&**ty) {
|
|
|
|
return quote! {
|
|
|
|
let #ident = match #core::v8::Local::<#core::v8::String>::try_from(args.get(#idx as i32)) {
|
|
|
|
Ok(v8_string) => Some(#core::serde_v8::to_utf8(v8_string, scope)),
|
|
|
|
Err(_) => None
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
2022-09-07 06:51:47 -04:00
|
|
|
// Fast path for &/&mut [u8] and &/&mut [u32]
|
2022-09-17 07:18:15 -04:00
|
|
|
match is_ref_slice(&**ty) {
|
|
|
|
None => {}
|
|
|
|
Some(SliceType::U32Mut) => {
|
|
|
|
let blck = codegen_u32_mut_slice(core, idx);
|
|
|
|
return quote! {
|
|
|
|
let #ident = #blck;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Some(_) => {
|
|
|
|
let blck = codegen_u8_slice(core, idx);
|
|
|
|
return quote! {
|
|
|
|
let #ident = #blck;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Otherwise deserialize it via serde_v8
|
|
|
|
quote! {
|
|
|
|
let #ident = args.get(#idx as i32);
|
|
|
|
let #ident = match #core::serde_v8::from_v8(scope, #ident) {
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(err) => {
|
|
|
|
let msg = format!("Error parsing args at position {}: {}", #idx, #core::anyhow::Error::from(err));
|
|
|
|
return #core::_ops::throw_type_error(scope, msg);
|
|
|
|
}
|
2022-09-07 06:51:47 -04:00
|
|
|
};
|
2022-09-17 07:18:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn codegen_u8_slice(core: &TokenStream2, idx: usize) -> TokenStream2 {
|
|
|
|
quote! {{
|
|
|
|
let value = args.get(#idx as i32);
|
|
|
|
match #core::v8::Local::<#core::v8::ArrayBuffer>::try_from(value) {
|
|
|
|
Ok(b) => {
|
|
|
|
let store = b.data() as *mut u8;
|
|
|
|
// SAFETY: rust guarantees that lifetime of slice is no longer than the call.
|
|
|
|
unsafe { ::std::slice::from_raw_parts_mut(store, b.byte_length()) }
|
|
|
|
},
|
|
|
|
Err(_) => {
|
2022-09-07 06:51:47 -04:00
|
|
|
if let Ok(view) = #core::v8::Local::<#core::v8::ArrayBufferView>::try_from(value) {
|
|
|
|
let (offset, len) = (view.byte_offset(), view.byte_length());
|
|
|
|
let buffer = match view.buffer(scope) {
|
|
|
|
Some(v) => v,
|
|
|
|
None => {
|
|
|
|
return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx));
|
|
|
|
}
|
|
|
|
};
|
2022-09-17 07:18:15 -04:00
|
|
|
let store = buffer.data() as *mut u8;
|
|
|
|
// SAFETY: rust guarantees that lifetime of slice is no longer than the call.
|
|
|
|
unsafe { ::std::slice::from_raw_parts_mut(store.add(offset), len) }
|
2022-09-07 06:51:47 -04:00
|
|
|
} else {
|
2022-09-17 07:18:15 -04:00
|
|
|
return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx));
|
2022-09-07 06:51:47 -04:00
|
|
|
}
|
2022-09-17 07:18:15 -04:00
|
|
|
}
|
|
|
|
}}
|
2022-09-07 06:51:47 -04:00
|
|
|
}
|
2022-09-17 07:18:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn codegen_u32_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 {
|
2022-03-14 13:44:15 -04:00
|
|
|
quote! {
|
2022-09-17 07:18:15 -04:00
|
|
|
if let Ok(view) = #core::v8::Local::<#core::v8::Uint32Array>::try_from(args.get(#idx as i32)) {
|
|
|
|
let (offset, len) = (view.byte_offset(), view.byte_length());
|
|
|
|
let buffer = match view.buffer(scope) {
|
|
|
|
Some(v) => v,
|
|
|
|
None => {
|
|
|
|
return #core::_ops::throw_type_error(scope, format!("Expected Uint32Array at position {}", #idx));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let store = buffer.data() as *mut u8;
|
|
|
|
// SAFETY: buffer from Uint32Array. Rust guarantees that lifetime of slice is no longer than the call.
|
|
|
|
unsafe { ::std::slice::from_raw_parts_mut(store.add(offset) as *mut u32, len / 4) }
|
|
|
|
} else {
|
|
|
|
return #core::_ops::throw_type_error(scope, format!("Expected Uint32Array at position {}", #idx));
|
|
|
|
}
|
2022-03-14 13:44:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn codegen_sync_ret(
|
|
|
|
core: &TokenStream2,
|
|
|
|
output: &syn::ReturnType,
|
|
|
|
) -> TokenStream2 {
|
2022-05-12 15:51:57 -04:00
|
|
|
if is_void(output) {
|
|
|
|
return quote! {};
|
|
|
|
}
|
2022-03-14 13:44:15 -04:00
|
|
|
|
2022-07-01 13:29:24 -04:00
|
|
|
if is_u32_rv(output) {
|
|
|
|
return quote! {
|
|
|
|
rv.set_uint32(result as u32);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
// Optimize Result<(), Err> to skip serde_v8 when Ok(...)
|
2022-05-12 15:51:57 -04:00
|
|
|
let ok_block = if is_unit_result(output) {
|
2022-03-14 13:44:15 -04:00
|
|
|
quote! {}
|
2022-07-01 13:29:24 -04:00
|
|
|
} else if is_u32_rv_result(output) {
|
|
|
|
quote! {
|
|
|
|
rv.set_uint32(result as u32);
|
|
|
|
}
|
2022-03-14 13:44:15 -04:00
|
|
|
} else {
|
|
|
|
quote! {
|
2022-05-12 15:51:57 -04:00
|
|
|
match #core::serde_v8::to_v8(scope, result) {
|
2022-03-19 10:59:44 -04:00
|
|
|
Ok(ret) => rv.set(ret),
|
|
|
|
Err(err) => #core::_ops::throw_type_error(
|
|
|
|
scope,
|
|
|
|
format!("Error serializing return: {}", #core::anyhow::Error::from(err)),
|
|
|
|
),
|
|
|
|
};
|
2022-03-14 13:44:15 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-05-12 15:51:57 -04:00
|
|
|
if !is_result(output) {
|
|
|
|
return ok_block;
|
|
|
|
}
|
2022-05-12 13:13:25 -04:00
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
quote! {
|
|
|
|
match result {
|
2022-05-12 15:51:57 -04:00
|
|
|
Ok(result) => {
|
2022-03-14 13:44:15 -04:00
|
|
|
#ok_block
|
|
|
|
},
|
|
|
|
Err(err) => {
|
2022-08-11 05:57:20 -04:00
|
|
|
let exception = #core::error::to_v8_error(scope, op_state.get_error_class_fn, &err);
|
|
|
|
scope.throw_exception(exception);
|
2022-03-14 13:44:15 -04:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 15:51:57 -04:00
|
|
|
fn is_void(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(ty).is_empty()
|
|
|
|
}
|
|
|
|
|
2022-05-12 13:13:25 -04:00
|
|
|
fn is_result(ty: impl ToTokens) -> bool {
|
|
|
|
let tokens = tokens(ty);
|
|
|
|
if tokens.trim_start_matches("-> ").starts_with("Result <") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// Detect `io::Result<...>`, `anyhow::Result<...>`, etc...
|
|
|
|
// i.e: Result aliases/shorthands which are unfortunately "opaque" at macro-time
|
|
|
|
match tokens.find(":: Result <") {
|
|
|
|
Some(idx) => !tokens.split_at(idx).0.contains('<'),
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:01:14 -04:00
|
|
|
fn is_string(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(ty) == "String"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_option_string(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(ty) == "Option < String >"
|
|
|
|
}
|
|
|
|
|
2022-09-07 06:51:47 -04:00
|
|
|
enum SliceType {
|
|
|
|
U8,
|
|
|
|
U8Mut,
|
2022-09-17 07:18:15 -04:00
|
|
|
U32Mut,
|
2022-09-07 06:51:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn is_ref_slice(ty: impl ToTokens) -> Option<SliceType> {
|
|
|
|
if is_u8_slice(&ty) {
|
|
|
|
return Some(SliceType::U8);
|
|
|
|
}
|
|
|
|
if is_u8_slice_mut(&ty) {
|
|
|
|
return Some(SliceType::U8Mut);
|
|
|
|
}
|
2022-09-17 07:18:15 -04:00
|
|
|
if is_u32_slice_mut(&ty) {
|
|
|
|
return Some(SliceType::U32Mut);
|
|
|
|
}
|
2022-09-07 06:51:47 -04:00
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_u8_slice(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(ty) == "& [u8]"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_u8_slice_mut(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(ty) == "& mut [u8]"
|
|
|
|
}
|
|
|
|
|
2022-09-17 07:18:15 -04:00
|
|
|
fn is_u32_slice_mut(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(ty) == "& mut [u32]"
|
|
|
|
}
|
|
|
|
|
2022-09-01 06:23:06 -04:00
|
|
|
fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(&ty).contains("Option < & mut FastApiCallbackOptions")
|
|
|
|
}
|
|
|
|
|
2022-07-01 13:29:24 -04:00
|
|
|
/// Detects if the type can be set using `rv.set_uint32` fast path
|
|
|
|
fn is_u32_rv(ty: impl ToTokens) -> bool {
|
|
|
|
["u32", "u8", "u16"].iter().any(|&s| tokens(&ty) == s) || is_resource_id(&ty)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Detects if the type is of the format Result<u32/u8/u16, Err>
|
|
|
|
fn is_u32_rv_result(ty: impl ToTokens) -> bool {
|
|
|
|
is_result(&ty)
|
|
|
|
&& (tokens(&ty).contains("Result < u32")
|
|
|
|
|| tokens(&ty).contains("Result < u8")
|
|
|
|
|| tokens(&ty).contains("Result < u16")
|
|
|
|
|| is_resource_id(&ty))
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:44:15 -04:00
|
|
|
/// Detects if a type is of the form Result<(), Err>
|
2022-05-12 14:22:42 -04:00
|
|
|
fn is_unit_result(ty: impl ToTokens) -> bool {
|
|
|
|
is_result(&ty) && tokens(&ty).contains("Result < ()")
|
2022-03-14 13:44:15 -04:00
|
|
|
}
|
2022-03-15 19:33:46 -04:00
|
|
|
|
2022-07-01 13:29:24 -04:00
|
|
|
fn is_resource_id(arg: impl ToTokens) -> bool {
|
|
|
|
static RE: Lazy<Regex> =
|
|
|
|
Lazy::new(|| Regex::new(r#": (?:deno_core :: )?ResourceId$"#).unwrap());
|
|
|
|
RE.is_match(&tokens(arg))
|
|
|
|
}
|
|
|
|
|
2022-08-21 08:07:53 -04:00
|
|
|
fn is_mut_ref_opstate(arg: impl ToTokens) -> bool {
|
2022-06-16 13:40:26 -04:00
|
|
|
static RE: Lazy<Regex> =
|
|
|
|
Lazy::new(|| Regex::new(r#": & mut (?:deno_core :: )?OpState$"#).unwrap());
|
|
|
|
RE.is_match(&tokens(arg))
|
2022-03-15 19:33:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn is_rc_refcell_opstate(arg: &syn::FnArg) -> bool {
|
2022-06-16 13:40:26 -04:00
|
|
|
static RE: Lazy<Regex> = Lazy::new(|| {
|
|
|
|
Regex::new(r#": Rc < RefCell < (?:deno_core :: )?OpState > >$"#).unwrap()
|
|
|
|
});
|
|
|
|
RE.is_match(&tokens(arg))
|
2022-03-15 19:33:46 -04:00
|
|
|
}
|
|
|
|
|
2022-05-12 06:36:09 -04:00
|
|
|
fn is_handle_scope(arg: &syn::FnArg) -> bool {
|
2022-06-16 13:40:26 -04:00
|
|
|
static RE: Lazy<Regex> = Lazy::new(|| {
|
|
|
|
Regex::new(r#": & mut (?:deno_core :: )?v8 :: HandleScope(?: < '\w+ >)?$"#)
|
|
|
|
.unwrap()
|
|
|
|
});
|
|
|
|
RE.is_match(&tokens(arg))
|
2022-05-12 06:36:09 -04:00
|
|
|
}
|
|
|
|
|
2022-06-07 22:28:26 -04:00
|
|
|
fn is_future(ty: impl ToTokens) -> bool {
|
|
|
|
tokens(&ty).contains("impl Future < Output =")
|
|
|
|
}
|
|
|
|
|
2022-03-15 19:33:46 -04:00
|
|
|
fn tokens(x: impl ToTokens) -> String {
|
|
|
|
x.to_token_stream().to_string()
|
|
|
|
}
|
2022-06-07 05:25:10 -04:00
|
|
|
|
|
|
|
fn exclude_lifetime_params(
|
|
|
|
generic_params: &Punctuated<GenericParam, Comma>,
|
|
|
|
) -> Punctuated<GenericParam, Comma> {
|
|
|
|
generic_params
|
|
|
|
.iter()
|
|
|
|
.filter(|t| !tokens(t).starts_with('\''))
|
|
|
|
.cloned()
|
|
|
|
.collect::<Punctuated<GenericParam, Comma>>()
|
|
|
|
}
|