1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(ops): V8 Fast Calls (#15291)

This commit is contained in:
Divy Srivastava 2022-08-21 17:37:53 +05:30 committed by GitHub
parent e39d4e3e7f
commit 906aa78af3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 504 additions and 38 deletions

23
Cargo.lock generated
View file

@ -1149,12 +1149,14 @@ dependencies = [
name = "deno_ops"
version = "0.25.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]]
@ -1930,6 +1932,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"
@ -4990,6 +4998,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"

View file

@ -5,12 +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.fast(a, b);
}
// deno-lint-ignore no-inner-declarations
function addJS(a, b) {
return a + b;
}
Deno.bench("op_add", () => add(1, 2));
Deno.bench("add_js", () => addJS(1, 2));
}
// deno-lint-ignore camelcase
const { op_void_sync } = Deno.core.ops;
function sync() {
return op_void_sync.fast();
}
sync(); // Warmup
// Void ops measure op-overhead
Deno.bench(
"op_void_sync",
{ n: 1e7 },
() => Deno.core.ops.op_void_sync(),
);
Deno.bench("op_void_sync", () => sync());
Deno.bench(
"op_void_async",

View file

@ -9,17 +9,39 @@ 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 {
function: call_console.map_fn_to(),
}])
});
pub fn external_references(
ops: &[OpCtx],
snapshot_loaded: bool,
) -> v8::ExternalReferences {
let mut references = vec![v8::ExternalReference {
function: call_console.map_fn_to(),
}];
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 snapshot_loaded {
if let Some(fast_fn) = &ctx.decl.fast_fn {
references.push(v8::ExternalReference {
pointer: fast_fn.function() as _,
});
}
}
}
let refs = v8::ExternalReferences::new(&references);
// Leak, V8 takes ownership of the references.
std::mem::forget(references);
refs
}
// TODO(nayeemrmn): Move to runtime and/or make `pub(crate)`.
pub fn script_origin<'a>(
@ -82,7 +104,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 +117,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 +126,46 @@ 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);
// If this is a fast op, we don't want it to be in the snapshot.
// Only initialize once snapshot is loaded.
if ctx.decl.fast_fn.is_some() && snapshot_loaded {
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,
"fast",
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());
} else {
set_func_raw(
scope,
ops_obj,
ctx.decl.name,
ctx.decl.v8_fn_ptr,
ctx_ptr,
&None,
snapshot_loaded,
);
}
}
}
@ -129,13 +189,25 @@ 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 {
// Don't initialize fast ops when snapshotting, the external references count mismatch.
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());
}

View file

@ -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,14 @@ 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 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 {

View file

@ -118,6 +118,8 @@ pub mod _ops {
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

View file

@ -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]

View file

@ -328,7 +328,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()
@ -341,12 +340,14 @@ impl JsRuntime {
.collect::<Vec<_>>()
.into_boxed_slice();
let refs = bindings::external_references(&op_ctxs, !options.will_snapshot);
// V8 takes ownership of external_references.
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.
@ -369,7 +370,7 @@ impl JsRuntime {
V8_WRAPPER_OBJECT_INDEX,
)
})
.external_references(&**bindings::EXTERNAL_REFERENCES);
.external_references(&**refs);
let snapshot_loaded = if let Some(snapshot) = options.startup_snapshot {
params = match snapshot {
Snapshot::Static(data) => params.snapshot_blob(data),

View file

@ -1344,8 +1344,8 @@ async fn op_flash_next_async(
// the ContextScope creation is optimized away and the op is as simple as:
// f(info: *const v8::FunctionCallbackInfo) { let rv = ...; rv.set_uint32(op_flash_next()); }
#[op]
fn op_flash_next(op_state: &mut OpState) -> u32 {
let flash_ctx = op_state.borrow_mut::<FlashContext>();
fn op_flash_next(state: &mut OpState) -> u32 {
let flash_ctx = state.borrow_mut::<FlashContext>();
let ctx = flash_ctx.servers.get_mut(&0).unwrap();
next_request_sync(ctx)
}
@ -1353,8 +1353,8 @@ fn op_flash_next(op_state: &mut OpState) -> u32 {
// Syncrhonous version of op_flash_next_async. Under heavy load,
// this can collect buffered requests from rx channel and return tokens in a single batch.
#[op]
fn op_flash_next_server(op_state: &mut OpState, server_id: u32) -> u32 {
let flash_ctx = op_state.borrow_mut::<FlashContext>();
fn op_flash_next_server(state: &mut OpState, server_id: u32) -> u32 {
let flash_ctx = state.borrow_mut::<FlashContext>();
let ctx = flash_ctx.servers.get_mut(&server_id).unwrap();
next_request_sync(ctx)
}

View file

@ -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(),

View file

@ -17,3 +17,7 @@ proc-macro2 = "1"
quote = "1"
regex = "1.6.0"
syn = { version = "1", features = ["full", "extra-traits"] }
[dev-dependencies]
deno_core = { path = "../core" }
trybuild = "1.0.61"

View file

@ -4,9 +4,9 @@
```rust
// Declare an op.
#[op]
pub fn op_add(_: &mut OpState, a: i32, b: i32) -> Result<i32, AnyError> {
Ok(a + b)
#[op(fast)]
pub fn op_add(_: &mut OpState, a: i32, b: i32) -> i32 {
a + b
}
// Register with an extension.
@ -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 shoukd 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.

View file

@ -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()
}
@ -265,6 +282,117 @@ 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) {
if !must_be_fast {
return (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 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,
@ -282,7 +410,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);
@ -305,6 +432,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,
@ -448,7 +693,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))

View 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
}

View 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
View file

@ -0,0 +1,5 @@
#[test]
fn op_macro() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/*.rs");
}