summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock30
-rw-r--r--ops/Cargo.toml3
-rw-r--r--ops/attrs.rs37
-rw-r--r--ops/deno.rs32
-rw-r--r--ops/fast_call.rs399
-rw-r--r--ops/lib.rs770
-rw-r--r--ops/optimizer.rs600
-rw-r--r--ops/optimizer_tests/callback_options.expected8
-rw-r--r--ops/optimizer_tests/callback_options.out25
-rw-r--r--ops/optimizer_tests/callback_options.rs5
-rw-r--r--ops/optimizer_tests/incompatible_1.expected1
-rw-r--r--ops/optimizer_tests/incompatible_1.rs9
-rw-r--r--ops/optimizer_tests/op_state.expected8
-rw-r--r--ops/optimizer_tests/op_state.out34
-rw-r--r--ops/optimizer_tests/op_state.rs3
-rw-r--r--ops/optimizer_tests/op_state_basic1.expected8
-rw-r--r--ops/optimizer_tests/op_state_basic1.out35
-rw-r--r--ops/optimizer_tests/op_state_basic1.rs3
-rw-r--r--ops/optimizer_tests/op_state_generics.expected8
-rw-r--r--ops/optimizer_tests/op_state_generics.out39
-rw-r--r--ops/optimizer_tests/op_state_generics.rs5
-rw-r--r--ops/optimizer_tests/op_state_result.expected8
-rw-r--r--ops/optimizer_tests/op_state_result.out42
-rw-r--r--ops/optimizer_tests/op_state_result.rs3
-rw-r--r--ops/optimizer_tests/op_state_with_transforms.expected8
-rw-r--r--ops/optimizer_tests/op_state_with_transforms.out47
-rw-r--r--ops/optimizer_tests/op_state_with_transforms.rs5
-rw-r--r--ops/optimizer_tests/opstate_with_arity.expected8
-rw-r--r--ops/optimizer_tests/opstate_with_arity.out44
-rw-r--r--ops/optimizer_tests/opstate_with_arity.rs3
-rw-r--r--ops/optimizer_tests/param_mut_binding_warning.expected1
-rw-r--r--ops/optimizer_tests/param_mut_binding_warning.rs11
-rw-r--r--ops/optimizer_tests/serde_v8_value.expected8
-rw-r--r--ops/optimizer_tests/serde_v8_value.out26
-rw-r--r--ops/optimizer_tests/serde_v8_value.rs3
-rw-r--r--ops/optimizer_tests/u64_result.expected1
-rw-r--r--ops/optimizer_tests/u64_result.rs5
-rw-r--r--ops/tests/compile_fail/unsupported.stderr55
-rwxr-xr-xtools/lint.js10
39 files changed, 1702 insertions, 648 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1b4a242ec..df49cb67b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1166,11 +1166,14 @@ version = "0.36.0"
dependencies = [
"deno_core",
"once_cell",
+ "pmutil",
+ "prettyplease",
"proc-macro-crate",
"proc-macro2 1.0.43",
"quote 1.0.21",
"regex",
"syn 1.0.99",
+ "testing_macros",
"trybuild",
]
@@ -3338,6 +3341,16 @@ dependencies = [
]
[[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]]
name = "proc-macro-crate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4760,6 +4773,23 @@ dependencies = [
]
[[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]]
name = "text-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/ops/Cargo.toml b/ops/Cargo.toml
index 408d597d1..56c2f916a 100644
--- a/ops/Cargo.toml
+++ b/ops/Cargo.toml
@@ -12,6 +12,7 @@ proc-macro = true
[dependencies]
once_cell = "1.10.0"
+pmutil = "0.5.3"
proc-macro-crate = "1.1.3"
proc-macro2 = "1"
quote = "1"
@@ -20,4 +21,6 @@ syn = { version = "1", features = ["full", "extra-traits"] }
[dev-dependencies]
deno_core = { path = "../core" }
+prettyplease = "0.1.21"
+testing_macros = "0.2.7"
trybuild = "1.0.61"
diff --git a/ops/attrs.rs b/ops/attrs.rs
new file mode 100644
index 000000000..95374ef36
--- /dev/null
+++ b/ops/attrs.rs
@@ -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"),
+ })
+ }
+}
diff --git a/ops/deno.rs b/ops/deno.rs
new file mode 100644
index 000000000..67af603e9
--- /dev/null
+++ b/ops/deno.rs
@@ -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)
+ }
+ }
+}
diff --git a/ops/fast_call.rs b/ops/fast_call.rs
new file mode 100644
index 000000000..4b5ba6e9b
--- /dev/null
+++ b/ops/fast_call.rs
@@ -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);
+ }
+ }
+}
diff --git a/ops/lib.rs b/ops/lib.rs
index 44f783280..7d4e77f90 100644
--- a/ops/lib.rs
+++ b/ops/lib.rs
@@ -1,189 +1,192 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use core::panic;
+use attrs::Attributes;
use once_cell::sync::Lazy;
+use optimizer::{BailoutReason, Optimizer};
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;
-use quote::format_ident;
-use quote::quote;
-use quote::ToTokens;
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::{quote, ToTokens};
use regex::Regex;
-use std::collections::HashMap;
-use syn::punctuated::Punctuated;
-use syn::token::Comma;
-use syn::FnArg;
-use syn::GenericParam;
-use syn::Ident;
+use syn::{
+ parse, parse_macro_input, punctuated::Punctuated, token::Comma, FnArg,
+ GenericParam, Ident, ItemFn, Lifetime, LifetimeDef,
+};
+
+mod attrs;
+mod deno;
+mod fast_call;
+mod optimizer;
#[cfg(test)]
mod tests;
-// 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.
-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)
- }
- }
-}
+const SCOPE_LIFETIME: &str = "'scope";
-#[derive(Copy, Clone, Debug, Default)]
-struct MacroArgs {
- is_unstable: bool,
- is_v8: bool,
- must_be_fast: bool,
- deferred: bool,
+/// Add the 'scope lifetime to the function signature.
+fn add_scope_lifetime(func: &mut ItemFn) {
+ let span = Span::call_site();
+ let lifetime = LifetimeDef::new(Lifetime::new(SCOPE_LIFETIME, span));
+ let generics = &mut func.sig.generics;
+ if !generics.lifetimes().any(|def| *def == lifetime) {
+ generics.params.push(GenericParam::Lifetime(lifetime));
+ }
}
-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();
- for var in vars.iter() {
- if !["unstable", "v8", "fast", "deferred"].contains(var) {
- return Err(syn::Error::new(
- input.span(),
- "Ops expect #[op] or #[op(unstable)]",
- ));
- }
+struct Op {
+ orig: ItemFn,
+ item: ItemFn,
+ /// Is this an async op?
+ /// - `async fn`
+ /// - returns a Future
+ is_async: bool,
+ type_params: Punctuated<GenericParam, Comma>,
+ // optimizer: Optimizer,
+ core: TokenStream2,
+ attrs: Attributes,
+}
+
+impl Op {
+ fn new(mut item: ItemFn, attrs: Attributes) -> Self {
+ add_scope_lifetime(&mut item);
+
+ // Preserve the original function. Change the name to `call`.
+ //
+ // impl op_foo {
+ // fn call() {}
+ // ...
+ // }
+ let mut orig = item.clone();
+ orig.sig.ident = Ident::new("call", Span::call_site());
+
+ 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,
}
- 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]
-pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
- let margs = syn::parse_macro_input!(attr as MacroArgs);
- let MacroArgs {
- is_unstable,
- is_v8,
- 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
- };
+ 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 core = core_import();
+ 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 asyncness = func.sig.asyncness.is_some();
- let is_async = asyncness || is_future(&func.sig.output);
+ let is_v8 = attrs.is_v8;
+ let is_unstable = attrs.is_unstable;
- // 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 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)
+ }
- 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)
- };
+ pub fn v8_fn_ptr #generics () -> #core::v8::FunctionCallback #where_clause {
+ use #core::v8::MapFnTo;
+ Self::v8_func::<#type_params>.map_fn_to()
+ }
- 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 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,
+ }
+ }
- pub fn v8_fn_ptr #generics () -> #core::v8::FunctionCallback #where_clause {
- use #core::v8::MapFnTo;
- Self::v8_func::<#type_params>.map_fn_to()
- }
+ #[inline]
+ #[allow(clippy::too_many_arguments)]
+ #orig
- 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,
+ pub fn v8_func #generics (
+ scope: &mut #core::v8::HandleScope<'scope>,
+ args: #core::v8::FunctionCallbackArguments,
+ mut rv: #core::v8::ReturnValue,
+ ) #where_clause {
+ #v8_body
}
}
- #[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
- }
+ #impl_and_fn
}
+ }
+}
- #fast_impl
- }.into()
+#[proc_macro_attribute]
+pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
+ let margs = parse_macro_input!(attr as Attributes);
+ let func = parse::<ItemFn>(item).expect("expected a function");
+ let op = Op::new(func, margs);
+ op.gen().into()
}
/// Generate the body of a v8 func for an async op
fn codegen_v8_async(
core: &TokenStream2,
f: &syn::ItemFn,
- margs: MacroArgs,
+ margs: Attributes,
asyncness: bool,
deferred: bool,
) -> (TokenStream2, usize) {
- let MacroArgs { is_v8, .. } = margs;
+ let Attributes { is_v8, .. } = margs;
let special_args = f
.sig
.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
fn codegen_v8_sync(
core: &TokenStream2,
f: &syn::ItemFn,
- margs: MacroArgs,
+ margs: Attributes,
has_fallible_fast_call: bool,
) -> (TokenStream2, usize) {
- let MacroArgs { is_v8, .. } = margs;
+ let Attributes { is_v8, .. } = margs;
let special_args = f
.sig
.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)
type ArgumentDecl = (TokenStream2, TokenStream2, usize);
diff --git a/ops/optimizer.rs b/ops/optimizer.rs
new file mode 100644
index 000000000..3e3887549
--- /dev/null
+++ b/ops/optimizer.rs
@@ -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);
+ }
+ }
+}
diff --git a/ops/optimizer_tests/callback_options.expected b/ops/optimizer_tests/callback_options.expected
new file mode 100644
index 000000000..063032bb5
--- /dev/null
+++ b/ops/optimizer_tests/callback_options.expected
@@ -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: {}
diff --git a/ops/optimizer_tests/callback_options.out b/ops/optimizer_tests/callback_options.out
new file mode 100644
index 000000000..426fe0c4a
--- /dev/null
+++ b/ops/optimizer_tests/callback_options.out
@@ -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
+}
diff --git a/ops/optimizer_tests/callback_options.rs b/ops/optimizer_tests/callback_options.rs
new file mode 100644
index 000000000..c210171d2
--- /dev/null
+++ b/ops/optimizer_tests/callback_options.rs
@@ -0,0 +1,5 @@
+fn op_fallback(options: Option<&mut FastApiCallbackOptions>) {
+ if let Some(options) = options {
+ options.fallback = true;
+ }
+}
diff --git a/ops/optimizer_tests/incompatible_1.expected b/ops/optimizer_tests/incompatible_1.expected
new file mode 100644
index 000000000..250ff1022
--- /dev/null
+++ b/ops/optimizer_tests/incompatible_1.expected
@@ -0,0 +1 @@
+FastUnsupportedParamType \ No newline at end of file
diff --git a/ops/optimizer_tests/incompatible_1.rs b/ops/optimizer_tests/incompatible_1.rs
new file mode 100644
index 000000000..326189aa1
--- /dev/null
+++ b/ops/optimizer_tests/incompatible_1.rs
@@ -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(())
+}
diff --git a/ops/optimizer_tests/op_state.expected b/ops/optimizer_tests/op_state.expected
new file mode 100644
index 000000000..f23bf764a
--- /dev/null
+++ b/ops/optimizer_tests/op_state.expected
@@ -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: {}
diff --git a/ops/optimizer_tests/op_state.out b/ops/optimizer_tests/op_state.out
new file mode 100644
index 000000000..a98db68d8
--- /dev/null
+++ b/ops/optimizer_tests/op_state.out
@@ -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
+}
diff --git a/ops/optimizer_tests/op_state.rs b/ops/optimizer_tests/op_state.rs
new file mode 100644
index 000000000..04e9a886d
--- /dev/null
+++ b/ops/optimizer_tests/op_state.rs
@@ -0,0 +1,3 @@
+fn op_set_exit_code(state: &mut OpState, code: i32) {
+ state.borrow_mut::<ExitCode>().set(code);
+}
diff --git a/ops/optimizer_tests/op_state_basic1.expected b/ops/optimizer_tests/op_state_basic1.expected
new file mode 100644
index 000000000..3639959b8
--- /dev/null
+++ b/ops/optimizer_tests/op_state_basic1.expected
@@ -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: {}
diff --git a/ops/optimizer_tests/op_state_basic1.out b/ops/optimizer_tests/op_state_basic1.out
new file mode 100644
index 000000000..0f03f2c58
--- /dev/null
+++ b/ops/optimizer_tests/op_state_basic1.out
@@ -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
+}
diff --git a/ops/optimizer_tests/op_state_basic1.rs b/ops/optimizer_tests/op_state_basic1.rs
new file mode 100644
index 000000000..9c89b41ce
--- /dev/null
+++ b/ops/optimizer_tests/op_state_basic1.rs
@@ -0,0 +1,3 @@
+fn foo(state: &mut OpState, a: u32, b: u32) -> u32 {
+ a + b
+}
diff --git a/ops/optimizer_tests/op_state_generics.expected b/ops/optimizer_tests/op_state_generics.expected
new file mode 100644
index 000000000..83e938502
--- /dev/null
+++ b/ops/optimizer_tests/op_state_generics.expected
@@ -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: {}
diff --git a/ops/optimizer_tests/op_state_generics.out b/ops/optimizer_tests/op_state_generics.out
new file mode 100644
index 000000000..d141c7445
--- /dev/null
+++ b/ops/optimizer_tests/op_state_generics.out
@@ -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
+}
diff --git a/ops/optimizer_tests/op_state_generics.rs b/ops/optimizer_tests/op_state_generics.rs
new file mode 100644
index 000000000..7fa498981
--- /dev/null
+++ b/ops/optimizer_tests/op_state_generics.rs
@@ -0,0 +1,5 @@
+pub fn op_foo<SP>(state: &mut OpState)
+where
+ SP: SomePermission + 'static,
+{
+}
diff --git a/ops/optimizer_tests/op_state_result.expected b/ops/optimizer_tests/op_state_result.expected
new file mode 100644
index 000000000..16e71c38c
--- /dev/null
+++ b/ops/optimizer_tests/op_state_result.expected
@@ -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: {}
diff --git a/ops/optimizer_tests/op_state_result.out b/ops/optimizer_tests/op_state_result.out
new file mode 100644
index 000000000..5174dd7f2
--- /dev/null
+++ b/ops/optimizer_tests/op_state_result.out
@@ -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()
+ }
+ }
+}
diff --git a/ops/optimizer_tests/op_state_result.rs b/ops/optimizer_tests/op_state_result.rs
new file mode 100644
index 000000000..331005c08
--- /dev/null
+++ b/ops/optimizer_tests/op_state_result.rs
@@ -0,0 +1,3 @@
+fn foo(state: &mut OpState, a: u32, b: u32) -> Result<u32, AnyError> {
+ Ok(a + b)
+}
diff --git a/ops/optimizer_tests/op_state_with_transforms.expected b/ops/optimizer_tests/op_state_with_transforms.expected
new file mode 100644
index 000000000..388d396f5
--- /dev/null
+++ b/ops/optimizer_tests/op_state_with_transforms.expected
@@ -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 }}
diff --git a/ops/optimizer_tests/op_state_with_transforms.out b/ops/optimizer_tests/op_state_with_transforms.out
new file mode 100644
index 000000000..f981748be
--- /dev/null
+++ b/ops/optimizer_tests/op_state_with_transforms.out
@@ -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
+}
diff --git a/ops/optimizer_tests/op_state_with_transforms.rs b/ops/optimizer_tests/op_state_with_transforms.rs
new file mode 100644
index 000000000..4e7e616f3
--- /dev/null
+++ b/ops/optimizer_tests/op_state_with_transforms.rs
@@ -0,0 +1,5 @@
+pub fn op_now<TP>(state: &mut OpState, buf: &mut [u8])
+where
+ TP: TimersPermission + 'static,
+{
+}
diff --git a/ops/optimizer_tests/opstate_with_arity.expected b/ops/optimizer_tests/opstate_with_arity.expected
new file mode 100644
index 000000000..6259f3e28
--- /dev/null
+++ b/ops/optimizer_tests/opstate_with_arity.expected
@@ -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: {}
diff --git a/ops/optimizer_tests/opstate_with_arity.out b/ops/optimizer_tests/opstate_with_arity.out
new file mode 100644
index 000000000..20b7769e7
--- /dev/null
+++ b/ops/optimizer_tests/opstate_with_arity.out
@@ -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()
+ }
+ }
+}
diff --git a/ops/optimizer_tests/opstate_with_arity.rs b/ops/optimizer_tests/opstate_with_arity.rs
new file mode 100644
index 000000000..7212ca975
--- /dev/null
+++ b/ops/optimizer_tests/opstate_with_arity.rs
@@ -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)
+}
diff --git a/ops/optimizer_tests/param_mut_binding_warning.expected b/ops/optimizer_tests/param_mut_binding_warning.expected
new file mode 100644
index 000000000..250ff1022
--- /dev/null
+++ b/ops/optimizer_tests/param_mut_binding_warning.expected
@@ -0,0 +1 @@
+FastUnsupportedParamType \ No newline at end of file
diff --git a/ops/optimizer_tests/param_mut_binding_warning.rs b/ops/optimizer_tests/param_mut_binding_warning.rs
new file mode 100644
index 000000000..c47122728
--- /dev/null
+++ b/ops/optimizer_tests/param_mut_binding_warning.rs
@@ -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)
+}
diff --git a/ops/optimizer_tests/serde_v8_value.expected b/ops/optimizer_tests/serde_v8_value.expected
new file mode 100644
index 000000000..5acd38655
--- /dev/null
+++ b/ops/optimizer_tests/serde_v8_value.expected
@@ -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 }}
diff --git a/ops/optimizer_tests/serde_v8_value.out b/ops/optimizer_tests/serde_v8_value.out
new file mode 100644
index 000000000..8c7630547
--- /dev/null
+++ b/ops/optimizer_tests/serde_v8_value.out
@@ -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
+}
diff --git a/ops/optimizer_tests/serde_v8_value.rs b/ops/optimizer_tests/serde_v8_value.rs
new file mode 100644
index 000000000..c986930d9
--- /dev/null
+++ b/ops/optimizer_tests/serde_v8_value.rs
@@ -0,0 +1,3 @@
+fn op_is_proxy(value: serde_v8::Value) -> bool {
+ value.v8_value.is_proxy()
+}
diff --git a/ops/optimizer_tests/u64_result.expected b/ops/optimizer_tests/u64_result.expected
new file mode 100644
index 000000000..250ff1022
--- /dev/null
+++ b/ops/optimizer_tests/u64_result.expected
@@ -0,0 +1 @@
+FastUnsupportedParamType \ No newline at end of file
diff --git a/ops/optimizer_tests/u64_result.rs b/ops/optimizer_tests/u64_result.rs
new file mode 100644
index 000000000..1cc783db8
--- /dev/null
+++ b/ops/optimizer_tests/u64_result.rs
@@ -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)
+}
diff --git a/ops/tests/compile_fail/unsupported.stderr b/ops/tests/compile_fail/unsupported.stderr
index 9a1d1462d..5dccead46 100644
--- a/ops/tests/compile_fail/unsupported.stderr
+++ b/ops/tests/compile_fail/unsupported.stderr
@@ -1,39 +1,30 @@
-error: custom attribute panicked
- --> 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
+error: fast async calls are not supported
--> tests/compile_fail/unsupported.rs:22:1
|
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`
- --> tests/compile_fail/unsupported.rs:15:5
- |
-15 | use deno_core::v8::fast_api::FastApiCallbackOptions;
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+error[E0277]: the trait bound `&mut FastApiCallbackOptions<'_>: Deserialize<'_>` is not satisfied
+ --> tests/compile_fail/unsupported.rs:17:1
|
- = note: `#[warn(unused_imports)]` on by default
+17 | #[op(fast)]
+ | ^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `&mut FastApiCallbackOptions<'_>`
+ |
+ = 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)
diff --git a/tools/lint.js b/tools/lint.js
index 65f8888b7..11e912f49 100755
--- a/tools/lint.js
+++ b/tools/lint.js
@@ -112,7 +112,15 @@ async function clippy() {
}
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",
stderr: "inherit",
});