summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2022-07-22 17:54:22 +0530
committerGitHub <noreply@github.com>2022-07-22 17:54:22 +0530
commit03dc3b8972f460e40d0b75fc3207cae9fe4f60da (patch)
tree410bc92530e34ca2eee8a154f705be85ae9641b3
parent244c00d95b7ec8f30a5e81b743b4b618049b6c37 (diff)
feat(ops): V8 Fast Calls (#15122)
Co-authored-by: Bartek IwaƄczuk <biwanczuk@gmail.com>
-rw-r--r--Cargo.lock23
-rw-r--r--cli/bench/deno_common.js25
-rw-r--r--core/01_core.js4
-rw-r--r--core/bindings.rs80
-rw-r--r--core/extensions.rs9
-rw-r--r--core/lib.rs3
-rw-r--r--core/ops_builtin.rs8
-rw-r--r--core/runtime.rs19
-rw-r--r--ext/net/lib.rs4
-rw-r--r--ext/web/02_timers.js4
-rw-r--r--ext/web/timers.rs2
-rw-r--r--ops/Cargo.toml4
-rw-r--r--ops/README.md28
-rw-r--r--ops/lib.rs250
-rw-r--r--ops/tests/compile_fail/unsupported.rs27
-rw-r--r--ops/tests/compile_fail/unsupported.stderr31
-rw-r--r--ops/tests/mod.rs5
17 files changed, 494 insertions, 32 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c3e84f40b..7489f9ba1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1118,12 +1118,14 @@ dependencies = [
name = "deno_ops"
version = "0.22.0"
dependencies = [
+ "deno_core",
"once_cell",
"proc-macro-crate",
"proc-macro2 1.0.39",
"quote 1.0.18",
"regex",
"syn 1.0.96",
+ "trybuild",
]
[[package]]
@@ -1898,6 +1900,12 @@ dependencies = [
]
[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
name = "glow"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4954,6 +4962,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
+name = "trybuild"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "764b9e244b482a9b81bde596aa37aa6f1347bf8007adab25e59f901b32b4e0a0"
+dependencies = [
+ "glob",
+ "once_cell",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "termcolor",
+ "toml",
+]
+
+[[package]]
name = "tungstenite"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/bench/deno_common.js b/cli/bench/deno_common.js
index c31ccd981..c02a6c883 100644
--- a/cli/bench/deno_common.js
+++ b/cli/bench/deno_common.js
@@ -5,8 +5,31 @@ Deno.bench("date_now", { n: 5e5 }, () => {
Date.now();
});
+// Fast API calls
+{
+ // deno-lint-ignore camelcase
+ const { op_add } = Deno.core.ops;
+ // deno-lint-ignore no-inner-declarations
+ function add(a, b) {
+ return op_add.call(a, b);
+ }
+ // deno-lint-ignore no-inner-declarations
+ function addJS(a, b) {
+ return a + b;
+ }
+ Deno.bench("op_add", { n: 1e7 }, () => add(1, 2));
+ Deno.bench("add_js", { n: 1e7 }, () => addJS(1, 2));
+}
+
+// deno-lint-ignore camelcase
+const { op_void_sync } = Deno.core.ops;
+function sync() {
+ return op_void_sync.call();
+}
+sync(); // Warmup
+console.log(sync());
// Void ops measure op-overhead
-Deno.bench("op_void_sync", { n: 1e7 }, () => Deno.core.opSync("op_void_sync"));
+Deno.bench("op_void_sync", { n: 1e7 }, () => sync());
Deno.bench(
"op_void_async",
diff --git a/core/01_core.js b/core/01_core.js
index 21376b695..354540fb7 100644
--- a/core/01_core.js
+++ b/core/01_core.js
@@ -147,7 +147,7 @@
function opAsync(opName, ...args) {
const promiseId = nextPromiseId++;
- const maybeError = ops[opName](promiseId, ...args);
+ const maybeError = ops[opName].call(promiseId, ...args);
// Handle sync error (e.g: error parsing args)
if (maybeError) return unwrapOpResult(maybeError);
let p = PromisePrototypeThen(setPromise(promiseId), unwrapOpResult);
@@ -167,7 +167,7 @@
}
function opSync(opName, ...args) {
- return unwrapOpResult(ops[opName](...args));
+ return unwrapOpResult(ops[opName].call(...args));
}
function refOp(promiseId) {
diff --git a/core/bindings.rs b/core/bindings.rs
index 6fa9f745b..afbbcee4e 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -9,17 +9,36 @@ use crate::modules::ModuleMap;
use crate::ops::OpCtx;
use crate::JsRuntime;
use log::debug;
-use once_cell::sync::Lazy;
use std::option::Option;
use std::os::raw::c_void;
+use v8::fast_api::FastFunction;
use v8::MapFnTo;
-pub static EXTERNAL_REFERENCES: Lazy<v8::ExternalReferences> =
- Lazy::new(|| {
- v8::ExternalReferences::new(&[v8::ExternalReference {
+pub fn external_references(ops: &[OpCtx]) -> v8::ExternalReferences {
+ let mut references = vec![
+ v8::ExternalReference {
function: call_console.map_fn_to(),
- }])
- });
+ },
+ v8::ExternalReference {
+ pointer: ops as *const _ as _,
+ },
+ ];
+
+ for ctx in ops {
+ let ctx_ptr = ctx as *const OpCtx as _;
+ references.push(v8::ExternalReference { pointer: ctx_ptr });
+ references.push(v8::ExternalReference {
+ function: ctx.decl.v8_fn_ptr,
+ });
+ if let Some(fast_fn) = &ctx.decl.fast_fn {
+ references.push(v8::ExternalReference {
+ pointer: fast_fn.function() as _,
+ });
+ }
+ }
+
+ v8::ExternalReferences::new(&references)
+}
// TODO(nayeemrmn): Move to runtime and/or make `pub(crate)`.
pub fn script_origin<'a>(
@@ -82,7 +101,8 @@ pub fn initialize_context<'s>(
// Grab the Deno.core.ops object & init it
let ops_obj = JsRuntime::grab_global::<v8::Object>(scope, "Deno.core.ops")
.expect("Deno.core.ops to exist");
- initialize_ops(scope, ops_obj, op_ctxs);
+ initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
+
return scope.escape(context);
}
@@ -94,7 +114,8 @@ pub fn initialize_context<'s>(
// Bind functions to Deno.core.ops.*
let ops_obj = JsRuntime::ensure_objs(scope, global, "Deno.core.ops").unwrap();
- initialize_ops(scope, ops_obj, op_ctxs);
+
+ initialize_ops(scope, ops_obj, op_ctxs, snapshot_loaded);
scope.escape(context)
}
@@ -102,10 +123,32 @@ fn initialize_ops(
scope: &mut v8::HandleScope,
ops_obj: v8::Local<v8::Object>,
op_ctxs: &[OpCtx],
+ snapshot_loaded: bool,
) {
for ctx in op_ctxs {
let ctx_ptr = ctx as *const OpCtx as *const c_void;
- set_func_raw(scope, ops_obj, ctx.decl.name, ctx.decl.v8_fn_ptr, ctx_ptr);
+
+ let object_template = v8::ObjectTemplate::new(scope);
+ assert!(object_template.set_internal_field_count(
+ (crate::runtime::V8_WRAPPER_OBJECT_INDEX + 1) as usize
+ ));
+
+ let method_obj = object_template.new_instance(scope).unwrap();
+ method_obj.set_aligned_pointer_in_internal_field(
+ crate::runtime::V8_WRAPPER_OBJECT_INDEX,
+ ctx_ptr,
+ );
+ set_func_raw(
+ scope,
+ method_obj,
+ "call",
+ ctx.decl.v8_fn_ptr,
+ ctx_ptr,
+ &ctx.decl.fast_fn,
+ snapshot_loaded,
+ );
+ let method_key = v8::String::new(scope, ctx.decl.name).unwrap();
+ ops_obj.set(scope, method_key.into(), method_obj.into());
}
}
@@ -129,13 +172,24 @@ pub fn set_func_raw(
name: &'static str,
callback: v8::FunctionCallback,
external_data: *const c_void,
+ fast_function: &Option<Box<dyn FastFunction>>,
+ snapshot_loaded: bool,
) {
let key = v8::String::new(scope, name).unwrap();
let external = v8::External::new(scope, external_data as *mut c_void);
- let val = v8::Function::builder_raw(callback)
- .data(external.into())
- .build(scope)
- .unwrap();
+ let builder =
+ v8::FunctionTemplate::builder_raw(callback).data(external.into());
+ let templ = if let Some(fast_function) = fast_function {
+ if !snapshot_loaded {
+ builder.build(scope)
+ } else {
+ // TODO(@littledivy): Support fast api overloads in ops.
+ builder.build_fast(scope, &**fast_function, None)
+ }
+ } else {
+ builder.build(scope)
+ };
+ let val = templ.get_function(scope).unwrap();
val.set_name(key);
obj.set(scope, key.into(), val.into());
}
diff --git a/core/extensions.rs b/core/extensions.rs
index ce6957875..39b4471bf 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -2,6 +2,7 @@
use crate::OpState;
use anyhow::Error;
use std::{cell::RefCell, rc::Rc, task::Context};
+use v8::fast_api::FastFunction;
pub type SourcePair = (&'static str, &'static str);
pub type OpFnRef = v8::FunctionCallback;
@@ -9,14 +10,16 @@ pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl;
pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), Error>;
pub type OpEventLoopFn = dyn Fn(Rc<RefCell<OpState>>, &mut Context) -> bool;
-#[derive(Clone, Copy)]
+pub trait FastFunctionSignature {}
+
pub struct OpDecl {
pub name: &'static str,
pub v8_fn_ptr: OpFnRef,
pub enabled: bool,
- pub is_async: bool, // TODO(@AaronO): enum sync/async/fast ?
+ pub is_async: bool,
pub is_unstable: bool,
pub is_v8: bool,
+ pub fast_fn: Option<Box<dyn FastFunction>>,
}
impl OpDecl {
@@ -32,7 +35,7 @@ impl OpDecl {
#[derive(Default)]
pub struct Extension {
js_files: Option<Vec<SourcePair>>,
- ops: Option<Vec<OpDecl>>,
+ pub ops: Option<Vec<OpDecl>>,
opstate_fn: Option<Box<OpStateFn>>,
middleware_fn: Option<Box<OpMiddlewareFn>>,
event_loop_middleware: Option<Box<OpEventLoopFn>>,
diff --git a/core/lib.rs b/core/lib.rs
index ab22392c4..a77705ff3 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -113,9 +113,12 @@ pub fn v8_version() -> &'static str {
pub mod _ops {
pub use super::bindings::throw_type_error;
pub use super::error_codes::get_error_code;
+ pub use super::extensions::FastFunctionSignature;
pub use super::ops::to_op_result;
pub use super::ops::OpCtx;
pub use super::runtime::queue_async_op;
+ pub use super::runtime::V8_WRAPPER_OBJECT_INDEX;
+ pub use super::runtime::V8_WRAPPER_TYPE_INDEX;
}
/// A helper macro that will return a call site in Rust code. Should be
diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs
index a42c0bae8..2e911e415 100644
--- a/core/ops_builtin.rs
+++ b/core/ops_builtin.rs
@@ -31,6 +31,7 @@ pub(crate) fn init_builtins() -> Extension {
op_wasm_streaming_set_url::decl(),
op_void_sync::decl(),
op_void_async::decl(),
+ op_add::decl(),
// // TODO(@AaronO): track IO metrics for builtin streams
op_read::decl(),
op_write::decl(),
@@ -54,7 +55,12 @@ pub fn op_resources(state: &mut OpState) -> Vec<(ResourceId, String)> {
.collect()
}
-#[op]
+#[op(fast)]
+fn op_add(a: i32, b: i32) -> i32 {
+ a + b
+}
+
+#[op(fast)]
pub fn op_void_sync() {}
#[op]
diff --git a/core/runtime.rs b/core/runtime.rs
index 64e7f635c..caadd0089 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -230,6 +230,7 @@ fn v8_init(
" --harmony-import-assertions",
" --no-validate-asm",
" --turbo_fast_api_calls",
+ " --allow-natives-syntax",
);
if predictable {
@@ -242,6 +243,9 @@ fn v8_init(
}
}
+pub const V8_WRAPPER_TYPE_INDEX: i32 = 0;
+pub const V8_WRAPPER_OBJECT_INDEX: i32 = 1;
+
#[derive(Default)]
pub struct RuntimeOptions {
/// Source map reference for errors.
@@ -317,7 +321,6 @@ impl JsRuntime {
if let Some(get_error_class_fn) = options.get_error_class_fn {
op_state.get_error_class_fn = get_error_class_fn;
}
-
let op_state = Rc::new(RefCell::new(op_state));
let op_ctxs = ops
.into_iter()
@@ -330,12 +333,13 @@ impl JsRuntime {
.collect::<Vec<_>>()
.into_boxed_slice();
+ let refs = bindings::external_references(&op_ctxs);
+ let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs));
let global_context;
let (mut isolate, maybe_snapshot_creator) = if options.will_snapshot {
// TODO(ry) Support loading snapshots before snapshotting.
assert!(options.startup_snapshot.is_none());
- let mut creator =
- v8::SnapshotCreator::new(Some(&bindings::EXTERNAL_REFERENCES));
+ let mut creator = v8::SnapshotCreator::new(Some(refs));
// SAFETY: `get_owned_isolate` is unsafe because it may only be called
// once. This is the only place we call this function, so this call is
// safe.
@@ -352,8 +356,13 @@ impl JsRuntime {
let mut params = options
.create_params
.take()
- .unwrap_or_else(v8::Isolate::create_params)
- .external_references(&**bindings::EXTERNAL_REFERENCES);
+ .unwrap_or_else(|| {
+ v8::Isolate::create_params().embedder_wrapper_type_info_offsets(
+ V8_WRAPPER_TYPE_INDEX,
+ V8_WRAPPER_OBJECT_INDEX,
+ )
+ })
+ .external_references(&**refs);
let snapshot_loaded = if let Some(snapshot) = options.startup_snapshot {
params = match snapshot {
Snapshot::Static(data) => params.snapshot_blob(data),
diff --git a/ext/net/lib.rs b/ext/net/lib.rs
index c95348020..249170060 100644
--- a/ext/net/lib.rs
+++ b/ext/net/lib.rs
@@ -81,6 +81,8 @@ pub fn init<P: NetPermissions + 'static>(
unstable: bool,
unsafely_ignore_certificate_errors: Option<Vec<String>>,
) -> Extension {
+ let mut ops = ops::init::<P>();
+ ops.extend(ops_tls::init::<P>());
Extension::builder()
.js(include_js_files!(
prefix "deno:ext/net",
@@ -88,7 +90,7 @@ pub fn init<P: NetPermissions + 'static>(
"02_tls.js",
"04_net_unstable.js",
))
- .ops([&ops::init::<P>()[..], &ops_tls::init::<P>()[..]].concat())
+ .ops(ops)
.state(move |state| {
state.put(DefaultTlsOptions {
root_cert_store: root_cert_store.clone(),
diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js
index a4ce33a18..1a456f9bb 100644
--- a/ext/web/02_timers.js
+++ b/ext/web/02_timers.js
@@ -23,9 +23,11 @@
const { webidl } = window.__bootstrap;
const { reportException } = window.__bootstrap.event;
const { assert } = window.__bootstrap.infra;
+ // deno-lint-ignore camelcase
+ const { op_now } = Deno.core.ops;
function opNow() {
- return core.opSync("op_now");
+ return op_now.call();
}
// ---------------------------------------------------------------------------
diff --git a/ext/web/timers.rs b/ext/web/timers.rs
index f57baa559..ebef717ba 100644
--- a/ext/web/timers.rs
+++ b/ext/web/timers.rs
@@ -28,7 +28,7 @@ pub type StartTime = Instant;
// If the High precision flag is not set, the
// nanoseconds are rounded on 2ms.
#[op]
-pub fn op_now<TP>(state: &mut OpState, _argument: ()) -> f64
+pub fn op_now<TP>(state: &mut OpState) -> f64
where
TP: TimersPermission + 'static,
{
diff --git a/ops/Cargo.toml b/ops/Cargo.toml
index e0453b9e2..9628ca91f 100644
--- a/ops/Cargo.toml
+++ b/ops/Cargo.toml
@@ -17,3 +17,7 @@ proc-macro2 = "1"
quote = "1"
regex = "1.5.6"
syn = { version = "1", features = ["full", "extra-traits"] }
+
+[dev-dependencies]
+deno_core = { path = "../core" }
+trybuild = "1.0.61"
diff --git a/ops/README.md b/ops/README.md
index 02ee266f7..7b61704b5 100644
--- a/ops/README.md
+++ b/ops/README.md
@@ -14,3 +14,31 @@ Extension::builder()
.ops(vec![op_add::decl()])
.build();
```
+
+## Peformance
+
+The macro can optimize away code, short circuit fast paths and generate a Fast
+API impl.
+
+Cases where code is optimized away:
+
+- `-> ()` skips serde_v8 and `rv.set` calls.
+- `-> Result<(), E>` skips serde_v8 and `rv.set` calls for `Ok()` branch.
+- `-> ResourceId` or `-> [int]` types will use specialized method like
+ `v8::ReturnValue::set_uint32`. A fast path for SMI.
+- `-> Result<ResourceId, E>` or `-> Result<[int], E>` types will be optimized
+ like above for the `Ok()` branch.
+
+### Fast calls
+
+The macro will infer and try to auto generate V8 fast API call trait impl for
+`sync` ops with:
+
+- arguments: integers / `&mut OpState`
+- return_type: integers
+
+The `#[op(fast)]` attribute can be used to enforce fast call generation at
+compile time.
+
+Trait gen for `async` ops & a ZeroCopyBuf equivalent type is planned and will be
+added soon.
diff --git a/ops/lib.rs b/ops/lib.rs
index df8abf61e..bd40630ab 100644
--- a/ops/lib.rs
+++ b/ops/lib.rs
@@ -1,10 +1,13 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use core::panic;
use once_cell::sync::Lazy;
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 regex::Regex;
@@ -14,6 +17,9 @@ use syn::FnArg;
use syn::GenericParam;
use syn::Ident;
+#[cfg(test)]
+mod tests;
+
// Identifier to the `deno_core` crate.
//
// If macro called in deno_core, `crate` is used.
@@ -44,6 +50,7 @@ fn core_import() -> TokenStream2 {
struct MacroArgs {
is_unstable: bool,
is_v8: bool,
+ must_be_fast: bool,
}
impl syn::parse::Parse for MacroArgs {
@@ -55,7 +62,7 @@ impl syn::parse::Parse for MacroArgs {
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"].contains(var) {
+ if !["unstable", "v8", "fast"].contains(var) {
return Err(syn::Error::new(
input.span(),
"Ops expect #[op] or #[op(unstable)]",
@@ -65,6 +72,7 @@ impl syn::parse::Parse for MacroArgs {
Ok(Self {
is_unstable: vars.contains(&"unstable"),
is_v8: vars.contains(&"v8"),
+ must_be_fast: vars.contains(&"fast"),
})
}
}
@@ -72,7 +80,11 @@ impl syn::parse::Parse for MacroArgs {
#[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 } = margs;
+ let MacroArgs {
+ is_unstable,
+ is_v8,
+ must_be_fast,
+ } = margs;
let func = syn::parse::<syn::ItemFn>(item).expect("expected a function");
let name = &func.sig.ident;
let mut generics = func.sig.generics.clone();
@@ -102,6 +114,8 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
} else {
codegen_v8_sync(&core, &func, margs)
};
+ let (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
@@ -129,6 +143,7 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
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,
@@ -147,6 +162,8 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream {
#v8_body
}
}
+
+ #fast_impl
}.into()
}
@@ -260,6 +277,114 @@ 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,
+) -> (TokenStream2, TokenStream2) {
+ 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 must_be_fast && is_async {
+ panic!("async op cannot be a fast api. enforced by #[op(fast)]")
+ }
+ if !is_async {
+ if let Some(FastApiSyn {
+ args,
+ ret,
+ use_recv,
+ }) = fast_info
+ {
+ let inputs = &f
+ .sig
+ .inputs
+ .iter()
+ .skip(if use_recv { 1 } else { 0 })
+ .collect::<Vec<_>>();
+ let input_idents = f
+ .sig
+ .inputs
+ .iter()
+ .map(|a| match a {
+ FnArg::Receiver(_) => unreachable!(),
+ FnArg::Typed(t) => match &*t.pat {
+ syn::Pat::Ident(i) => format_ident!("{}", i.ident),
+ _ => unreachable!(),
+ },
+ })
+ .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),*) {
+ let op_ctx = recv.get_aligned_pointer_from_internal_field(#core::_ops::V8_WRAPPER_OBJECT_INDEX);
+ let op_id = op_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 = &f.sig.output;
+ let func_name = format_ident!("func_{}", name);
+ let recv_decl = if use_recv {
+ quote! {
+ let ptr = unsafe { recv.get_aligned_pointer_from_internal_field(#core::_ops::V8_WRAPPER_OBJECT_INDEX) };
+ let op_ctx = unsafe { &*(ptr as *const #core::_ops::OpCtx) };
+ let state = &mut op_ctx.state.borrow_mut();
+ }
+ } else {
+ quote!()
+ };
+
+ (
+ quote! {
+ fn #func_name #generics (recv: #core::v8::Local<#core::v8::Object>, #(#inputs),*) #output #where_clause {
+ #recv_decl
+ #name::call::<#type_params>(#(#input_idents),*)
+ }
+ },
+ quote! {
+ #func_name #ty_generics as *const _
+ },
+ )
+ };
+ return (
+ quote! {
+ #trampoline
+ impl #impl_generics #core::v8::fast_api::FastFunction for #name #ty_generics {
+ 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(#name #ty_generics)) },
+ );
+ }
+ }
+
+ // Default impl to satisfy generic bounds for non-fast ops
+ (quote! {}, quote! { None })
+}
+
/// Generate the body of a v8 func for a sync op
fn codegen_v8_sync(
core: &TokenStream2,
@@ -277,7 +402,6 @@ fn codegen_v8_sync(
.collect::<Vec<_>>();
let rust_i0 = special_args.len();
let args_head = special_args.into_iter().collect::<TokenStream2>();
-
let (arg_decls, args_tail) = codegen_args(core, f, rust_i0, 0);
let ret = codegen_sync_ret(core, &f.sig.output);
let type_params = exclude_lifetime_params(&f.sig.generics.params);
@@ -300,6 +424,124 @@ fn codegen_v8_sync(
}
}
+struct FastApiSyn {
+ args: TokenStream2,
+ ret: TokenStream2,
+ use_recv: bool,
+}
+
+fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
+ // TODO(@littledivy): Support generics
+ if !f.sig.generics.params.is_empty() {
+ return None;
+ }
+
+ let inputs = &f.sig.inputs;
+ let ret = match &f.sig.output {
+ syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void),
+ syn::ReturnType::Type(_, ty) => match is_fast_scalar(core, ty, true) {
+ Some(ret) => ret,
+ None => return None,
+ },
+ };
+
+ let mut use_recv = false;
+ let mut args = vec![quote! { #core::v8::fast_api::Type::V8Value }];
+ for (pos, input) in inputs.iter().enumerate() {
+ if pos == 0 && is_mut_ref_opstate(input) {
+ use_recv = true;
+ continue;
+ }
+
+ let ty = match input {
+ syn::FnArg::Typed(pat) => &pat.ty,
+ _ => unreachable!(),
+ };
+
+ match is_fast_scalar(core, ty, false) {
+ None => match is_fast_arg_sequence(core, ty) {
+ Some(arg) => {
+ args.push(arg);
+ }
+ // early return, this function cannot be a fast call.
+ None => return None,
+ },
+ Some(arg) => {
+ args.push(arg);
+ }
+ }
+ }
+
+ let args = args
+ .iter()
+ .map(|arg| format!("{}", arg))
+ .collect::<Vec<_>>()
+ .join(", ");
+ Some(FastApiSyn {
+ args: args.parse().unwrap(),
+ ret,
+ use_recv,
+ })
+}
+
+// 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_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_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 }),
+ _ => None,
+ }
+}
+
fn codegen_args(
core: &TokenStream2,
f: &syn::ItemFn,
@@ -443,7 +685,7 @@ fn is_resource_id(arg: impl ToTokens) -> bool {
RE.is_match(&tokens(arg))
}
-fn is_mut_ref_opstate(arg: &syn::FnArg) -> bool {
+fn is_mut_ref_opstate(arg: impl ToTokens) -> bool {
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#": & mut (?:deno_core :: )?OpState$"#).unwrap());
RE.is_match(&tokens(arg))
diff --git a/ops/tests/compile_fail/unsupported.rs b/ops/tests/compile_fail/unsupported.rs
new file mode 100644
index 000000000..1c4d6407a
--- /dev/null
+++ b/ops/tests/compile_fail/unsupported.rs
@@ -0,0 +1,27 @@
+// Copyright 2019-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_ops::op;
+
+#[op(fast)]
+fn op_result_return(a: i32, b: i32) -> Result<(), ()> {
+ a + b
+}
+
+#[op(fast)]
+fn op_u8_arg(a: u8, b: u8) {
+ //
+}
+
+#[op(fast)]
+fn op_u16_arg(a: u16, b: u16) {
+ //
+}
+
+#[op(fast)]
+async fn op_async_fn(a: i32, b: i32) -> i32 {
+ a + b
+}
+
+fn main() {
+ // pass
+}
diff --git a/ops/tests/compile_fail/unsupported.stderr b/ops/tests/compile_fail/unsupported.stderr
new file mode 100644
index 000000000..68c9f7f16
--- /dev/null
+++ b/ops/tests/compile_fail/unsupported.stderr
@@ -0,0 +1,31 @@
+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:15:1
+ |
+15 | #[op(fast)]
+ | ^^^^^^^^^^^
+ |
+ = help: message: op cannot be a fast api. enforced by #[op(fast)]
+
+error: custom attribute panicked
+ --> tests/compile_fail/unsupported.rs:20:1
+ |
+20 | #[op(fast)]
+ | ^^^^^^^^^^^
+ |
+ = help: message: async op cannot be a fast api. enforced by #[op(fast)]
diff --git a/ops/tests/mod.rs b/ops/tests/mod.rs
new file mode 100644
index 000000000..522647f51
--- /dev/null
+++ b/ops/tests/mod.rs
@@ -0,0 +1,5 @@
+#[test]
+fn op_macro() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/compile_fail/*.rs");
+}