mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(ops): V8 Fast Calls (#15122)
Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
244c00d95b
commit
03dc3b8972
17 changed files with 494 additions and 32 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -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]]
|
||||
|
@ -1897,6 +1899,12 @@ dependencies = [
|
|||
"polyval",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -4953,6 +4961,21 @@ version = "0.2.3"
|
|||
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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
250
ops/lib.rs
250
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))
|
||||
|
|
27
ops/tests/compile_fail/unsupported.rs
Normal file
27
ops/tests/compile_fail/unsupported.rs
Normal file
|
@ -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
|
||||
}
|
31
ops/tests/compile_fail/unsupported.stderr
Normal file
31
ops/tests/compile_fail/unsupported.stderr
Normal file
|
@ -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)]
|
5
ops/tests/mod.rs
Normal file
5
ops/tests/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
#[test]
|
||||
fn op_macro() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/compile_fail/*.rs");
|
||||
}
|
Loading…
Reference in a new issue